on master help sign in
279 files changed+12649−6214
since splitsnapshots
unifiedsplit
.circleci/config.yml+19 .circleci/tickle.sh+24 .ghci+1−1 .gitignore+13−9 CHANGELOG.md+575 CODE_OF_CONDUCT.md+45 LICENSE+1−1 Makefile+9 README.markdown+9−6 examples/brochure/about.rst → data/example/about.rst data/example/contact.markdown+6 data/example/css/default.css+141 data/example/images/haskell-logo.png data/example/index.html+16 data/example/posts/2015-08-12-spqr.markdown+59 data/example/posts/2015-10-07-rosa-rosa-rosam.markdown+46 data/example/posts/2015-11-28-carpe-diem.markdown+50 data/example/posts/2015-12-07-tu-quoque.markdown+58 data/example/site.hs+67 data/example/templates/archive.html+2 data/example/templates/default.html+33 data/example/templates/post-list.html+7 data/example/templates/post.html+11 data/templates/atom-item.xml+2−1 data/templates/atom.xml+2−1 data/templates/rss-item.xml+2−1 data/templates/rss.xml+4−3 examples/brochure/code.lhs−21 examples/brochure/css/default.css−17 examples/brochure/css/syntax.css−18 examples/brochure/hakyll.hs−20 examples/brochure/index.markdown−25 examples/brochure/templates/default.html−21 examples/feedblog/css/default.css−17 examples/feedblog/hakyll.hs−68 examples/feedblog/posts/2009-11-05-a-first-post.markdown−23 examples/feedblog/posts/2009-11-10-another-post.markdown−17 examples/feedblog/posts/2009-11-28-a-third-post.markdown−21 examples/feedblog/posts/2009-12-04-this-blog-aint-dead.markdown−14 examples/feedblog/posts/2009-12-23-almost-christmas.markdown−15 examples/feedblog/templates/default.html−23 examples/feedblog/templates/index.html−14 examples/feedblog/templates/post.html−5 examples/feedblog/templates/postitem.html−4 examples/feedblog/templates/posts.html−4 examples/hakyll/changelog.markdown−133 examples/hakyll/css/syntax.css−18 examples/hakyll/examples/brochure.zip examples/hakyll/examples/categoryblog.zip examples/hakyll/examples/feedblog.zip examples/hakyll/examples/morepages.zip examples/hakyll/examples/simpleblog.zip examples/hakyll/examples/tagblog.zip examples/hakyll/hakyll.hs−52 examples/hakyll/index.markdown−51 examples/hakyll/philosophy.markdown−28 examples/hakyll/sidebar.markdown−8 examples/hakyll/templates/tutorial.html−10 examples/hakyll/templates/tutorialitem.html−3 examples/hakyll/templates/tutorials.html−14 examples/hakyll/tutorial.markdown−293 examples/morepages/about.markdown−28 examples/morepages/css/default.css−28 examples/morepages/footer.markdown−19 examples/morepages/hakyll.hs−29 examples/morepages/index.markdown−25 examples/morepages/products.markdown−24 examples/morepages/templates/default.html−24 examples/simpleblog/css/default.css−17 examples/simpleblog/hack examples/simpleblog/hakyll.hs−55 examples/simpleblog/posts/2009-11-05-a-first-post.markdown−21 examples/simpleblog/posts/2009-11-10-another-post.markdown−15 examples/simpleblog/posts/2009-11-28-a-third-post.markdown−19 examples/simpleblog/posts/2009-12-04-this-blog-aint-dead.markdown−12 examples/simpleblog/posts/2009-12-23-almost-christmas.markdown−13 examples/simpleblog/templates/default.html−19 examples/simpleblog/templates/index.html−14 examples/simpleblog/templates/post.html−5 examples/simpleblog/templates/postitem.html−4 examples/simpleblog/templates/posts.html−4 examples/tagblog/css/default.css−17 examples/tagblog/hakyll.hs−99 examples/tagblog/posts/2009-11-05-a-first-post.markdown−22 examples/tagblog/posts/2009-11-10-another-post.markdown−16 examples/tagblog/posts/2009-11-28-a-third-post.markdown−20 examples/tagblog/posts/2009-12-04-this-blog-aint-dead.markdown−13 examples/tagblog/posts/2009-12-23-almost-christmas.markdown−14 examples/tagblog/templates/default.html−23 examples/tagblog/templates/index.html−19 examples/tagblog/templates/post.html−7 examples/tagblog/templates/postitem.html−4 examples/tagblog/templates/posts.html−4 hakyll.cabal+327−114 lib/Data/List/Extended.hs+15 lib/Data/Yaml/Extended.hs+24 lib/Hakyll.hs+66 lib/Hakyll/Check.hs+302 lib/Hakyll/Commands.hs+164 lib/Hakyll/Core/Compiler.hs+222 lib/Hakyll/Core/Compiler/Internal.hs+364 lib/Hakyll/Core/Compiler/Require.hs+123 lib/Hakyll/Core/Configuration.hs+134 lib/Hakyll/Core/Dependencies.hs+146 lib/Hakyll/Core/File.hs+93 lib/Hakyll/Core/Identifier.hs+80 lib/Hakyll/Core/Identifier/Pattern.hs+264 lib/Hakyll/Core/Identifier/Pattern/Internal.hs+92 lib/Hakyll/Core/Item.hs+63 lib/Hakyll/Core/Item/SomeItem.hs+23 lib/Hakyll/Core/Logger.hs+103 lib/Hakyll/Core/Metadata.hs+138 lib/Hakyll/Core/Provider.hs+43 lib/Hakyll/Core/Provider/Internal.hs+202 lib/Hakyll/Core/Provider/Metadata.hs+152 lib/Hakyll/Core/Provider/MetadataCache.hs+62 lib/Hakyll/Core/Routes.hs+211 lib/Hakyll/Core/Rules.hs+223 lib/Hakyll/Core/Rules/Internal.hs+123 lib/Hakyll/Core/Runtime.hs+276 lib/Hakyll/Core/Store.hs+200 lib/Hakyll/Core/UnixFilter.hs+160 lib/Hakyll/Core/Util/File.hs+56 lib/Hakyll/Core/Util/Parser.hs+31 src/Hakyll/Core/Util/String.hs → lib/Hakyll/Core/Util/String.hs+36−6 lib/Hakyll/Core/Writable.hs+56 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/Feed.hs+173 lib/Hakyll/Web/Html.hs+188 lib/Hakyll/Web/Html/RelativizeUrls.hs+52 lib/Hakyll/Web/Paginate.hs+153 lib/Hakyll/Web/Pandoc.hs+165 lib/Hakyll/Web/Pandoc/Biblio.hs+126 lib/Hakyll/Web/Pandoc/Binary.hs+33 lib/Hakyll/Web/Pandoc/FileType.hs+74 lib/Hakyll/Web/Redirect.hs+100 lib/Hakyll/Web/Tags.hs+345 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/Web/Template/List.hs+91 src-inotify/Hakyll/Web/Preview/Poll.hs−50 src-interval/Hakyll/Web/Preview/Poll.hs−36 src/Hakyll.hs−61 src/Hakyll/Core/CompiledItem.hs−47 src/Hakyll/Core/Compiler.hs−342 src/Hakyll/Core/Compiler/Internal.hs−156 src/Hakyll/Core/Configuration.hs−44 src/Hakyll/Core/DependencyAnalyzer.hs−155 src/Hakyll/Core/DirectedGraph.hs−84 src/Hakyll/Core/DirectedGraph/Dot.hs−32 src/Hakyll/Core/DirectedGraph/Internal.hs−52 src/Hakyll/Core/Identifier.hs−82 src/Hakyll/Core/Identifier/Pattern.hs−189 src/Hakyll/Core/Logger.hs−99 src/Hakyll/Core/Resource.hs−24 src/Hakyll/Core/Resource/Provider.hs−101 src/Hakyll/Core/Resource/Provider/Dummy.hs−21 src/Hakyll/Core/Resource/Provider/File.hs−24 src/Hakyll/Core/Routes.hs−136 src/Hakyll/Core/Rules.hs−232 src/Hakyll/Core/Rules/Internal.hs−99 src/Hakyll/Core/Run.hs−197 src/Hakyll/Core/Store.hs−93 src/Hakyll/Core/UnixFilter.hs−76 src/Hakyll/Core/Util/Arrow.hs−25 src/Hakyll/Core/Util/File.hs−90 src/Hakyll/Core/Writable.hs−30 src/Hakyll/Core/Writable/CopyFile.hs−29 src/Hakyll/Core/Writable/WritableTuple.hs−37 src/Hakyll/Main.hs−113 src/Hakyll/Web/Blaze.hs−34 src/Hakyll/Web/CompressCss.hs−51 src/Hakyll/Web/Feed.hs−124 src/Hakyll/Web/Page.hs−124 src/Hakyll/Web/Page/Internal.hs−50 src/Hakyll/Web/Page/Metadata.hs−156 src/Hakyll/Web/Page/Read.hs−60 src/Hakyll/Web/Pandoc.hs−110 src/Hakyll/Web/Pandoc/FileType.hs−55 src/Hakyll/Web/Preview/Server.hs−73 src/Hakyll/Web/RelativizeUrls.hs−62 src/Hakyll/Web/Tags.hs−204 src/Hakyll/Web/Template.hs−109 src/Hakyll/Web/Template/Internal.hs−45 src/Hakyll/Web/Template/Read.hs−10 src/Hakyll/Web/Template/Read/Hakyll.hs−35 src/Hakyll/Web/Template/Read/Hamlet.hs−46 src/Hakyll/Web/Util/Url.hs−35 src/Init.hs+123 stack.yaml+33 test.hs+30 tests/Hakyll/Core/Dependencies/Tests.hs+71 tests/Hakyll/Core/DependencyAnalyzer/Tests.hs−70 tests/Hakyll/Core/Identifier/Tests.hs+32−12 tests/Hakyll/Core/Provider/Metadata/Tests.hs+62 tests/Hakyll/Core/Provider/Tests.hs+36 tests/Hakyll/Core/Routes/Tests.hs+41−15 tests/Hakyll/Core/Rules/Tests.hs+104 tests/Hakyll/Core/Runtime/Tests.hs+96 tests/Hakyll/Core/Store/Tests.hs+83 tests/Hakyll/Core/UnixFilter/Tests.hs+66 tests/Hakyll/Core/Util/String/Tests.hs+45 tests/Hakyll/Web/CompressCss/Tests.hs+64 tests/Hakyll/Web/Html/RelativizeUrls/Tests.hs+38 tests/Hakyll/Web/Html/Tests.hs+84 tests/Hakyll/Web/Page/Metadata/Tests.hs−54 tests/Hakyll/Web/Page/Tests.hs−32 tests/Hakyll/Web/Pandoc/FileType/Tests.hs+26 tests/Hakyll/Web/RelativizeUrls/Tests.hs−20 tests/Hakyll/Web/Template/Context/Tests.hs+95 tests/Hakyll/Web/Template/Tests.hs+149−37 tests/Hakyll/Web/Util/Url/Tests.hs−25 tests/TestSuite.hs+48−24 tests/TestSuite/Util.hs+117−8 tests/data/embed.html+1 tests/data/example.md+5 tests/data/example.md.metadata+5 tests/data/images/favicon.ico 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/2010-08-26-birthday.md+1 tests/data/posts/2018-09-26.md+1 tests/data/posts/2019/05/10/tomorrow.md+1 tests/data/russian.md+14 tests/data/strip.html+34 tests/data/strip.html.out+18 tests/data/template.html+30 tests/data/template.html.out+28 examples/hakyll/about.markdown → web/about.markdown+14−8 examples/hakyll/css/default.css → web/css/default.css+45−30 web/css/syntax.css+61 web/examples.markdown+215 web/images/brochure-files.png web/images/hakyll-system-1.png web/images/hakyll-system-2.png web/images/hakyll-system-3.png examples/hakyll/images/lambda.png → web/images/lambda.png web/index.markdown+42 web/libraries.markdown+21 examples/hakyll/reference.markdown → web/reference.markdown+1−1 web/site.hs+117 examples/hakyll/templates/default.html → web/templates/default.html+19−6 web/templates/tutorial.html+21 web/templates/tutorials.html+56 web/tutorials/01-installation.markdown+54 web/tutorials/02-basics.markdown+49 web/tutorials/03-rules-routes-compilers.markdown+137 web/tutorials/04-compilers.markdown+295 web/tutorials/05-snapshots-feeds.markdown+129 web/tutorials/06-versions.markdown+68 web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+96 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+95 web/tutorials/github-pages-tutorial.md+181 web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+78 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+52 web/tutorials/using-teasers-in-hakyll.markdown+109
.circleci/config.ymlnew file+19
@@ -0,0 +1,19 @@
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.shnew file+24
@@ -0,0 +1,24 @@
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+1−1
@@ -1 +1 @@
1−:set -isrc -isrc-inotify -itests -idist/build/autogen
1+:set -isrc -itests -idist/build/autogen -DPREVIEW_SERVER
.gitignore+13−9
@@ -1,8 +1,4 @@
1−##
2−## gitignore(5) -- specify ignored files
3−##
4−
5−# Ignore swap files and cabal output.
1+# Ignore swap files and stack/cabal output.
62*.hi
73*.o
84*.swo
@@ -12,13 +8,21 @@
128TAGS
139dist
1410tags
11+cabal.sandbox.config
12+.cabal-sandbox/
13+.stack-work
1514
1615# Ignore test builds.
1716tests/Main
17+_cache
18+_store
19+_site
1820
1921# Rest of the file: ignore examples
20−examples/*/_cache
21−examples/*/_site
22−examples/*/hakyll
23−examples/hakyll/reference
22+web/_cache
23+web/_site
24+web/site
25+web/reference
2426
27+# This is generated from the CHANGELOG
28+web/releases.markdown
CHANGELOG.mdnew file+575
@@ -0,0 +1,575 @@
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.mdnew file+45
@@ -0,0 +1,45 @@
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, Jasper Van der Jeugt
1+Copyright (c) 2009 - 2017, Jasper Van der Jeugt
22 
33All rights reserved.
44 
Makefilenew file+9
@@ -0,0 +1,9 @@
1+PACKAGE="$(shell stack list-dependencies --separator='-' | grep hakyll)"
2+LOCAL_DOC_ROOT="$(shell stack path --local-doc-root)"
3+
4+# Generate the docs and copy them to the website dir
5+haddock:
6+ stack build --haddock --no-haddock-deps
7+ rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
8+
9+.PHONY: haddock
README.markdown+9−6
@@ -1,12 +1,15 @@
1−# Hakyll
1+# hakyll
22
3−Hakyll is a static site generator library in Haskell.
3+[![Build Status](https://img.shields.io/circleci/project/github/jaspervdj/hakyll.svg)](https://circleci.com/gh/jaspervdj/hakyll)
44
5−## Installation
5+Hakyll is a static site generator library in Haskell. More information
6+(including a tutorial) can be found on
7+[the hakyll homepage](http://jaspervdj.be/hakyll).
8+
9+You can install this library using cabal:
610
711 cabal install hakyll
812
9−## Tutorial and information
13+Or using stack:
1014
11−More information can be found on
12−[the hakyll homepage](http://jaspervdj.be/hakyll).
15+ stack install hakyll
examples/brochure/about.rst → data/example/about.rstrenamed

The content is the same.

data/example/contact.markdownnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: Contact
3+---
4+
5+I live in a small hut in the mountains of Kumano Kodō on Kii Hantō and would not
6+like to be contacted.
data/example/css/default.cssnew file+141
@@ -0,0 +1,141 @@
1+html {
2+ font-size: 62.5%;
3+}
4+
5+body {
6+ font-size: 1.6rem;
7+ color: #000;
8+}
9+
10+header {
11+ border-bottom: 0.2rem solid #000;
12+}
13+
14+nav {
15+ text-align: right;
16+}
17+
18+nav a {
19+ font-size: 1.8rem;
20+ font-weight: bold;
21+ color: black;
22+ text-decoration: none;
23+ text-transform: uppercase;
24+}
25+
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;
32+}
33+
34+h1 {
35+ font-size: 2.4rem;
36+}
37+
38+h2 {
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+ }
110+}
111+
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+ }
141+}
data/example/images/haskell-logo.pngnew file

Binary file not shown.

data/example/index.htmlnew file+16
@@ -0,0 +1,16 @@
1+---
2+title: Home
3+---
4+
5+<h2>Welcome</h2>
6+
7+<img src="/images/haskell-logo.png" style="float: right; margin: 10px;" />
8+
9+<p>Welcome to my blog!</p>
10+
11+<p>I've reproduced a list of recent posts here for your reading pleasure:</p>
12+
13+<h2>Posts</h2>
14+$partial("templates/post-list.html")$
15+
16+<p>…or you can find more in the <a href="/archive.html">archives</a>.</p>
data/example/posts/2015-08-12-spqr.markdownnew file+59
@@ -0,0 +1,59 @@
1+---
2+title: S.P.Q.R.
3+---
4+
5+Mauris in lorem nisl. Maecenas tempus facilisis ante, eget viverra nisl
6+tincidunt et. Donec turpis lectus, mattis ac malesuada a, accumsan eu libero.
7+Morbi condimentum, tortor et tincidunt ullamcorper, sem quam pretium nulla, id
8+convallis lectus libero nec turpis. Proin dapibus nisi id est sodales nec
9+ultrices tortor pellentesque. Vivamus vel nisi ac lacus sollicitudin vulputate
10+ac ut ligula. Nullam feugiat risus eget eros gravida in molestie sapien euismod.
11+Nunc sed hendrerit orci. Nulla mollis consequat lorem ac blandit. Ut et turpis
12+mauris. Nulla est odio, posuere id ullamcorper sit amet, tincidunt vel justo.
13+Curabitur placerat tincidunt varius. Nulla vulputate, ipsum eu consectetur
14+mollis, dui nibh aliquam neque, at ultricies leo ligula et arcu. Proin et mi
15+eget tellus sodales lobortis. Sed tempor, urna vel pulvinar faucibus, lectus
16+urna vehicula ante, at facilisis dolor odio at lorem. Morbi vehicula euismod
17+urna, et imperdiet urna ornare vitae.
18+
19+Sed tincidunt sollicitudin ultrices. In hac habitasse platea dictumst. Morbi
20+ligula lectus, egestas at ultricies nec, fringilla et tellus. Duis urna lorem,
21+bibendum a ornare sed, euismod sed nunc. Aliquam tempor massa at velit fringilla
22+fringilla. Praesent sit amet tempor felis. Maecenas id felis ac velit aliquam
23+tempor a sit amet orci. Nunc placerat nulla pellentesque sem commodo cursus.
24+Praesent quis sapien orci, quis ultricies augue. Nam vestibulum sem non augue
25+semper tincidunt pellentesque ipsum volutpat. Duis congue, nunc a aliquam
26+luctus, quam ante convallis nisi, ac pellentesque lacus orci vel turpis. Cum
27+sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
28+mus. Suspendisse hendrerit nisl eu felis sagittis faucibus. Nunc eu congue
29+lorem. Quisque non nibh nisi, et ultrices massa. Sed vitae erat vitae nulla
30+pellentesque fermentum.
31+
32+Ut diam nunc, consectetur ut ultrices eu, iaculis sed felis. Sed lacinia, odio
33+et accumsan luctus, arcu ipsum accumsan erat, sit amet malesuada libero lacus et
34+velit. Donec accumsan tristique tristique. Proin a metus magna, vitae mattis
35+nisl. Integer a libero ipsum. Mauris faucibus eleifend metus id sodales. Morbi
36+ornare, nibh nec facilisis imperdiet, turpis sem commodo lorem, id commodo
37+mauris metus vitae justo. Etiam at pellentesque tortor. Proin mollis accumsan
38+ligula, nec tempus augue auctor quis. Nulla lacinia, mi quis lobortis auctor,
39+nisi diam posuere dui, pulvinar feugiat dui libero eget quam. Fusce eu risus
40+nunc, a consectetur orci. Class aptent taciti sociosqu ad litora torquent per
41+conubia nostra, per inceptos himenaeos. Maecenas venenatis aliquet orci, a
42+ultricies sem facilisis eu. Donec dolor purus, porta condimentum convallis nec,
43+dignissim nec libero.
44+
45+Etiam rutrum ultricies dui, et interdum metus elementum et. Nulla sapien nunc,
46+interdum tristique porttitor in, laoreet vitae mi. Ut vehicula auctor mauris sit
47+amet bibendum. Phasellus adipiscing mattis libero, eget adipiscing erat
48+dignissim at. Vivamus convallis malesuada metus nec cursus. Ut cursus, lorem
49+eleifend sollicitudin condimentum, felis tortor sodales augue, ac tempus lacus
50+ipsum vitae quam. Vestibulum vitae lacus non tortor vehicula iaculis faucibus
51+quis massa.
52+
53+Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
54+mus. Duis malesuada neque nec ante porttitor accumsan. Suspendisse potenti.
55+Aliquam in lacus magna, imperdiet laoreet lectus. Praesent id diam nec ante
56+commodo rhoncus nec vel augue. Pellentesque tortor massa, dignissim ut sagittis
57+sed, hendrerit vitae nunc. Nam gravida, urna vitae hendrerit rutrum, felis augue
58+vulputate tortor, ut varius velit libero nec lectus. In adipiscing massa in est
59+scelerisque ullamcorper. Vivamus in nisi metus.
data/example/posts/2015-10-07-rosa-rosa-rosam.markdownnew file+46
@@ -0,0 +1,46 @@
1+---
2+title: Rosa Rosa Rosam
3+author: Ovidius
4+---
5+
6+Suspendisse pharetra ullamcorper sem et auctor. Suspendisse vitae tellus eu
7+turpis dignissim gravida ut ut tortor. Cum sociis natoque penatibus et magnis
8+dis parturient montes, nascetur ridiculus mus. Morbi aliquam sapien quis nisl
9+sodales non aliquet nisl iaculis. Curabitur fermentum orci vel sapien
10+pellentesque id condimentum metus vehicula. Curabitur turpis purus, scelerisque
11+at interdum quis, placerat sit amet tortor. Aliquam erat volutpat.
12+
13+Integer posuere felis non arcu suscipit ullamcorper. Nam tempus risus venenatis
14+orci sagittis eu aliquam ante tincidunt. Aenean vehicula ipsum id sapien
15+tincidunt commodo. Aliquam erat volutpat. Curabitur vehicula libero ac turpis
16+cursus consectetur. Praesent posuere egestas purus et dapibus. Mauris egestas,
17+lectus vitae scelerisque ultricies, metus lorem tempor nisi, sed vehicula tortor
18+mauris nec urna. Quisque urna tellus, facilisis at mollis eget, adipiscing in
19+nisl. Proin quam arcu, euismod et imperdiet sed, ultricies sed orci.
20+
21+Nulla malesuada sem eget lectus scelerisque nec rhoncus metus interdum. In dui
22+felis, rhoncus id scelerisque eget, vulputate id sem. Nulla facilisi. Vestibulum
23+eleifend, metus dignissim lacinia ornare, magna nulla vehicula nisi, sed
24+molestie mauris ipsum vel turpis. Class aptent taciti sociosqu ad litora
25+torquent per conubia nostra, per inceptos himenaeos. Nulla urna leo, vehicula
26+eget dignissim a, hendrerit ut risus. Fusce ultricies elementum placerat. Nam at
27+dolor sed nisi mollis sollicitudin vitae at urna. Vestibulum iaculis adipiscing
28+eros et mollis.
29+
30+Phasellus ultricies elit eu risus sagittis eu dictum ante ultrices. Nulla
31+congue, augue ac placerat tempor, orci mi luctus nisi, at varius ipsum sem sed
32+eros. Vivamus eget velit eget felis posuere ornare. In sed metus non est iaculis
33+facilisis dapibus sit amet enim. Aliquam viverra tortor eget neque volutpat in
34+auctor urna rutrum. Aliquam ligula augue, congue sit amet rutrum in, semper vel
35+nulla. Sed tempus porttitor faucibus. Donec cursus sodales nulla, quis lacinia
36+mi vehicula vel. Sed nec purus orci. Nam leo sapien, rutrum a ultrices quis,
37+placerat vel ligula. Donec massa quam, pellentesque et molestie nec, hendrerit
38+id mauris. In hac habitasse platea dictumst. Cras quis quam sem. Curabitur in
39+arcu diam, in interdum mauris.
40+
41+Proin lorem sapien, iaculis et faucibus nec, dictum sed nunc. Pellentesque in
42+purus justo. Vestibulum facilisis rutrum nisi, a egestas nunc suscipit sed. Ut
43+quis tortor a arcu bibendum placerat non sed ante. Praesent orci sem, posuere
44+sit amet cursus molestie, volutpat ut purus. Curabitur aliquam, purus in
45+pharetra viverra, lorem leo aliquam tellus, vel consequat felis neque et mauris.
46+Aliquam erat volutpat.
data/example/posts/2015-11-28-carpe-diem.markdownnew file+50
@@ -0,0 +1,50 @@
1+---
2+title: Carpe Diem
3+---
4+
5+Fusce tortor quam, egestas in posuere quis, porttitor vel turpis. Donec
6+vulputate porttitor augue at rhoncus. Proin iaculis consectetur sagittis.
7+Curabitur venenatis turpis sit amet purus tristique nec posuere risus laoreet.
8+Nullam nisi sem, dapibus id semper id, egestas vel arcu. Morbi porttitor ipsum
9+placerat erat consequat sed consequat purus feugiat. Donec auctor elit ut risus
10+mattis facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
11+
12+Proin vulputate sapien facilisis leo ornare pulvinar. Fusce tempus massa a risus
13+semper iaculis. Suspendisse sollicitudin posuere nunc, sit amet rutrum leo
14+facilisis mattis. Sed ornare auctor dui, vitae rutrum neque auctor sit amet.
15+Proin ac dui magna. Mauris vehicula interdum augue, nec ultrices libero egestas
16+quis. Nunc convallis euismod ipsum, id sollicitudin orci consequat ac. Fusce
17+bibendum congue libero, in rutrum nulla congue non. Cras sit amet risus tortor,
18+eu pellentesque dui. Phasellus euismod enim non nibh sodales quis consectetur
19+lorem laoreet. Vivamus a egestas quam. Curabitur in tortor augue, vitae varius
20+tellus. Integer varius, elit ac gravida suscipit, eros erat pellentesque nisi,
21+et tristique augue odio id nulla. Aliquam sit amet nunc vel tellus hendrerit
22+tempus ac vel sem.
23+
24+Aenean tincidunt sollicitudin sapien ut porttitor. Curabitur molestie adipiscing
25+lorem vel scelerisque. Donec vitae interdum est. Proin rutrum vulputate
26+faucibus. Suspendisse sit amet felis odio, non volutpat ante. Sed eu lectus
27+quam. Curabitur tristique rhoncus est, vel commodo tortor suscipit semper.
28+Maecenas feugiat vestibulum nisi id facilisis. Nulla non tincidunt libero.
29+Praesent ultrices interdum commodo. Sed euismod nisl auctor leo ultrices rutrum.
30+Aliquam nibh felis, congue molestie blandit at, bibendum at eros. Aenean
31+tincidunt, tortor iaculis placerat sollicitudin, lorem justo tempor diam, et
32+posuere sapien leo et magna. Quisque vel aliquam mauris.
33+
34+Proin varius tempus fermentum. Cum sociis natoque penatibus et magnis dis
35+parturient montes, nascetur ridiculus mus. Sed tincidunt nunc id magna
36+adipiscing non sollicitudin turpis tempor. Etiam vel elit ipsum, quis euismod
37+velit. Quisque elementum magna vitae quam venenatis lacinia. Sed at arcu ipsum.
38+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos
39+himenaeos. Donec ut lorem ac sapien cursus lacinia sit amet mollis dolor.
40+Vivamus tempus odio nec magna faucibus sed hendrerit lorem tempor.
41+
42+Vestibulum eu nisi arcu. Curabitur nisi risus, fermentum ut lacinia ut, interdum
43+nec magna. Nunc aliquet gravida massa, eu aliquam lorem faucibus at. Sed
44+sollicitudin volutpat velit id tempor. In nibh justo, pharetra et pretium
45+dignissim, tempus in turpis. Phasellus eget lobortis nisl. Phasellus sed
46+fermentum diam. Nam tempus pharetra odio, quis congue eros imperdiet eu. Aliquam
47+dui eros, hendrerit et vulputate vel, porta eu eros. Nullam nisi dui, commodo
48+eget pharetra ut, ornare sit amet nunc. Fusce vel neque urna. Maecenas nulla
49+ante, egestas at consequat quis, fermentum a enim. Aliquam id tristique urna.
50+Integer augue justo, scelerisque et consectetur id, rhoncus eget enim.
data/example/posts/2015-12-07-tu-quoque.markdownnew file+58
@@ -0,0 +1,58 @@
1+---
2+title: Tu Quoque
3+author: Julius
4+---
5+
6+Vestibulum leo turpis, dignissim quis ultrices sit amet, iaculis ac ligula.
7+Pellentesque tristique, velit eget scelerisque scelerisque, est dolor ultrices
8+arcu, quis ullamcorper justo arcu luctus mauris. Integer congue molestie nisi id
9+posuere. Fusce pellentesque gravida tempus. Integer viverra tortor nec eros
10+mollis quis convallis sem laoreet. Nulla id libero ac erat varius laoreet. Proin
11+sed est est. Curabitur lacinia fermentum lorem, elementum malesuada ipsum
12+malesuada ut. Donec suscipit elit id leo vehicula mattis non sed leo. Morbi
13+varius eleifend varius. Nulla vestibulum, neque vitae aliquam eleifend, nisi
14+tellus placerat nunc, quis suscipit elit turpis eu tortor. Etiam euismod
15+convallis lectus quis venenatis. Phasellus laoreet magna in nibh cursus eu
16+egestas nulla convallis. Aliquam vel ullamcorper risus. Fusce dictum, massa id
17+consequat viverra, nulla ante tristique est, a faucibus nisi enim nec dui. Donec
18+metus ligula, condimentum at porttitor eget, lobortis at quam.
19+
20+Aenean vel libero in magna ultricies congue in a odio. Donec faucibus rutrum
21+ornare. Fusce dictum eleifend fermentum. Vestibulum vel nibh a metus porttitor
22+rhoncus. Pellentesque id quam neque, eget molestie arcu. Integer in elit vel
23+neque viverra ultricies in eget massa. Nam ut convallis est. Pellentesque eros
24+eros, sodales non vehicula et, tincidunt ut odio. Cras suscipit ultrices metus
25+sit amet molestie. Fusce enim leo, vehicula sed sodales quis, adipiscing at
26+ipsum.
27+
28+Nunc tempor dignissim enim, sed tincidunt eros bibendum quis. Curabitur et dolor
29+augue, id laoreet mi. Nulla cursus felis id dui vehicula vitae ornare lorem
30+blandit. Cras eget dui nec odio volutpat pharetra. Fusce hendrerit justo justo,
31+vel imperdiet enim. Vivamus elit risus, interdum ultrices accumsan eleifend,
32+vestibulum vitae sapien. Integer bibendum ullamcorper tristique. Nulla quis odio
33+lectus, quis eleifend augue. Integer a ligula mauris. Aenean et tempus tortor.
34+Quisque at tortor mi. Vivamus accumsan feugiat est a blandit. Sed vitae enim ut
35+dolor semper sodales. Duis tristique, ante et placerat elementum, nulla tellus
36+pellentesque sapien, quis posuere velit mi eget nulla. Sed vestibulum nunc non
37+est porttitor ut rutrum nibh semper. Pellentesque habitant morbi tristique
38+senectus et netus et malesuada fames ac turpis egestas.
39+
40+Nulla adipiscing ultricies lobortis. Vivamus iaculis nisl vitae tellus laoreet
41+vitae aliquet lacus mollis. Phasellus ut lacus urna, sed sagittis ante. Etiam
42+consectetur pretium nisl sed dignissim. Pellentesque convallis, nisl eget
43+commodo mollis, sem magna consequat arcu, sed pretium ipsum arcu sit amet neque.
44+Aliquam erat volutpat. Morbi sed mi sed urna vestibulum placerat vitae vel
45+metus. Fusce ac ante at justo pharetra vehicula. Vivamus vel tortor eget augue
46+aliquet aliquet at vel odio. Nunc venenatis, magna quis facilisis fringilla,
47+augue tellus varius neque, in vulputate est eros ut tortor. Duis lorem neque,
48+aliquam congue posuere id, condimentum non dui. Phasellus ut dui massa,
49+porttitor suscipit augue. Praesent quis tellus quam, vel volutpat metus. Vivamus
50+enim est, aliquam in imperdiet et, sagittis eu ligula. Vestibulum hendrerit
51+placerat orci et aliquet. Cras pharetra, dolor placerat lobortis tempor, metus
52+odio cursus ligula, et posuere lacus ligula quis dui.
53+
54+Donec a lectus eu nibh malesuada aliquam. Proin at metus quam, et tincidunt leo.
55+Quisque lacus justo, scelerisque sodales pulvinar sed, dignissim ut sapien.
56+Vivamus diam felis, adipiscing sollicitudin ultricies id, accumsan ac felis. In
57+eu posuere ligula. Suspendisse potenti. Donec porttitor dictum dui id vehicula.
58+Integer ante velit, congue id dictum et, adipiscing a tortor.
data/example/site.hsnew file+67
@@ -0,0 +1,67 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+import Data.Monoid (mappend)
4+import Hakyll
5+
6+
7+--------------------------------------------------------------------------------
8+main :: IO ()
9+main = hakyll $ do
10+ match "images/*" $ do
11+ route idRoute
12+ compile copyFileCompiler
13+
14+ match "css/*" $ do
15+ route idRoute
16+ compile compressCssCompiler
17+
18+ match (fromList ["about.rst", "contact.markdown"]) $ do
19+ route $ setExtension "html"
20+ compile $ pandocCompiler
21+ >>= loadAndApplyTemplate "templates/default.html" defaultContext
22+ >>= relativizeUrls
23+
24+ match "posts/*" $ do
25+ route $ setExtension "html"
26+ compile $ pandocCompiler
27+ >>= loadAndApplyTemplate "templates/post.html" postCtx
28+ >>= loadAndApplyTemplate "templates/default.html" postCtx
29+ >>= relativizeUrls
30+
31+ create ["archive.html"] $ do
32+ route idRoute
33+ compile $ do
34+ posts <- recentFirst =<< loadAll "posts/*"
35+ let archiveCtx =
36+ listField "posts" postCtx (return posts) `mappend`
37+ constField "title" "Archives" `mappend`
38+ defaultContext
39+
40+ makeItem ""
41+ >>= loadAndApplyTemplate "templates/archive.html" archiveCtx
42+ >>= loadAndApplyTemplate "templates/default.html" archiveCtx
43+ >>= relativizeUrls
44+
45+
46+ match "index.html" $ do
47+ route idRoute
48+ compile $ do
49+ posts <- recentFirst =<< loadAll "posts/*"
50+ let indexCtx =
51+ listField "posts" postCtx (return posts) `mappend`
52+ constField "title" "Home" `mappend`
53+ defaultContext
54+
55+ getResourceBody
56+ >>= applyAsTemplate indexCtx
57+ >>= loadAndApplyTemplate "templates/default.html" indexCtx
58+ >>= relativizeUrls
59+
60+ match "templates/*" $ compile templateBodyCompiler
61+
62+
63+--------------------------------------------------------------------------------
64+postCtx :: Context String
65+postCtx =
66+ dateField "date" "%B %e, %Y" `mappend`
67+ defaultContext
data/example/templates/archive.htmlnew file+2
@@ -0,0 +1,2 @@
1+Here you can find all my previous posts:
2+$partial("templates/post-list.html")$
data/example/templates/default.htmlnew file+33
@@ -0,0 +1,33 @@
1+<!doctype html>
2+<html lang="en">
3+ <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">
7+ <title>My Hakyll Blog - $title$</title>
8+ <link rel="stylesheet" href="/css/default.css" />
9+ </head>
10+ <body>
11+ <header>
12+ <div class="logo">
13+ <a href="/">My Hakyll Blog</a>
14+ </div>
15+ <nav>
16+ <a href="/">Home</a>
17+ <a href="/about.html">About</a>
18+ <a href="/contact.html">Contact</a>
19+ <a href="/archive.html">Archive</a>
20+ </nav>
21+ </header>
22+
23+ <main role="main">
24+ <h1>$title$</h1>
25+ $body$
26+ </main>
27+
28+ <footer>
29+ Site proudly generated by
30+ <a href="http://jaspervdj.be/hakyll">Hakyll</a>
31+ </footer>
32+ </body>
33+</html>
data/example/templates/post-list.htmlnew file+7
@@ -0,0 +1,7 @@
1+<ul>
2+ $for(posts)$
3+ <li>
4+ <a href="$url$">$title$</a> - $date$
5+ </li>
6+ $endfor$
7+</ul>
data/example/templates/post.htmlnew file+11
@@ -0,0 +1,11 @@
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>
data/templates/atom-item.xml+2−1
@@ -2,6 +2,7 @@
22 <title>$title$</title>
33 <link href="$root$$url$" />
44 <id>$root$$url$</id>
5− <updated>$timestamp$</updated>
5+ <published>$published$</published>
6+ <updated>$updated$</updated>
67 <summary type="html"><![CDATA[$description$]]></summary>
78</entry>
data/templates/atom.xml+2−1
@@ -6,7 +6,8 @@
66 <id>$root$$url$</id>
77 <author>
88 <name>$authorName$</name>
9+ <email>$authorEmail$</email>
910 </author>
10− <updated>$timestamp$</updated>
11+ <updated>$updated$</updated>
1112 $body$
1213</feed>
data/templates/rss-item.xml+2−1
@@ -2,6 +2,7 @@
22 <title>$title$</title>
33 <link>$root$$url$</link>
44 <description><![CDATA[$description$]]></description>
5− <pubDate>$timestamp$</pubDate>
5+ <pubDate>$published$</pubDate>
66 <guid>$root$$url$</guid>
7+ <dc:creator>$authorName$</dc:creator>
78</item>
data/templates/rss.xml+4−3
@@ -1,12 +1,13 @@
11<?xml version="1.0" encoding="utf-8"?>
2−<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
2+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
3+ xmlns:dc="http://purl.org/dc/elements/1.1/">
34 <channel>
45 <title>$title$</title>
56 <link>$root$</link>
67 <description><![CDATA[$description$]]></description>
78 <atom:link href="$root$$url$" rel="self"
89 type="application/rss+xml" />
9− <lastBuildDate>$timestamp$</lastBuildDate>
10+ <lastBuildDate>$updated$</lastBuildDate>
1011 $body$
11− </channel>
12+ </channel>
1213</rss>
examples/brochure/code.lhsdeleted−21
@@ -1,21 +0,0 @@
1−---
2−title: Code
3−---
4−Etiam condimentum auctor semper. Donec lobortis, magna id sodales sollicitudin,
5−lectus mi egestas nulla, pulvinar lobortis nunc eros id nisl. Curabitur
6−imperdiet, erat at accumsan vulputate.
7−
8−> reverse' [] = []
9−> reverse' (x:xs) = (reverse' xs) ++ [x]
10−
11−Purus nunc blandit nulla, dictum.
12−
13−vestibulum sem lorem eget ipsum. Nunc tincidunt, turpis ut fermentum dapibus,
14−justo tortor bibendum sem, at facilisis justo odio luctus lectus. Quisque nec
15−ligula ipsum. Nunc augue lacus, ullamcorper vel dapibus in, mattis eget elit.
16−
17−> main = print $ reverse' [1 .. 10]
18−
19−Integer ut dui eu felis mollis vestibulum. Etiam at nibh id diam aliquet
20−vestibulum sit amet a nibh. Aliquam erat volutpat. Etiam vitae nulla at dolor
21−fringilla tempor ut a nunc. Pellentesque elementum elit lorem.
examples/brochure/css/default.cssdeleted−17
@@ -1,17 +0,0 @@
1−body {
2− width: 600px;
3− margin: 0px auto 0px auto;
4−}
5−
6−div#navigation {
7− text-align: center;
8− border-bottom: 4px solid black;
9−}
10−
11−div#navigation a {
12− color: white;
13− text-decoration: none;
14− background-color: black;
15− padding: 3px 10px 3px 10px;
16− margin: 0px 10px 0px 10px;
17−}
examples/brochure/css/syntax.cssdeleted−18
@@ -1,18 +0,0 @@
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; }
examples/brochure/hakyll.hsdeleted−20
@@ -1,20 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−import Control.Arrow ((>>>))
3−import Control.Monad (forM_)
4−
5−import Hakyll
6−
7−main :: IO ()
8−main = hakyll $ do
9− match "css/*" $ do
10− route idRoute
11− compile compressCssCompiler
12−
13− match "templates/*" $ compile templateCompiler
14−
15− forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page ->
16− match page $ do
17− route $ setExtension "html"
18− compile $ pageCompiler
19− >>> applyTemplateCompiler "templates/default.html"
20− >>> relativizeUrlsCompiler
examples/brochure/index.markdowndeleted−25
@@ -1,25 +0,0 @@
1−---
2−title: Home
3−---
4−Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempor, urna et
5−auctor tincidunt, eros mauris facilisis purus, eget sollicitudin leo massa sit
6−amet ipsum. Vivamus eu massa in urna vehicula rutrum eget sit amet purus.
7−
8−- Mauris sit amet justo mi.
9−- Curabitur vel quam felis.
10−- In hac habitasse platea dictumst.
11−
12−Etiam nec consequat risus. Donec consequat est vitae neque fermentum
13−feugiat nec ac nibh. Nulla rhoncus, odio quis scelerisque rutrum, metus sem
14−tempor ante, a ornare ipsum felis sed ligula. Morbi urna lectus, scelerisque
15−non pharetra in, rutrum quis ligula. Phasellus semper ullamcorper arcu eu
16−auctor.
17−
18−Aliquam sagittis tincidunt libero ut elementum. Ut sit amet vestibulum metus.
19−Ut aliquet congue neque eu tincidunt. Integer eu elit sed massa sollicitudin
20−vehicula nec ut sem. Cras euismod enim eget purus lacinia non feugiat urna
21−imperdiet. Aliquam justo sem, viverra eu vehicula vitae, imperdiet vel magna.
22−
23−Pellentesque enim dui, interdum elementum vehicula luctus, feugiat vitae arcu.
24−Vestibulum ut felis justo. Quisque vestibulum mauris eget ipsum luctus
25−consequat.
examples/brochure/templates/default.htmldeleted−21
@@ -1,21 +0,0 @@
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">
5− <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7− <title>MyAweSomeCompany - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
9− <link rel="stylesheet" type="text/css" href="/css/syntax.css" />
10− </head>
11− <body>
12− <h1>MyAweSomeCompany - $title$</h1>
13− <div id="navigation">
14− <a href="/">Home</a>
15− <a href="/about.html">About</a>
16− <a href="/code.html">Code</a>
17− </div>
18−
19− $body$
20− </body>
21−</html>
examples/feedblog/css/default.cssdeleted−17
@@ -1,17 +0,0 @@
1−body {
2− width: 600px;
3− margin: 0px auto 0px auto;
4−}
5−
6−div#navigation {
7− text-align: center;
8− border-bottom: 4px solid black;
9−}
10−
11−div#navigation a {
12− color: white;
13− text-decoration: none;
14− background-color: black;
15− padding: 3px 10px 3px 10px;
16− margin: 0px 10px 0px 10px;
17−}
examples/feedblog/hakyll.hsdeleted−68
@@ -1,68 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Main where
3−
4−import Prelude hiding (id)
5−import Control.Category (id)
6−import Control.Arrow ((>>>), (***), arr)
7−import Data.Monoid (mempty, mconcat)
8−
9−import Hakyll
10−
11−main :: IO ()
12−main = hakyll $ do
13− -- Compress CSS
14− match "css/*" $ do
15− route idRoute
16− compile compressCssCompiler
17−
18− -- Render posts
19− match "posts/*" $ do
20− route $ setExtension ".html"
21− compile $ pageCompiler
22− >>> applyTemplateCompiler "templates/post.html"
23− >>> applyTemplateCompiler "templates/default.html"
24− >>> relativizeUrlsCompiler
25−
26− -- Render posts list
27− match "posts.html" $ route idRoute
28− create "posts.html" $ constA mempty
29− >>> arr (setField "title" "All posts")
30− >>> requireAllA "posts/*" addPostList
31− >>> applyTemplateCompiler "templates/posts.html"
32− >>> applyTemplateCompiler "templates/default.html"
33− >>> relativizeUrlsCompiler
34−
35− -- Index
36− match "index.html" $ route idRoute
37− create "index.html" $ constA mempty
38− >>> arr (setField "title" "Home")
39− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
40− >>> applyTemplateCompiler "templates/index.html"
41− >>> applyTemplateCompiler "templates/default.html"
42− >>> relativizeUrlsCompiler
43−
44− -- Render RSS feed
45− match "rss.xml" $ route idRoute
46− create "rss.xml" $
47− requireAll_ "posts/*" >>> renderRss feedConfiguration
48−
49− -- Read templates
50− match "templates/*" $ compile templateCompiler
51−
52−-- | Auxiliary compiler: generate a post list from a list of given posts, and
53−-- add it to the current page under @$posts@
54−--
55−addPostList :: Compiler (Page String, [Page String]) (Page String)
56−addPostList = setFieldA "posts" $
57− arr (reverse . sortByBaseName)
58− >>> require "templates/postitem.html" (\p t -> map (applyTemplate t) p)
59− >>> arr mconcat
60− >>> arr pageBody
61−
62−feedConfiguration :: FeedConfiguration
63−feedConfiguration = FeedConfiguration
64− { feedTitle = "SimpleBlog RSS feed."
65− , feedDescription = "A simple demo of an RSS feed created with Hakyll."
66− , feedAuthorName = "Jasper Van der Jeugt"
67− , feedRoot = "http://example.com"
68− }
examples/feedblog/posts/2009-11-05-a-first-post.markdowndeleted−23
@@ -1,23 +0,0 @@
1−---
2−title: A first post
3−author: Julius Caesar
4−date: November 5, 2009
5−description: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
6−---
7−
8−Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus pretium leo
9−adipiscing lectus iaculis lobortis. Vivamus scelerisque velit dignissim metus
10−gravida sit amet dapibus ligula tempor. Quisque sit amet viverra nunc.
11−Suspendisse cursus elementum ante, ut venenatis nisi dictum eu. Nulla diam
12−ligula, eleifend in varius quis, malesuada a nibh. Vivamus consequat
13−pellentesque erat non blandit. Nunc sit amet eros vel massa semper ullamcorper
14−quis iaculis magna. Vestibulum ullamcorper urna sit amet est elementum
15−pulvinar. Vestibulum consequat lacus ac quam hendrerit tincidunt. Praesent
16−bibendum vehicula diam, nec sagittis risus tempus a. Nulla quis odio sit amet
17−odio vehicula cursus ut id odio. Curabitur semper magna euismod magna mollis
18−venenatis. Nunc eget eleifend velit. Mauris sed posuere sem. Fusce id nunc
19−nisi, a aliquam orci. Suspendisse laoreet justo non enim laoreet eget consequat
20−velit porttitor. Aenean faucibus sodales metus at tincidunt. Donec vestibulum
21−leo pulvinar erat auctor ac ultrices massa euismod. Phasellus blandit cursus
22−magna, eget lacinia mi lobortis sed. Suspendisse ultricies enim ligula, vel
23−scelerisque mauris.
examples/feedblog/posts/2009-11-10-another-post.markdowndeleted−17
@@ -1,17 +0,0 @@
1−---
2−title: Another post
3−author: Marcus Tullius Cicero
4−date: November 10, 2009
5−description: Vestibulum in ultrices urna. Etiam tempor enim dui, nec malesuada elit.
6−---
7−
8−Vestibulum in ultrices urna. Etiam tempor enim dui, nec malesuada elit. Donec
9−tempor ligula et quam volutpat quis fermentum eros congue. Sed ut pulvinar sem.
10−Sed aliquam ipsum id purus sollicitudin vulputate. Cras et mauris dui, vel
11−hendrerit leo. Ut metus ipsum, fermentum ac malesuada id, tempus pharetra quam.
12−Donec diam felis, consequat ac scelerisque cursus, gravida non lectus. Sed
13−faucibus elit dapibus diam elementum id varius nisi tristique. Proin consequat
14−faucibus neque in aliquam. Vestibulum ligula odio, pulvinar vel hendrerit
15−vitae, egestas ut nibh. Praesent ut velit elit, in consequat dolor. Praesent
16−sem enim, commodo in gravida sed, adipiscing vel eros. Lorem ipsum dolor sit
17−amet, consectetur adipiscing elit. Proin non aliquam nunc.
examples/feedblog/posts/2009-11-28-a-third-post.markdowndeleted−21
@@ -1,21 +0,0 @@
1−---
2−title: A third post
3−author: Publius Ovidius Naso
4−date: November 28, 2009
5−description: Pellentesque tempor blandit elit, vel ultricies arcu congue egestas.
6−---
7−
8−Pellentesque tempor blandit elit, vel ultricies arcu congue egestas. Fusce
9−vitae rutrum nisl. Fusce id mauris libero, a venenatis tellus. Fusce iaculis,
10−lorem et ornare molestie, mauris risus mollis nisi, non fermentum lacus lacus
11−sit amet ipsum. Praesent lobortis ullamcorper dolor, eget convallis ligula
12−dignissim a. Suspendisse nulla nisi, congue et pharetra vel, convallis non
13−libero. Ut a nulla ipsum. Phasellus cursus velit id neque viverra ut
14−pellentesque justo posuere. Curabitur laoreet enim et velit tempor consectetur.
15−Donec eu pretium urna. Suspendisse vitae nisi at metus vestibulum aliquam in
16−sit amet nisl. Donec convallis lacinia odio, vestibulum molestie nunc feugiat
17−a. Suspendisse vehicula, sapien id aliquet consectetur, sem sapien ullamcorper
18−arcu, scelerisque porttitor elit ipsum posuere ligula. Nulla at velit eu metus
19−tincidunt auctor ut sit amet enim. Donec placerat dapibus nisi id facilisis.
20−Maecenas pellentesque pulvinar auctor. Curabitur gravida quam sit amet purus
21−consectetur blandit.
examples/feedblog/posts/2009-12-04-this-blog-aint-dead.markdowndeleted−14
@@ -1,14 +0,0 @@
1−---
2−title: This blog ain't dead
3−author: Marcus Antonius
4−date: December 4, 2009
5−description: Etiam non felis aliquet tellus dictum vestibulum.
6−---
7−
8−Etiam non felis aliquet tellus dictum vestibulum. Aliquam accumsan mauris non
9−lacus ultricies nec lacinia enim rhoncus. Curabitur vel tortor massa, elementum
10−tincidunt elit. Maecenas venenatis luctus arcu ut ullamcorper. Donec interdum
11−dolor eu enim tristique vel vehicula risus mollis. Nunc nec tortor quam. Nulla
12−a mauris arcu. Phasellus venenatis tortor vel odio tincidunt consequat. Integer
13−venenatis nibh vitae lectus laoreet eu feugiat nunc pretium. Integer nec turpis
14−metus, in fermentum lorem.
examples/feedblog/posts/2009-12-23-almost-christmas.markdowndeleted−15
@@ -1,15 +0,0 @@
1−---
2−title: Almost Christmas!
3−author: Publius Vergilius Maro
4−date: December 23, 2009
5−description: Morbi tincidunt eleifend ante, eu gravida ante rutrum vel.
6−---
7−
8−Morbi tincidunt eleifend ante, eu gravida ante rutrum vel. Nunc bibendum nulla
9−tellus, eget egestas sapien. Nam rhoncus interdum libero, eget congue orci
10−imperdiet eu. Quisque pellentesque fringilla urna, ac venenatis ante ultricies
11−et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames
12−ac turpis egestas. Mauris eleifend sagittis ultrices. Quisque ultrices accumsan
13−nisl, sed pellentesque metus porta vitae. Nulla facilisi. In et nibh tincidunt
14−mi volutpat pellentesque vitae nec sapien. Integer massa ipsum, pellentesque in
15−elementum at, cursus sit amet diam.
examples/feedblog/templates/default.htmldeleted−23
@@ -1,23 +0,0 @@
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">
5− <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7− <title>SimpleBlog - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
9− <link rel="alternate"
10− type="application/rss+xml"
11− title="SimpleBlog"
12− href="/rss.xml" />
13− </head>
14− <body>
15− <h1>SimpleBlog - $title$</h1>
16− <div id="navigation">
17− <a href="/">Home</a>
18− <a href="/posts.html">All posts</a>
19− </div>
20−
21− $body$
22− </body>
23−</html>
examples/feedblog/templates/index.htmldeleted−14
@@ -1,14 +0,0 @@
1−<div id="posts">
2− <h1>Recent posts</h1>
3− <ul>
4− $posts$
5− </ul>
6− <p><a href="/posts.html">All posts&hellip;</a></p>
7−</div>
8−
9−<div class="about">
10− <h1>About</h1>
11− <p>
12− This is a sample blog for educational purposes.
13− </p>
14−</div>
examples/feedblog/templates/post.htmldeleted−5
@@ -1,5 +0,0 @@
1−<h1>$title$</h1>
2−
3−<p>by <em>$author$</em> on <strong>$date$</strong></p>
4−
5−$body$
examples/feedblog/templates/postitem.htmldeleted−4
@@ -1,4 +0,0 @@
1−<li>
2− <a href="$url$">$title$</a>
3− - <em>$date$</em> - by <em>$author$</em>
4−</li>
examples/feedblog/templates/posts.htmldeleted−4
@@ -1,4 +0,0 @@
1−<h1>All posts</h1>
2−<ul>
3− $posts$
4−</ul>
examples/hakyll/changelog.markdowndeleted−133
@@ -1,133 +0,0 @@
1−---
2−title: Changelog
3−---
4−
5−## Hakyll 3.1
6−
7−- New `match` function in rules DSL
8−- More expressive `Pattern`s
9−
10−## Hakyll 3
11−
12−- Complete rewrite
13−
14−## Hakyll 2.4.1
15−
16−- Add a number of utility functions
17−- Fix bug in `enableIndexUrl` mode
18−
19−## Hakyll 2.4
20−
21−- Arrow based interface to pandoc
22−- Easier custom fields
23−
24−## Hakyll 2.3
25−
26−- Ability to choose between preview modes.
27−- Simple static configuration available.
28−- Support hamlet templates.
29−
30−## Hakyll 2.2.2
31−
32−- Cabal dependency fixes.
33−
34−## Hakyll 2.2.1
35−
36−- Allow custom time locale for `renderDate`.
37−- Render RSS feeds with `CDATA` sections.
38−
39−## Hakyll 2.2
40−
41−- Allow markup languages in templates.
42−
43−## Hakyll 2.1.1
44−
45−- Fix issues in autocompilation/preview mode.
46−
47−## Hakyll 2.1
48−
49−May 21, 2010
50−
51−- Expose pandoc options to HakyllConfiguration.
52−- Allow dashes in pages.
53−- Some typo's and bugs fixed.
54−
55−## Hakyll 2.0
56−
57−March 31, 2010
58−
59−- Rewrite of the API to a clean, Arrow based API.
60−- Added built-in support for RSS and Atom.
61−- Added more documentation.
62−- Added pagination.
63−- Many bugfixes.
64−
65−## Hakyll 1.4
66−
67−February 17, 2010
68−
69−- Added an autocompilation feature.
70−- Support for index URL's (`enableIndexUrl`).
71−
72−## Hakyll 1.3
73−
74−January 30, 2010
75−
76−- Added categories in addition to tags.
77−- Added `createListing` and `createListingWith` function for a more high-level
78− way to create listings.
79−
80−## Hakyll 1.2
81−
82−January 27, 2010
83−
84−- `Data.Binary` is now used for serialization.
85−- Rewrite of the caching system.
86−- Specialized data structure for templates.
87−- Caching of pages and templates.
88−
89−## Hakyll 1.1
90−
91−January 19, 2010
92−
93−- Switched to a custom `Hakyll` monad stack instead of the `IO` monad.
94−- Page sections.
95−- Combining renderables.
96−- `renderAndConcat` can now use multiple templates.
97−
98−## Hakyll 1.0
99−
100−January 14, 2009
101−
102−- First stable release.
103−- Custom templating system.
104−- Added `$root` key for relative URL's.
105−
106−## Hakyll 0.4
107−
108−January 8, 2010
109−
110−- Added examples.
111−- Added `ContextManipulation` type.
112−
113−## Hakyll 0.3
114−
115−December 28, 2009
116−
117−- Added a general `directory` function.
118−- Added CSS compression.
119−- Added tag support.
120−- Added a simple HTTP server for testing purposes.
121−
122−## Hakyll 0.2
123−
124−December 16, 2010
125−
126−- Abstracted `Renderable` type.
127−- Added simple caching and dependency checking.
128−
129−## Hakyll 0.1
130−
131−December 5, 2009
132−
133−- Initial release.
examples/hakyll/css/syntax.cssdeleted−18
@@ -1,18 +0,0 @@
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; }
examples/hakyll/examples/brochure.zipdeleted

Binary file not shown.

examples/hakyll/examples/categoryblog.zipdeleted

Binary file not shown.

examples/hakyll/examples/feedblog.zipdeleted

Binary file not shown.

examples/hakyll/examples/morepages.zipdeleted

Binary file not shown.

examples/hakyll/examples/simpleblog.zipdeleted

Binary file not shown.

examples/hakyll/examples/tagblog.zipdeleted

Binary file not shown.

examples/hakyll/hakyll.hsdeleted−52
@@ -1,52 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−import Hakyll
3−import Control.Monad (forM_)
4−import Control.Arrow ((>>>), arr)
5−import Text.Pandoc
6−
7−main :: IO ()
8−main = hakyll $ do
9− match "css/*" $ do
10− route idRoute
11− compile compressCssCompiler
12−
13− -- Static directories
14− forM_ ["images/*", "examples/*", "reference/*"] $ \f -> match f $ do
15− route idRoute
16− compile copyFileCompiler
17−
18− -- Pages
19− forM_ pages $ \p -> match p $ do
20− route $ setExtension "html"
21− compile $ pageCompiler
22− >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
23− >>> applyTemplateCompiler "templates/default.html"
24− >>> relativizeUrlsCompiler
25−
26− -- Tutorial
27− match "tutorial.markdown" $ do
28− route $ setExtension "html"
29− compile $ readPageCompiler
30− >>> pageRenderPandocWith defaultHakyllParserState withToc
31− >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
32− >>> applyTemplateCompiler "templates/default.html"
33− >>> relativizeUrlsCompiler
34−
35− -- Sidebar
36− match "sidebar.markdown" $ compile pageCompiler
37−
38− -- Templates
39− match "templates/*" $ compile templateCompiler
40− where
41− withToc = defaultHakyllWriterOptions
42− { writerTableOfContents = True
43− , writerTemplate = "<h2>Table of contents</h2>\n$toc$\n$body$"
44− , writerStandalone = True
45− }
46−
47− pages = [ "about.markdown"
48− , "changelog.markdown"
49− , "index.markdown"
50− , "philosophy.markdown"
51− , "reference.markdown"
52− ]
examples/hakyll/index.markdowndeleted−51
@@ -1,51 +0,0 @@
1−---
2−title: Home
3−---
4−
5−## Hakyll 3
6−
7−Hakyll 3 has been released, and it can be installed by running
8−`cabal install hakyll`. For a limited time (but as long as is necessary) you can
9−access the old site and documentation [here](/hakyll2)
10−
11−## Overview
12−
13−Hakyll is a [Haskell](http://haskell.org) library for generating static sites,
14−mostly aimed at small-to-medium sites and personal blogs. It is written in a
15−very configurable way and uses an [xmonad](http://xmonad.org)-like DSL for
16−configuration.
17−
18−## Why static site generators?
19−
20−- Static sites are extremely fast.
21−- You don't have to worry about security issues.
22−
23−## Features of Hakyll
24−
25−- Integration with [pandoc](http://johnmacfarlane.net/pandoc/) gives us markdown
26− and TeX support, including syntax highlighting and other goodies.
27−- An easy templating system.
28−- Hakyll also features a simple http server for previewing your site.
29−
30−## See some examples
31−
32−Some sites written in Hakyll:
33−
34−- Blog of [Demotera](http://blog.demotera.com/), a French engineering company
35−- Personal home page and blog of [Chris Done](http://chrisdone.com/)
36−- Personal home page and blog of [Jasper Van der Jeugt](http://jaspervdj.be/)
37−- [The Haskell Alphabet](http://www.cis.upenn.edu/~byorgey/haskell-alphabet/)
38− by Brent Yorgey
39−- Personal home page and blog of [Nicolas Wu](http://zenzike.com/)
40−- Home page of [Ethan Schoonover](http://ethanschoonover.com/)
41−- [Extralogical](http://extralogical.net/) by Benedict Eastaugh
42−
43−## Getting Started
44−
45−You can get the latest version from hackage using `cabal install hakyll`. Then,
46−you can:
47−
48−- read the [tutorial](/tutorial.html);
49−- mail the [google discussion group](http://groups.google.com/group/hakyll);
50−- ask questions on the IRC channel: `#hakyll` on
51− [freenode](http://freenode.net/).
examples/hakyll/philosophy.markdowndeleted−28
@@ -1,28 +0,0 @@
1−---
2−title: Philosophy
3−---
4−
5−## Small-to-medium sites
6−
7−Hakyll was written to be used for small-to-medium sites. You can do some
8−advanced things with it, but don't use it to build a big online shop.
9−
10−## Hakyll.hs
11−
12−It should be possible to put all configuration in one file, so data and
13−configuration can be strictly separated. In addition, we think this file should
14−never exceed a 100 lines of code.
15−
16−## High-level
17−
18−Hakyll tries to provide as many high-level functions as possible for common
19−tasks, while the lower-level functions should also be accessible. If you think
20−you're writing something that can be used for many sites, please send a patch,
21−or your `hakyll.hs`, and we will see what we can do.
22−
23−## Well-documented
24−
25−A key to being easy-to-use is documentation. That's why we try to provide as
26−many working examples as possible. If you ever create a site using hakyll,
27−please consider open-sourcing it, as people might be able to learn from your
28−code.
examples/hakyll/sidebar.markdowndeleted−8
@@ -1,8 +0,0 @@
1−## Navigation
2−
3−[home](/index.html)
4−[philosophy](/philosophy.html)
5−[about](/about.html)
6−[tutorial](/tutorial.html)
7−[reference](/reference.html)
8−[changelog](/changelog.html)
examples/hakyll/templates/tutorial.htmldeleted−10
@@ -1,10 +0,0 @@
1−$body
2−
3−<h2>Helping out</h2>
4−
5−Hakyll is an open source project, and one of the hardest parts is writing
6−correct, up-to-date, and understandable documentation. Therefore, the
7−authors would really appreciate it if you would
8−<a href="http://groups.google.com/group/hakyll">give some feedback</a> about
9−the tutorials, and especially report errors or difficulties you encountered.
10−Thanks!
examples/hakyll/templates/tutorialitem.htmldeleted−3
@@ -1,3 +0,0 @@
1−<li>
2− <a href="$url">$title</a> $what.
3−</li>
examples/hakyll/templates/tutorials.htmldeleted−14
@@ -1,14 +0,0 @@
1−<h2>Tutorials about Hakyll</h2>
2−<p>
3− Here is a list of tutorials I've written about Hakyll:
4−</p>
5−<ul>
6− $body
7−</ul>
8−<p>
9− All these tutorials assume you are using the latest stable version of
10− Hakyll. If this is not the case, you might want to update using:
11− <pre><code>ghc-pkg unregister hakyll
12−cabal update
13−cabal install hakyll</code></pre>
14−</p>
examples/hakyll/tutorial.markdowndeleted−293
@@ -1,293 +0,0 @@
1−---
2−title: Tutorial
3−---
4−
5−Why static websites?
6−--------------------
7−
8−Modern web frameworks make it easy to create huge dynamic websites. Why would
9−anyone still care about a static website?
10−
11−- Static websites are fast, because it's simply files served directly from the
12− hard disk.
13−- Static websites are secure. Nobody has ever found an SQL injection in static
14− pages.
15−- Static websites are easy to deploy. Just copy them to your webhost using
16− (S)FTP/rsync/scp and you are done. They work on all webhosts: no CGI or extra
17− modules needed for the web server.
18−
19−Why Hakyll?
20−-----------
21−
22−Hakyll is a [Haskell] library meant for creating small-to-medium sized static
23−websites. It is a powerful publishing tool, precisely because of the power of
24−Haskell. By using the awesome [pandoc] library, it is able to create your
25−website from a large variety of input formats.
26−
27−[Haskell]: http://haskell.org/
28−[pandoc]: http://johnmacfarlane.net/pandoc/
29−
30−Features include:
31−
32−- easy templating system;
33−- a simple HTTP server for previewing and compiling your website on the go;
34−- powerful syntax highlighting;
35−- modules for common items such as tags and feeds;
36−- easily extensible.
37−
38−A simple brochure site
39−----------------------
40−
41−### The two layers
42−
43−Hakyll consists of two important layers:
44−
45−- A top-level declarative eDSL, used to describe the relations between the
46− different items,
47−- A lower-level filter-like eDSL built on Arrows.
48−
49−Both layer are used in the configuration file of your website. This
50−configuration file is conventionally called `hakyll.hs` and placed at the root
51−of your website directory.
52−
53−### The Rules DSL
54−
55−The Rules DSL is probably the simpler one. Let's look at a very simple example
56−of a `hakyll.hs`. This piece of code might look a little confusing, but don't
57−worry, we'll walk through it in detail.
58−
59−~~~~~{.haskell}
60−{-# LANGUAGE OverloadedStrings #-}
61−import Control.Arrow ((>>>))
62−import Control.Monad (forM_)
63−
64−import Hakyll
65−
66−main :: IO ()
67−main = hakyll $ do
68− match "css/*" $ do
69− route idRoute
70− compile compressCssCompiler
71−
72− match "images/*" $ do
73− route idRoute
74− compile copyFileCompiler
75−
76− match "templates/*" $ compile templateCompiler
77−
78− forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page ->
79− match page $ do
80− route $ setExtension "html"
81− compile $ pageCompiler
82− >>> applyTemplateCompiler "templates/default.html"
83− >>> relativizeUrlsCompiler
84−~~~~~
85−
86−This is enough code to create a small brochure site! You can find all code
87−and files necessary to build this site [right here](/examples/brochure.zip)
88−-- feel free to play around with it!
89−
90−To create your site, compile and run your `hakyll.hs`:
91−
92− [jasper@phoenix] ghc --make hakyll.hs
93− [jasper@phoenix] ./hakyll preview
94−
95−Alternatively,
96−
97− [jasper@phoenix] runghc hakyll.hs preview
98−
99−Our code begins with a number of imports. Nothing out of the ordinary here, but
100−do note that we use the `OverloadedStrings` extension for conciseness.
101−
102−~~~~~{.haskell}
103−{-# LANGUAGE OverloadedStrings #-}
104−import Control.Arrow ((>>>))
105−import Control.Monad (forM_)
106−
107−import Hakyll
108−~~~~~
109−
110−Our entry point is simply a `main` function with the type `IO ()` -- as in every
111−other Haskell application. However, we directly wrap it in a `hakyll` function,
112−which marks our declarative eDSL. Inside this function, we no longer operate in
113−the `IO` monad, we operate in the pure `RulesM` monad.
114−
115−~~~~~{.haskell}
116−main :: IO ()
117−main = hakyll $ do
118−~~~~~
119−
120−The `RulesM` monad is composed of a few functions. Seldomly, you want to apply a
121−compiler to *all* resources. You want to apply a compiler to certain files
122−instead. That's why the `match` function exists. First, let's handle the CSS of
123−our file. We use a `"css/*"` [Pattern] to match all files in the `css/`
124−directory.
125−
126−Note that a [Pattern] matches an [Identifier], it doesn't match filenames.
127−
128−[Pattern]: /reference/Hakyll-Core-Identifier-Pattern.html
129−[Identifier]: /reference/Hakyll-Core-Identifier.html
130−
131−~~~~~{.haskell}
132−match "css/*" $ do
133−~~~~~
134−
135−`route` creates a new rule for routing items. This rule is applied to all items
136−that are currently matched -- in this case, `"css/*"`. `idRoute` simply means
137−that an item will be routed to it's own filename. For example, `css/screen.css`
138−will be routed to `css/screen.css` -- not very exciting.
139−
140−~~~~~{.haskell}
141−route idRoute
142−~~~~~
143−
144−Apart from specifying where the items should go (using `route`), we also have to
145−specify *how* they need to be compiled. This is done using the `compile`
146−function. It takes a `Compiler` as its second argument. These compilers can
147−consist of very complicated constructions, but Hakyll also provides a number of
148−good default compilers. The `compressCssCompiler` compiler will simply compress
149−the CSS found in the files.
150−
151−~~~~~{.haskell}
152−compile compressCssCompiler
153−~~~~~
154−
155−We can compile our images in a similar way. We use `idRoute` again, but we don't
156−want to change anything -- so we use `copyFileCompiler`. The difference between
157−most other compilers and `copyFileCompiler` is that the lattern will *not*
158−attempt to read the file: it will copy the file directly, so it supports large
159−binary files as well.
160−
161−~~~~~{.haskell}
162−match "images/*" $ do
163− route idRoute
164− compile copyFileCompiler
165−~~~~~
166−
167−Next, we're going to render some pages. We're going to style the results a
168−little, so we're going to need a [Template]. We simply compile a template using
169−the `templateCompiler` compiler, it's good enough in most cases.
170−
171−[Template]: /reference/Hakyll-Web-Template.html
172−
173−We don't use a route for these templates, after all, we don't want to route them
174−anywhere, we just want to use them to style our pages a little.
175−
176−~~~~~{.haskell}
177−match "templates/*" $ compile templateCompiler
178−~~~~~
179−
180−We can conclude that some rules do not *directly* add an output page on our
181−site. In this case, we compile the template so it is available to the compiler
182−later[^1].
183−
184−[^1]: Actually, since the rules DSL is declarative, we could also add the
185− template compile rule at the bottom -- this would make no difference.
186−
187−Now, it's time to actually render our pages. We use the `forM_` monad combinator
188−so we can describe all files at once (instead of compiling all three files
189−manually).
190−
191−~~~~~{.haskell}
192−forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page -> do
193− match page $ do
194−~~~~~
195−
196−The pages all have different extensions. In our website, we only want to see
197−`.html` files. Hakyll provides a route to do just that:
198−
199−~~~~~{.haskell}
200−route setExtension "html"
201−~~~~~
202−
203−The [Rules] reference page has a complete listing of the API used.
204−
205−[Rules]: /reference/Hakyll-Core-Rules.html
206−
207−The compilation of our pages is slightly more complicated: we're using another
208−DSL there.
209−
210−### The Compiler DSL
211−
212−The gist of it is that the `Compiler a b` type has two parameters -- it is an
213−Arrow, and we can chain compilers using the `>>>` operator. The [Compiler]
214−reference page has some more readable information on this subject.
215−
216−[Compiler]: /reference/Hakyll-Core-Compiler.html
217−
218−~~~~~{.haskell}
219−compile pageCompiler
220− >>> applyTemplateCompiler "templates/default.html"
221− >>> relativizeUrlsCompiler
222−~~~~~
223−
224−Note that we can only use `applyTemplateCompiler` with
225−`"templates/default.html"` because we compiled `"templates/default.html"`. If we
226−didn't list a rule for that item, the compilation would fail (Hakyll would not
227−find the template).
228−
229−Now, let's look at the concrete compiler:
230−
231−- `pageCompiler` starts by reading the [Page], parsing it, and rendering it
232− using [pandoc].
233−- `applyTemplateCompiler` applies a [Template] which we have already loaded.
234−- `relativizeUrlsCompiler` will [relativize] the URL's so we have a site we can
235− deploy everywhere.
236−
237−[Page]: /reference/Hakyll-Web-Page.html
238−[relativize]: /reference/Hakyll-Web-RelativizeUrls.html
239−
240−Various tips and tricks
241−-----------------------
242−
243−### Syntax highlighting
244−
245−Syntax highlighting is enabled by default in Hakyll. However, you also need to
246−enable it in pandoc. If no syntax highlighting shows up, try
247−
248− [jasper@phoenix] cabal install --reinstall -fhighlighting pandoc
249−
250−### When to rebuild
251−
252−If you execute a `./hakyll build`, Hakyll will build your site incrementally.
253−This means it will be very fast, but it will not pick up _all_ changes.
254−
255−- In case you edited `hakyll.hs`, you first want to compile it again.
256−- It is generally recommended to do a `./hakyll rebuild` before you deploy your
257− site.
258−
259−After rebuilding your site, all files will look as "modified" to the filesystem.
260−This means that when you upload your site, it will usually transfer all files --
261−this can generate more traffic than necessary, since it is possible that some
262−files were not actually modified. If you use `rsync`, you can counter this using
263−the `--checksum` option.
264−
265−### Using inotify for the preview server
266−
267−Hakyll is able to use [inotify] to power the preview server. This is generally
268−faster and uses less resources than the default. However, [inotify] is only
269−supported on linux systems. You can enable the bindings using:
270−
271− [jasper@phoenix] cabal install -finotify hakyll
272−
273−[inotify]: http://inotify.aiken.cz/
274−
275−Problems
276−--------
277−
278−### regex-pcre dependency on Mac OS X
279−
280−Hakyll requires [regex-pcre], which might fail to build on Mac OS X. To solve
281−this problem, make sure the [pcre] C library is installed (via homebrew or
282−macports). Then install [regex-pcre] using:
283−
284− cabal install --extra-include-dirs=/usr/local/include regex-pcre
285−
286−or
287−
288− cabal install --extra-include-dirs=/opt/local/include regex-pcre
289−
290−...and proceed to install Hakyll the regular way.
291−
292−[regex-pcre]: http://hackage.haskell.org/package/regex-pcre
293−[pcre]: http://www.pcre.org/
examples/morepages/about.markdowndeleted−28
@@ -1,28 +0,0 @@
1−---
2−title: About
3−---
4−
5−## Mattis
6−
7−Nullam imperdiet sodales orci vitae molestie. Nunc quam orci, pharetra a
8−rhoncus vitae, eleifend id felis. Suspendisse potenti. Etiam vitae urna orci.
9−Quisque pellentesque dignissim felis, egestas tempus urna luctus vitae. In hac
10−habitasse platea dictumst. Morbi fringilla mattis odio, et mattis tellus
11−accumsan vitae.
12−
13−## Orci
14−
15−Vivamus eget mauris sit amet nulla laoreet lobortis. Nulla in diam elementum
16−risus convallis commodo. Cras vehicula varius dui vitae facilisis. Proin
17−elementum libero eget leo aliquet quis euismod orci vestibulum. Duis rhoncus
18−lorem consequat tellus vestibulum aliquam. Quisque orci orci, malesuada porta
19−blandit et, interdum nec magna.
20−
21−## Augue
22−
23−In urna ante, pulvinar et imperdiet nec, fermentum ac tortor. Cras tristique
24−pellentesque euismod. Pellentesque est ante, sagittis vitae vehicula vitae,
25−ullamcorper eget lectus. Curabitur egestas accumsan leo, ac ullamcorper nibh
26−tincidunt id. Curabitur lorem libero, fermentum non tincidunt ac, pretium in
27−libero. Donec vel mi eu tortor accumsan dictum ut in augue. Vestibulum est
28−lorem, bibendum eu vehicula eu, convallis eget mauris.
examples/morepages/css/default.cssdeleted−28
@@ -1,28 +0,0 @@
1−body {
2− width: 600px;
3− margin: 0px auto 0px auto;
4−}
5−
6−div#navigation {
7− text-align: center;
8− border-bottom: 4px solid black;
9−}
10−
11−div#navigation a {
12− color: white;
13− text-decoration: none;
14− background-color: black;
15− padding: 3px 10px 3px 10px;
16− margin: 0px 10px 0px 10px;
17−}
18−
19−div.column {
20− width: 30%;
21− float: left;
22− margin: 0px 10px 0px 10px;
23−}
24−
25−div#footer {
26− clear: both;
27− border-top: 4px solid black;
28−}
examples/morepages/footer.markdowndeleted−19
@@ -1,19 +0,0 @@
1−---
2−title: Footer
3−---
4−
5−## Sapien
6−
7−In hac habitasse platea dictumst. Cras placerat felis nec risus varius in
8−accumsan sem fermentum. Vestibulum elementum aliquam tortor semper vulputate.
9−Vivamus tincidunt tellus sed purus tempor fringilla. Morbi dui nisl, eleifend
10−non dictum vitae, luctus eu lacus. Duis vitae lacus sem, ut porta mauris.
11−Aenean sed ultricies dui. Vivamus ullamcorper metus lorem, at ornare nibh.
12−Mauris mi metus, convallis id lobortis vitae, interdum quis felis. Cras elit
13−massa, pellentesque sit amet pharetra ut, volutpat in arcu. Vivamus blandit,
14−ligula et ultricies consequat, metus sem congue quam, ac pretium enim velit at
15−tortor. Cras in tellus eu sapien pulvinar sollicitudin eu id ipsum. Mauris nec
16−urna tellus, et scelerisque tellus. Nunc imperdiet felis nec libero consectetur
17−tristique tristique ipsum sodales. Cras tortor nisl, condimentum in
18−pellentesque id, interdum vel mi. Suspendisse auctor vehicula orci at
19−scelerisque. Vivamus quis sagittis felis.
examples/morepages/hakyll.hsdeleted−29
@@ -1,29 +0,0 @@
1−{-# LANGUAGE OverloadedStrings, Arrows #-}
2−module Main where
3−
4−import Control.Monad (forM_)
5−import Control.Arrow (arr, (>>>))
6−
7−import Hakyll
8−
9−main :: IO ()
10−main = hakyll $ do
11− -- Compress CSS
12− match "css/*" $ do
13− route idRoute
14− compile compressCssCompiler
15−
16− -- Render static pages
17− forM_ ["about.markdown", "index.markdown", "products.markdown"] $ \p ->
18− match p $ do
19− route $ setExtension ".html"
20− compile $ pageCompiler
21− >>> requireA "footer.markdown" (setFieldA "footer" $ arr pageBody)
22− >>> applyTemplateCompiler "templates/default.html"
23− >>> relativizeUrlsCompiler
24−
25− -- Compile footer
26− match "footer.markdown" $ compile pageCompiler
27−
28− -- Read templates
29− match "templates/*" $ compile templateCompiler
examples/morepages/index.markdowndeleted−25
@@ -1,25 +0,0 @@
1−---
2−title: Home
3−---
4−
5−## Purus
6−
7−Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempor, urna et
8−auctor tincidunt, eros mauris facilisis purus, eget sollicitudin leo massa sit
9−amet ipsum. Vivamus eu massa in urna vehicula rutrum eget sit amet purus.
10−
11−## Ligula
12−
13−Mauris sit amet justo mi. Curabitur vel quam felis. In hac habitasse platea
14−dictumst. Etiam nec consequat risus. Donec consequat est vitae neque fermentum
15−feugiat nec ac nibh. Nulla rhoncus, odio quis scelerisque rutrum, metus sem
16−tempor ante, a ornare ipsum felis sed ligula. Morbi urna lectus, scelerisque
17−non pharetra in, rutrum quis ligula. Phasellus semper ullamcorper arcu eu
18−auctor.
19−
20−## Justo
21−
22−Aliquam sagittis tincidunt libero ut elementum. Ut sit amet vestibulum metus.
23−Ut aliquet congue neque eu tincidunt. Integer eu elit sed massa sollicitudin
24−vehicula nec ut sem. Cras euismod enim eget purus lacinia non feugiat urna
25−imperdiet. Aliquam justo sem, viverra eu vehicula vitae, imperdiet vel magna.
examples/morepages/products.markdowndeleted−24
@@ -1,24 +0,0 @@
1−---
2−title: Products
3−---
4−
5−## Lacus
6−
7−Etiam condimentum auctor semper. Donec lobortis, magna id sodales sollicitudin,
8−lectus mi egestas nulla, pulvinar lobortis nunc eros id nisl. Curabitur
9−imperdiet, erat at accumsan vulputate, purus nunc blandit nulla, dictum
10−vestibulum sem lorem eget ipsum.
11−
12−## Vitae
13−
14−Integer ut dui eu felis mollis vestibulum. Etiam at nibh id diam aliquet
15−vestibulum sit amet a nibh. Aliquam erat volutpat. Etiam vitae nulla at dolor
16−fringilla tempor ut a nunc. Pellentesque elementum elit lorem. Quisque nec
17−ligula ipsum. Nunc augue lacus, ullamcorper vel dapibus in, mattis eget elit.
18−
19−## Feugiat
20−
21−Pellentesque enim dui, interdum elementum vehicula luctus, feugiat vitae arcu.
22−Vestibulum ut felis justo. Quisque vestibulum mauris eget ipsum luctus
23−consequat. Nunc tincidunt, turpis ut fermentum dapibus,
24−justo tortor bibendum sem, at facilisis justo odio luctus lectus.
examples/morepages/templates/default.htmldeleted−24
@@ -1,24 +0,0 @@
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">
5− <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7− <title>MyAweSomeCompany - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
9− </head>
10− <body>
11− <h1>MyAweSomeCompany - $title$</h1>
12− <div id="navigation">
13− <a href="/">Home</a>
14− <a href="/about.html">About</a>
15− <a href="/products.html">Products</a>
16− </div>
17−
18− $body$
19−
20− <div id="footer">
21− $footer$
22− </div>
23− </body>
24−</html>
examples/simpleblog/css/default.cssdeleted−17
@@ -1,17 +0,0 @@
1−body {
2− width: 600px;
3− margin: 0px auto 0px auto;
4−}
5−
6−div#navigation {
7− text-align: center;
8− border-bottom: 4px solid black;
9−}
10−
11−div#navigation a {
12− color: white;
13− text-decoration: none;
14− background-color: black;
15− padding: 3px 10px 3px 10px;
16− margin: 0px 10px 0px 10px;
17−}
examples/simpleblog/hackdeleted

Binary file not shown.

examples/simpleblog/hakyll.hsdeleted−55
@@ -1,55 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Main where
3−
4−import Prelude hiding (id)
5−import Control.Category (id)
6−import Control.Arrow ((>>>), (***), arr)
7−import Data.Monoid (mempty, mconcat)
8−
9−import Hakyll
10−
11−main :: IO ()
12−main = hakyll $ do
13− -- Compress CSS
14− match "css/*" $ do
15− route idRoute
16− compile compressCssCompiler
17−
18− -- Render posts
19− match "posts/*" $ do
20− route $ setExtension ".html"
21− compile $ pageCompiler
22− >>> applyTemplateCompiler "templates/post.html"
23− >>> applyTemplateCompiler "templates/default.html"
24− >>> relativizeUrlsCompiler
25−
26− -- Render posts list
27− match "posts.html" $ route idRoute
28− create "posts.html" $ constA mempty
29− >>> arr (setField "title" "All posts")
30− >>> requireAllA "posts/*" addPostList
31− >>> applyTemplateCompiler "templates/posts.html"
32− >>> applyTemplateCompiler "templates/default.html"
33− >>> relativizeUrlsCompiler
34−
35− -- Index
36− match "index.html" $ route idRoute
37− create "index.html" $ constA mempty
38− >>> arr (setField "title" "Home")
39− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
40− >>> applyTemplateCompiler "templates/index.html"
41− >>> applyTemplateCompiler "templates/default.html"
42− >>> relativizeUrlsCompiler
43−
44− -- Read templates
45− match "templates/*" $ compile templateCompiler
46−
47−-- | Auxiliary compiler: generate a post list from a list of given posts, and
48−-- add it to the current page under @$posts@
49−--
50−addPostList :: Compiler (Page String, [Page String]) (Page String)
51−addPostList = setFieldA "posts" $
52− arr (reverse . sortByBaseName)
53− >>> require "templates/postitem.html" (\p t -> map (applyTemplate t) p)
54− >>> arr mconcat
55− >>> arr pageBody
examples/simpleblog/posts/2009-11-05-a-first-post.markdowndeleted−21
@@ -1,21 +0,0 @@
1−---
2−title: A first post
3−author: Julius Caesar
4−date: November 5, 2009
5−---
6−Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus pretium leo
7−adipiscing lectus iaculis lobortis. Vivamus scelerisque velit dignissim metus
8−gravida sit amet dapibus ligula tempor. Quisque sit amet viverra nunc.
9−Suspendisse cursus elementum ante, ut venenatis nisi dictum eu. Nulla diam
10−ligula, eleifend in varius quis, malesuada a nibh. Vivamus consequat
11−pellentesque erat non blandit. Nunc sit amet eros vel massa semper ullamcorper
12−quis iaculis magna. Vestibulum ullamcorper urna sit amet est elementum
13−pulvinar. Vestibulum consequat lacus ac quam hendrerit tincidunt. Praesent
14−bibendum vehicula diam, nec sagittis risus tempus a. Nulla quis odio sit amet
15−odio vehicula cursus ut id odio. Curabitur semper magna euismod magna mollis
16−venenatis. Nunc eget eleifend velit. Mauris sed posuere sem. Fusce id nunc
17−nisi, a aliquam orci. Suspendisse laoreet justo non enim laoreet eget consequat
18−velit porttitor. Aenean faucibus sodales metus at tincidunt. Donec vestibulum
19−leo pulvinar erat auctor ac ultrices massa euismod. Phasellus blandit cursus
20−magna, eget lacinia mi lobortis sed. Suspendisse ultricies enim ligula, vel
21−scelerisque mauris.
examples/simpleblog/posts/2009-11-10-another-post.markdowndeleted−15
@@ -1,15 +0,0 @@
1−---
2−title: Another post
3−author: Marcus Tullius Cicero
4−date: November 10, 2009
5−---
6−Vestibulum in ultrices urna. Etiam tempor enim dui, nec malesuada elit. Donec
7−tempor ligula et quam volutpat quis fermentum eros congue. Sed ut pulvinar sem.
8−Sed aliquam ipsum id purus sollicitudin vulputate. Cras et mauris dui, vel
9−hendrerit leo. Ut metus ipsum, fermentum ac malesuada id, tempus pharetra quam.
10−Donec diam felis, consequat ac scelerisque cursus, gravida non lectus. Sed
11−faucibus elit dapibus diam elementum id varius nisi tristique. Proin consequat
12−faucibus neque in aliquam. Vestibulum ligula odio, pulvinar vel hendrerit
13−vitae, egestas ut nibh. Praesent ut velit elit, in consequat dolor. Praesent
14−sem enim, commodo in gravida sed, adipiscing vel eros. Lorem ipsum dolor sit
15−amet, consectetur adipiscing elit. Proin non aliquam nunc.
examples/simpleblog/posts/2009-11-28-a-third-post.markdowndeleted−19
@@ -1,19 +0,0 @@
1−---
2−title: A third post
3−author: Publius Ovidius Naso
4−date: November 28, 2009
5−---
6−Pellentesque tempor blandit elit, vel ultricies arcu congue egestas. Fusce
7−vitae rutrum nisl. Fusce id mauris libero, a venenatis tellus. Fusce iaculis,
8−lorem et ornare molestie, mauris risus mollis nisi, non fermentum lacus lacus
9−sit amet ipsum. Praesent lobortis ullamcorper dolor, eget convallis ligula
10−dignissim a. Suspendisse nulla nisi, congue et pharetra vel, convallis non
11−libero. Ut a nulla ipsum. Phasellus cursus velit id neque viverra ut
12−pellentesque justo posuere. Curabitur laoreet enim et velit tempor consectetur.
13−Donec eu pretium urna. Suspendisse vitae nisi at metus vestibulum aliquam in
14−sit amet nisl. Donec convallis lacinia odio, vestibulum molestie nunc feugiat
15−a. Suspendisse vehicula, sapien id aliquet consectetur, sem sapien ullamcorper
16−arcu, scelerisque porttitor elit ipsum posuere ligula. Nulla at velit eu metus
17−tincidunt auctor ut sit amet enim. Donec placerat dapibus nisi id facilisis.
18−Maecenas pellentesque pulvinar auctor. Curabitur gravida quam sit amet purus
19−consectetur blandit.
examples/simpleblog/posts/2009-12-04-this-blog-aint-dead.markdowndeleted−12
@@ -1,12 +0,0 @@
1−---
2−title: This blog ain't dead
3−author: Marcus Antonius
4−date: December 4, 2009
5−---
6−Etiam non felis aliquet tellus dictum vestibulum. Aliquam accumsan mauris non
7−lacus ultricies nec lacinia enim rhoncus. Curabitur vel tortor massa, elementum
8−tincidunt elit. Maecenas venenatis luctus arcu ut ullamcorper. Donec interdum
9−dolor eu enim tristique vel vehicula risus mollis. Nunc nec tortor quam. Nulla
10−a mauris arcu. Phasellus venenatis tortor vel odio tincidunt consequat. Integer
11−venenatis nibh vitae lectus laoreet eu feugiat nunc pretium. Integer nec turpis
12−metus, in fermentum lorem.
examples/simpleblog/posts/2009-12-23-almost-christmas.markdowndeleted−13
@@ -1,13 +0,0 @@
1−---
2−title: Almost Christmas!
3−author: Publius Vergilius Maro
4−date: December 23, 2009
5−---
6−Morbi tincidunt eleifend ante, eu gravida ante rutrum vel. Nunc bibendum nulla
7−tellus, eget egestas sapien. Nam rhoncus interdum libero, eget congue orci
8−imperdiet eu. Quisque pellentesque fringilla urna, ac venenatis ante ultricies
9−et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames
10−ac turpis egestas. Mauris eleifend sagittis ultrices. Quisque ultrices accumsan
11−nisl, sed pellentesque metus porta vitae. Nulla facilisi. In et nibh tincidunt
12−mi volutpat pellentesque vitae nec sapien. Integer massa ipsum, pellentesque in
13−elementum at, cursus sit amet diam.
examples/simpleblog/templates/default.htmldeleted−19
@@ -1,19 +0,0 @@
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">
5− <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7− <title>SimpleBlog - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
9− </head>
10− <body>
11− <h1>SimpleBlog - $title$</h1>
12− <div id="navigation">
13− <a href="/index.html">Home</a>
14− <a href="/posts.html">All posts</a>
15− </div>
16−
17− $body$
18− </body>
19−</html>
examples/simpleblog/templates/index.htmldeleted−14
@@ -1,14 +0,0 @@
1−<div id="posts">
2− <h1>Recent posts</h1>
3− <ul>
4− $posts$
5− </ul>
6− <a href="/posts.html">All posts&hellip;</a>
7−</div>
8−
9−<div class="about">
10− <h1>About</h1>
11− <p>
12− This is a sample blog for educational purposes.
13− </p>
14−</div>
examples/simpleblog/templates/post.htmldeleted−5
@@ -1,5 +0,0 @@
1−<h1>$title$</h1>
2−
3−<p>by <em>$author$</em> on <strong>$date$</strong></p>
4−
5−$body$
examples/simpleblog/templates/postitem.htmldeleted−4
@@ -1,4 +0,0 @@
1−<li>
2− <a href="/$url$">$title$</a>
3− - <em>$date$</em> - by <em>$author$</em>
4−</li>
examples/simpleblog/templates/posts.htmldeleted−4
@@ -1,4 +0,0 @@
1−<h1>All posts</h1>
2−<ul>
3− $posts$
4−</ul>
examples/tagblog/css/default.cssdeleted−17
@@ -1,17 +0,0 @@
1−body {
2− width: 600px;
3− margin: 0px auto 0px auto;
4−}
5−
6−div#navigation {
7− text-align: center;
8− border-bottom: 4px solid black;
9−}
10−
11−div#navigation a {
12− color: white;
13− text-decoration: none;
14− background-color: black;
15− padding: 3px 10px 3px 10px;
16− margin: 0px 10px 0px 10px;
17−}
examples/tagblog/hakyll.hsdeleted−99
@@ -1,99 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Main where
3−
4−import Prelude hiding (id)
5−import Control.Arrow ((>>>), (***), arr)
6−import Control.Category (id)
7−import Data.Monoid (mempty, mconcat)
8−
9−import Hakyll
10−
11−main :: IO ()
12−main = hakyll $ do
13− -- Compress CSS
14− match "css/*" $ do
15− route idRoute
16− compile compressCssCompiler
17−
18− -- Render posts
19− match "posts/*" $ do
20− route $ setExtension ".html"
21− compile $ pageCompiler
22− >>> arr (renderDateField "date" "%B %e, %Y" "Date unknown")
23− >>> renderTagsField "prettytags" (fromCapture "tags/*")
24− >>> applyTemplateCompiler "templates/post.html"
25− >>> applyTemplateCompiler "templates/default.html"
26− >>> relativizeUrlsCompiler
27−
28− -- Render posts list
29− match "posts.html" $ route idRoute
30− create "posts.html" $ constA mempty
31− >>> arr (setField "title" "All posts")
32− >>> requireAllA "posts/*" addPostList
33− >>> applyTemplateCompiler "templates/posts.html"
34− >>> applyTemplateCompiler "templates/default.html"
35− >>> relativizeUrlsCompiler
36−
37− -- Index
38− match "index.html" $ route idRoute
39− create "index.html" $ constA mempty
40− >>> arr (setField "title" "Home")
41− >>> requireA "tags" (setFieldA "tagcloud" (renderTagCloud'))
42− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
43− >>> applyTemplateCompiler "templates/index.html"
44− >>> applyTemplateCompiler "templates/default.html"
45− >>> relativizeUrlsCompiler
46−
47− -- Tags
48− create "tags" $
49− requireAll "posts/*" (\_ ps -> readTags ps :: Tags String)
50−
51− -- Add a tag list compiler for every tag
52− match "tags/*" $ route $ setExtension ".html"
53− metaCompile $ require_ "tags"
54− >>> arr tagsMap
55− >>> arr (map (\(t, p) -> (tagIdentifier t, makeTagList t p)))
56−
57− -- Render RSS feed
58− match "rss.xml" $ route idRoute
59− create "rss.xml" $
60− requireAll_ "posts/*"
61− >>> mapCompiler (arr $ copyBodyToField "description")
62− >>> renderRss feedConfiguration
63−
64− -- Read templates
65− match "templates/*" $ compile templateCompiler
66− where
67− renderTagCloud' :: Compiler (Tags String) String
68− renderTagCloud' = renderTagCloud tagIdentifier 100 120
69−
70− tagIdentifier :: String -> Identifier
71− tagIdentifier = fromCapture "tags/*"
72−
73−-- | Auxiliary compiler: generate a post list from a list of given posts, and
74−-- add it to the current page under @$posts@
75−--
76−addPostList :: Compiler (Page String, [Page String]) (Page String)
77−addPostList = setFieldA "posts" $
78− arr (reverse . sortByBaseName)
79− >>> require "templates/postitem.html" (\p t -> map (applyTemplate t) p)
80− >>> arr mconcat
81− >>> arr pageBody
82−
83−makeTagList :: String
84− -> [Page String]
85− -> Compiler () (Page String)
86−makeTagList tag posts =
87− constA (mempty, posts)
88− >>> addPostList
89− >>> arr (setField "title" ("Posts tagged &#8216;" ++ tag ++ "&#8217;"))
90− >>> applyTemplateCompiler "templates/posts.html"
91− >>> applyTemplateCompiler "templates/default.html"
92−
93−feedConfiguration :: FeedConfiguration
94−feedConfiguration = FeedConfiguration
95− { feedTitle = "SimpleBlog RSS feed."
96− , feedDescription = "A simple demo of an RSS feed created with Hakyll."
97− , feedAuthorName = "Jasper Van der Jeugt"
98− , feedRoot = "http://example.com"
99− }
examples/tagblog/posts/2009-11-05-a-first-post.markdowndeleted−22
@@ -1,22 +0,0 @@
1−---
2−title: A first post
3−author: Julius Caesar
4−tags: caesar, random crap
5−---
6−
7−Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus pretium leo
8−adipiscing lectus iaculis lobortis. Vivamus scelerisque velit dignissim metus
9−gravida sit amet dapibus ligula tempor. Quisque sit amet viverra nunc.
10−Suspendisse cursus elementum ante, ut venenatis nisi dictum eu. Nulla diam
11−ligula, eleifend in varius quis, malesuada a nibh. Vivamus consequat
12−pellentesque erat non blandit. Nunc sit amet eros vel massa semper ullamcorper
13−quis iaculis magna. Vestibulum ullamcorper urna sit amet est elementum
14−pulvinar. Vestibulum consequat lacus ac quam hendrerit tincidunt. Praesent
15−bibendum vehicula diam, nec sagittis risus tempus a. Nulla quis odio sit amet
16−odio vehicula cursus ut id odio. Curabitur semper magna euismod magna mollis
17−venenatis. Nunc eget eleifend velit. Mauris sed posuere sem. Fusce id nunc
18−nisi, a aliquam orci. Suspendisse laoreet justo non enim laoreet eget consequat
19−velit porttitor. Aenean faucibus sodales metus at tincidunt. Donec vestibulum
20−leo pulvinar erat auctor ac ultrices massa euismod. Phasellus blandit cursus
21−magna, eget lacinia mi lobortis sed. Suspendisse ultricies enim ligula, vel
22−scelerisque mauris.
examples/tagblog/posts/2009-11-10-another-post.markdowndeleted−16
@@ -1,16 +0,0 @@
1−---
2−title: Another post
3−author: Marcus Tullius Cicero
4−tags: cicero, random crap
5−---
6−
7−Vestibulum in ultrices urna. Etiam tempor enim dui, nec malesuada elit. Donec
8−tempor ligula et quam volutpat quis fermentum eros congue. Sed ut pulvinar sem.
9−Sed aliquam ipsum id purus sollicitudin vulputate. Cras et mauris dui, vel
10−hendrerit leo. Ut metus ipsum, fermentum ac malesuada id, tempus pharetra quam.
11−Donec diam felis, consequat ac scelerisque cursus, gravida non lectus. Sed
12−faucibus elit dapibus diam elementum id varius nisi tristique. Proin consequat
13−faucibus neque in aliquam. Vestibulum ligula odio, pulvinar vel hendrerit
14−vitae, egestas ut nibh. Praesent ut velit elit, in consequat dolor. Praesent
15−sem enim, commodo in gravida sed, adipiscing vel eros. Lorem ipsum dolor sit
16−amet, consectetur adipiscing elit. Proin non aliquam nunc.
examples/tagblog/posts/2009-11-28-a-third-post.markdowndeleted−20
@@ -1,20 +0,0 @@
1−---
2−title: A third post
3−author: Publius Ovidius Naso
4−tags: epic fail, ovidius
5−---
6−
7−Pellentesque tempor blandit elit, vel ultricies arcu congue egestas. Fusce
8−vitae rutrum nisl. Fusce id mauris libero, a venenatis tellus. Fusce iaculis,
9−lorem et ornare molestie, mauris risus mollis nisi, non fermentum lacus lacus
10−sit amet ipsum. Praesent lobortis ullamcorper dolor, eget convallis ligula
11−dignissim a. Suspendisse nulla nisi, congue et pharetra vel, convallis non
12−libero. Ut a nulla ipsum. Phasellus cursus velit id neque viverra ut
13−pellentesque justo posuere. Curabitur laoreet enim et velit tempor consectetur.
14−Donec eu pretium urna. Suspendisse vitae nisi at metus vestibulum aliquam in
15−sit amet nisl. Donec convallis lacinia odio, vestibulum molestie nunc feugiat
16−a. Suspendisse vehicula, sapien id aliquet consectetur, sem sapien ullamcorper
17−arcu, scelerisque porttitor elit ipsum posuere ligula. Nulla at velit eu metus
18−tincidunt auctor ut sit amet enim. Donec placerat dapibus nisi id facilisis.
19−Maecenas pellentesque pulvinar auctor. Curabitur gravida quam sit amet purus
20−consectetur blandit.
examples/tagblog/posts/2009-12-04-this-blog-aint-dead.markdowndeleted−13
@@ -1,13 +0,0 @@
1−---
2−title: This blog ain't dead
3−author: Marcus Antonius
4−tags: epic fail, antonius
5−---
6−
7−Etiam non felis aliquet tellus dictum vestibulum. Aliquam accumsan mauris non
8−lacus ultricies nec lacinia enim rhoncus. Curabitur vel tortor massa, elementum
9−tincidunt elit. Maecenas venenatis luctus arcu ut ullamcorper. Donec interdum
10−dolor eu enim tristique vel vehicula risus mollis. Nunc nec tortor quam. Nulla
11−a mauris arcu. Phasellus venenatis tortor vel odio tincidunt consequat. Integer
12−venenatis nibh vitae lectus laoreet eu feugiat nunc pretium. Integer nec turpis
13−metus, in fermentum lorem.
examples/tagblog/posts/2009-12-23-almost-christmas.markdowndeleted−14
@@ -1,14 +0,0 @@
1−---
2−title: Almost Christmas!
3−author: Publius Vergilius Maro
4−tags: christmas, random crap
5−---
6−
7−Morbi tincidunt eleifend ante, eu gravida ante rutrum vel. Nunc bibendum nulla
8−tellus, eget egestas sapien. Nam rhoncus interdum libero, eget congue orci
9−imperdiet eu. Quisque pellentesque fringilla urna, ac venenatis ante ultricies
10−et. Pellentesque habitant morbi tristique senectus et netus et malesuada fames
11−ac turpis egestas. Mauris eleifend sagittis ultrices. Quisque ultrices accumsan
12−nisl, sed pellentesque metus porta vitae. Nulla facilisi. In et nibh tincidunt
13−mi volutpat pellentesque vitae nec sapien. Integer massa ipsum, pellentesque in
14−elementum at, cursus sit amet diam.
examples/tagblog/templates/default.htmldeleted−23
@@ -1,23 +0,0 @@
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">
5− <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7− <title>SimpleBlog - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
9− <link rel="alternate"
10− type="application/rss+xml"
11− title="SimpleBlog"
12− href="/rss.xml" />
13− </head>
14− <body>
15− <h1>SimpleBlog - $title$</h1>
16− <div id="navigation">
17− <a href="/">Home</a>
18− <a href="/posts.html">All posts</a>
19− </div>
20−
21− $body$
22− </body>
23−</html>
examples/tagblog/templates/index.htmldeleted−19
@@ -1,19 +0,0 @@
1−<div id="posts">
2− <h1>Recent posts</h1>
3− <ul>
4− $posts$
5− </ul>
6− <p><a href="/posts.html">All posts&hellip;</a></p>
7−
8− <h1>Tags</h1>
9− <div>
10− $tagcloud$
11− </div>
12−</div>
13−
14−<div class="about">
15− <h1>About</h1>
16− <p>
17− This is a sample blog for educational purposes.
18− </p>
19−</div>
examples/tagblog/templates/post.htmldeleted−7
@@ -1,7 +0,0 @@
1−<h1>$title$</h1>
2−
3−<p>by <em>$author$</em> on <strong>$date$</strong></p>
4−
5−<p>Tagged as: $prettytags$.</p>
6−
7−$body$
examples/tagblog/templates/postitem.htmldeleted−4
@@ -1,4 +0,0 @@
1−<li>
2− <a href="$url$">$title$</a>
3− - <em>$date$</em> - by <em>$author$</em>
4−</li>
examples/tagblog/templates/posts.htmldeleted−4
@@ -1,4 +0,0 @@
1−<h1>$title$</h1>
2−<ul>
3− $posts$
4−</ul>
hakyll.cabal+327−114
@@ -1,114 +1,327 @@
1−Name: hakyll
2−Version: 3.1.1.0
3−
4−Synopsis: A simple static site generator library.
5−Description: A simple static site generator library, mainly aimed at
6− creating blogs and brochure sites.
7−Author: Jasper Van der Jeugt
8−Maintainer: jaspervdj@gmail.com
9−Homepage: http://jaspervdj.be/hakyll
10−Bug-Reports: http://github.com/jaspervdj/Hakyll/issues
11−License: BSD3
12−License-File: LICENSE
13−Category: Text
14−Cabal-Version: >= 1.6
15−Data-Dir: data
16−Data-Files: templates/atom.xml
17− templates/atom-item.xml
18− templates/rss.xml
19− templates/rss-item.xml
20−extra-source-files: src-inotify/Hakyll/Web/Preview/Poll.hs
21− src-interval/Hakyll/Web/Preview/Poll.hs
22−
23−build-type: Simple
24−
25−source-repository head
26− type: git
27− location: git://github.com/jaspervdj/Hakyll.git
28−
29−flag inotify
30− description: Use the inotify bindings for the preview server. Better, but
31− only works on Linux.
32− default: False
33−
34−library
35− ghc-options: -Wall
36− hs-source-dirs: src
37−
38− if flag(inotify)
39− hs-source-dirs: src-inotify
40− build-depends: hinotify >= 0.3
41− else
42− hs-source-dirs: src-interval
43−
44− build-depends: base >= 4 && < 5,
45− filepath == 1.*,
46− directory == 1.*,
47− containers == 0.*,
48− pandoc == 1.*,
49− regex-base >= 0.93,
50− regex-pcre >= 0.93,
51− mtl >= 1,
52− old-locale == 1.*,
53− old-time == 1.*,
54− time >= 1.1,
55− binary >= 0.5,
56− hamlet >= 0.7,
57− blaze-html >= 0.4,
58− snap-server >= 0.4,
59− snap-core >= 0.4,
60− bytestring >= 0.9,
61− utf8-string >= 0.3,
62− HTTP >= 4000,
63− tagsoup >= 0.12,
64− hopenssl >= 1.4,
65− unix >= 2.4,
66− strict-concurrency >= 0.2
67− exposed-modules: Hakyll
68− Hakyll.Main
69− Hakyll.Web.Blaze
70− Hakyll.Web.Util.Url
71− Hakyll.Web.Preview.Server
72− Hakyll.Web.Preview.Poll
73− Hakyll.Web.CompressCss
74− Hakyll.Web.Template
75− Hakyll.Web.Feed
76− Hakyll.Web.Tags
77− Hakyll.Web.Pandoc
78− Hakyll.Web.Pandoc.FileType
79− Hakyll.Web.Page
80− Hakyll.Web.Template.Read
81− Hakyll.Web.RelativizeUrls
82− Hakyll.Web.Page.Read
83− Hakyll.Web.Page.Metadata
84− Hakyll.Core.Configuration
85− Hakyll.Core.DependencyAnalyzer
86− Hakyll.Core.Identifier.Pattern
87− Hakyll.Core.UnixFilter
88− Hakyll.Core.Util.Arrow
89− Hakyll.Core.Util.File
90− Hakyll.Core.Util.String
91− Hakyll.Core.Resource
92− Hakyll.Core.Resource.Provider
93− Hakyll.Core.Resource.Provider.File
94− Hakyll.Core.CompiledItem
95− Hakyll.Core.Compiler
96− Hakyll.Core.Run
97− Hakyll.Core.Store
98− Hakyll.Core.Writable
99− Hakyll.Core.Writable.CopyFile
100− Hakyll.Core.Writable.WritableTuple
101− Hakyll.Core.Identifier
102− Hakyll.Core.DirectedGraph.Dot
103− Hakyll.Core.DirectedGraph
104− Hakyll.Core.Rules
105− Hakyll.Core.Routes
106− Hakyll.Core.Logger
107− other-modules: Paths_hakyll
108− Hakyll.Web.Template.Read.Hakyll
109− Hakyll.Web.Template.Read.Hamlet
110− Hakyll.Web.Template.Internal
111− Hakyll.Web.Page.Internal
112− Hakyll.Core.Compiler.Internal
113− Hakyll.Core.DirectedGraph.Internal
114− Hakyll.Core.Rules.Internal
1+Name: hakyll
2+Version: 4.13.0.0
3+
4+Synopsis: A static website compiler library
5+Description:
6+ Hakyll is a static website compiler library. It provides you with the tools to
7+ create a simple or advanced static website using a Haskell DSL and formats
8+ such as markdown or RST. You can find more information, including a tutorial,
9+ on the website:
10+
11+ .
12+
13+ * <http://jaspervdj.be/hakyll>
14+
15+ .
16+
17+ If you seek assistance, there's:
18+
19+ .
20+
21+ * A google group: <http://groups.google.com/group/hakyll>
22+
23+ .
24+
25+ * An IRC channel, @#hakyll@ on freenode
26+
27+ .
28+
29+ Additionally, there's the Haddock documentation in the different modules,
30+ meant as a reference.
31+
32+Author: Jasper Van der Jeugt <m@jaspervdj.be>
33+Maintainer: Jasper Van der Jeugt <m@jaspervdj.be>
34+Homepage: http://jaspervdj.be/hakyll
35+Bug-Reports: http://github.com/jaspervdj/Hakyll/issues
36+License: BSD3
37+License-File: LICENSE
38+Category: Web
39+
40+Cabal-Version: >= 1.10
41+Build-Type: Simple
42+Data-Dir: data
43+
44+Data-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
49+ example/site.hs
50+ example/images/haskell-logo.png
51+ example/templates/post-list.html
52+ example/templates/default.html
53+ example/templates/archive.html
54+ example/templates/post.html
55+ example/css/default.css
56+ example/index.html
57+ example/about.rst
58+ example/contact.markdown
59+
60+Extra-source-files:
61+ CHANGELOG.md
62+ tests/data/example.md
63+ tests/data/example.md.metadata
64+ 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
70+ tests/data/posts/2010-08-26-birthday.md
71+ tests/data/posts/2018-09-26.md
72+ tests/data/russian.md
73+ tests/data/strip.html
74+ tests/data/strip.html.out
75+ tests/data/template.html
76+ 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
81+
82+Source-Repository head
83+ Type: git
84+ Location: git://github.com/jaspervdj/hakyll.git
85+
86+Flag previewServer
87+ Description: Include the preview server
88+ Default: True
89+
90+Flag watchServer
91+ Description: Include the watch server
92+ Default: True
93+
94+Flag checkExternal
95+ Description: Include external link checking
96+ Default: True
97+
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+
108+Library
109+ Ghc-Options: -Wall
110+ Hs-Source-Dirs: lib
111+ Default-language: Haskell2010
112+
113+ Exposed-Modules:
114+ Hakyll
115+ Hakyll.Commands
116+ Hakyll.Core.Compiler
117+ Hakyll.Core.Compiler.Internal
118+ Hakyll.Core.Configuration
119+ Hakyll.Core.Dependencies
120+ Hakyll.Core.File
121+ Hakyll.Core.Identifier
122+ Hakyll.Core.Identifier.Pattern
123+ Hakyll.Core.Item
124+ Hakyll.Core.Logger
125+ Hakyll.Core.Metadata
126+ Hakyll.Core.Provider
127+ Hakyll.Core.Provider.Metadata
128+ Hakyll.Core.Routes
129+ Hakyll.Core.Rules
130+ Hakyll.Core.Rules.Internal
131+ Hakyll.Core.Runtime
132+ Hakyll.Core.Store
133+ Hakyll.Core.UnixFilter
134+ Hakyll.Core.Util.File
135+ Hakyll.Core.Util.String
136+ Hakyll.Core.Writable
137+ Hakyll.Main
138+ Hakyll.Web.CompressCss
139+ Hakyll.Web.Feed
140+ Hakyll.Web.Html
141+ Hakyll.Web.Html.RelativizeUrls
142+ Hakyll.Web.Paginate
143+ Hakyll.Web.Redirect
144+ Hakyll.Web.Tags
145+ Hakyll.Web.Template
146+ Hakyll.Web.Template.Context
147+ Hakyll.Web.Template.Internal
148+ Hakyll.Web.Template.Internal.Element
149+ Hakyll.Web.Template.Internal.Trim
150+ Hakyll.Web.Template.List
151+
152+ Other-Modules:
153+ Data.List.Extended
154+ Data.Yaml.Extended
155+ Hakyll.Check
156+ Hakyll.Core.Compiler.Require
157+ Hakyll.Core.Identifier.Pattern.Internal
158+ Hakyll.Core.Item.SomeItem
159+ Hakyll.Core.Provider.Internal
160+ Hakyll.Core.Provider.MetadataCache
161+ Hakyll.Core.Util.Parser
162+ Paths_hakyll
163+
164+ 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
195+
196+ If flag(previewServer)
197+ 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
203+ Cpp-options:
204+ -DPREVIEW_SERVER
205+ Other-modules:
206+ Hakyll.Preview.Poll
207+ Hakyll.Preview.Server
208+
209+ If flag(watchServer)
210+ Build-depends:
211+ fsnotify >= 0.2 && < 0.4
212+ Cpp-options:
213+ -DWATCH_SERVER
214+ Other-modules:
215+ Hakyll.Preview.Poll
216+
217+ If flag(checkExternal)
218+ Build-depends:
219+ http-conduit >= 2.2 && < 2.4,
220+ http-types >= 0.7 && < 0.13
221+ Cpp-options:
222+ -DCHECK_EXTERNAL
223+
224+ If flag(usePandoc)
225+ Exposed-Modules:
226+ Hakyll.Web.Pandoc
227+ Hakyll.Web.Pandoc.Biblio
228+ Hakyll.Web.Pandoc.FileType
229+ Other-Modules:
230+ Hakyll.Web.Pandoc.Binary
231+ Build-Depends:
232+ pandoc >= 2.0.5 && < 2.8,
233+ pandoc-citeproc >= 0.14 && < 0.17
234+ Cpp-options:
235+ -DUSE_PANDOC
236+
237+Test-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+
244+ Other-modules:
245+ Hakyll.Core.Dependencies.Tests
246+ Hakyll.Core.Identifier.Tests
247+ Hakyll.Core.Provider.Metadata.Tests
248+ Hakyll.Core.Provider.Tests
249+ Hakyll.Core.Routes.Tests
250+ Hakyll.Core.Rules.Tests
251+ Hakyll.Core.Runtime.Tests
252+ Hakyll.Core.Store.Tests
253+ Hakyll.Core.UnixFilter.Tests
254+ Hakyll.Core.Util.String.Tests
255+ Hakyll.Web.CompressCss.Tests
256+ Hakyll.Web.Html.RelativizeUrls.Tests
257+ Hakyll.Web.Html.Tests
258+ Hakyll.Web.Tags.Tests
259+ Hakyll.Web.Template.Context.Tests
260+ Hakyll.Web.Template.Tests
261+ TestSuite.Util
262+
263+ 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,
269+ -- 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
277+
278+ If flag(previewServer)
279+ Cpp-options:
280+ -DPREVIEW_SERVER
281+
282+ If flag(watchServer)
283+ Cpp-options:
284+ -DWATCH_SERVER
285+
286+ If flag(checkExternal)
287+ Cpp-options:
288+ -DCHECK_EXTERNAL
289+
290+ If flag(usePandoc)
291+ Other-modules:
292+ Hakyll.Web.Pandoc.FileType.Tests
293+ Cpp-options:
294+ -DUSE_PANDOC
295+
296+Executable 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
304+
305+ Build-depends:
306+ hakyll,
307+ 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
lib/Data/List/Extended.hsnew file+15
@@ -0,0 +1,15 @@
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.hsnew file+24
@@ -0,0 +1,24 @@
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.hsnew file+66
@@ -0,0 +1,66 @@
1+--------------------------------------------------------------------------------
2+-- | Top-level module exporting all modules that are interesting for the user
3+{-# LANGUAGE CPP #-}
4+module Hakyll
5+ ( module Hakyll.Core.Compiler
6+ , module Hakyll.Core.Configuration
7+ , module Hakyll.Core.File
8+ , module Hakyll.Core.Identifier
9+ , module Hakyll.Core.Identifier.Pattern
10+ , module Hakyll.Core.Item
11+ , module Hakyll.Core.Metadata
12+ , module Hakyll.Core.Routes
13+ , module Hakyll.Core.Rules
14+ , module Hakyll.Core.UnixFilter
15+ , module Hakyll.Core.Util.File
16+ , module Hakyll.Core.Util.String
17+ , module Hakyll.Core.Writable
18+ , module Hakyll.Main
19+ , module Hakyll.Web.CompressCss
20+ , module Hakyll.Web.Feed
21+ , module Hakyll.Web.Html
22+ , module Hakyll.Web.Html.RelativizeUrls
23+ , module Hakyll.Web.Paginate
24+#ifdef USE_PANDOC
25+ , module Hakyll.Web.Pandoc
26+ , module Hakyll.Web.Pandoc.Biblio
27+ , module Hakyll.Web.Pandoc.FileType
28+#endif
29+ , module Hakyll.Web.Redirect
30+ , module Hakyll.Web.Tags
31+ , module Hakyll.Web.Template
32+ , module Hakyll.Web.Template.Context
33+ , module Hakyll.Web.Template.List
34+ ) where
35+
36+
37+--------------------------------------------------------------------------------
38+import Hakyll.Core.Compiler
39+import Hakyll.Core.Configuration
40+import Hakyll.Core.File
41+import Hakyll.Core.Identifier
42+import Hakyll.Core.Identifier.Pattern
43+import Hakyll.Core.Item
44+import Hakyll.Core.Metadata
45+import Hakyll.Core.Routes
46+import Hakyll.Core.Rules
47+import Hakyll.Core.UnixFilter
48+import Hakyll.Core.Util.File
49+import Hakyll.Core.Util.String
50+import Hakyll.Core.Writable
51+import Hakyll.Main
52+import Hakyll.Web.CompressCss
53+import Hakyll.Web.Feed
54+import Hakyll.Web.Html
55+import Hakyll.Web.Html.RelativizeUrls
56+import Hakyll.Web.Paginate
57+#ifdef USE_PANDOC
58+import Hakyll.Web.Pandoc
59+import Hakyll.Web.Pandoc.Biblio
60+import Hakyll.Web.Pandoc.FileType
61+#endif
62+import Hakyll.Web.Redirect
63+import Hakyll.Web.Tags
64+import Hakyll.Web.Template
65+import Hakyll.Web.Template.Context
66+import Hakyll.Web.Template.List
lib/Hakyll/Check.hsnew file+302
@@ -0,0 +1,302 @@
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/Commands.hsnew file+164
@@ -0,0 +1,164 @@
1+ --------------------------------------------------------------------------------
2+-- | Implementation of Hakyll commands: build, preview...
3+{-# LANGUAGE CPP #-}
4+module Hakyll.Commands
5+ ( Check(..)
6+ , build
7+ , check
8+ , clean
9+ , preview
10+ , rebuild
11+ , server
12+ , deploy
13+ , watch
14+ ) where
15+
16+
17+--------------------------------------------------------------------------------
18+import Control.Concurrent
19+import System.Exit (ExitCode)
20+
21+
22+--------------------------------------------------------------------------------
23+import Hakyll.Check (Check(..))
24+import qualified Hakyll.Check as Check
25+import Hakyll.Core.Configuration
26+import Hakyll.Core.Logger (Logger)
27+import qualified Hakyll.Core.Logger as Logger
28+import Hakyll.Core.Rules
29+import Hakyll.Core.Rules.Internal
30+import Hakyll.Core.Runtime
31+import Hakyll.Core.Util.File
32+
33+
34+--------------------------------------------------------------------------------
35+#ifdef WATCH_SERVER
36+import Hakyll.Preview.Poll (watchUpdates)
37+#endif
38+
39+#ifdef PREVIEW_SERVER
40+import Hakyll.Preview.Server
41+#endif
42+
43+#ifdef mingw32_HOST_OS
44+import Control.Monad (void)
45+import System.IO.Error (catchIOError)
46+#endif
47+
48+
49+--------------------------------------------------------------------------------
50+-- | Build the site
51+build :: Configuration -> Logger -> Rules a -> IO ExitCode
52+build conf logger rules = fst <$> run conf logger rules
53+
54+
55+--------------------------------------------------------------------------------
56+-- | Run the checker and exit
57+check :: Configuration -> Logger -> Check.Check -> IO ExitCode
58+check = Check.check
59+
60+
61+--------------------------------------------------------------------------------
62+-- | Remove the output directories
63+clean :: Configuration -> Logger -> IO ()
64+clean conf logger = do
65+ remove $ destinationDirectory conf
66+ remove $ storeDirectory conf
67+ remove $ tmpDirectory conf
68+ where
69+ remove dir = do
70+ Logger.header logger $ "Removing " ++ dir ++ "..."
71+ removeDirectory dir
72+
73+
74+--------------------------------------------------------------------------------
75+-- | Preview the site
76+preview :: Configuration -> Logger -> Rules a -> Int -> IO ()
77+#ifdef PREVIEW_SERVER
78+preview conf logger rules port = do
79+ deprecatedMessage
80+ watch conf logger "0.0.0.0" port True rules
81+ where
82+ deprecatedMessage = mapM_ putStrLn [ "The preview command has been deprecated."
83+ , "Use the watch command for recompilation and serving."
84+ ]
85+#else
86+preview _ _ _ _ = previewServerDisabled
87+#endif
88+
89+
90+--------------------------------------------------------------------------------
91+-- | Watch and recompile for changes
92+
93+watch :: Configuration -> Logger -> String -> Int -> Bool -> Rules a -> IO ()
94+#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'
106+ where
107+ update = do
108+ (_, ruleSet) <- run conf logger rules
109+ return $ rulesPattern ruleSet
110+ loop = threadDelay 100000 >> loop
111+ server' = if runServer then server conf logger host port else loop
112+#else
113+watch _ _ _ _ _ _ = watchServerDisabled
114+#endif
115+
116+--------------------------------------------------------------------------------
117+-- | Rebuild the site
118+rebuild :: Configuration -> Logger -> Rules a -> IO ExitCode
119+rebuild conf logger rules =
120+ clean conf logger >> build conf logger rules
121+
122+--------------------------------------------------------------------------------
123+-- | Start a server
124+server :: Configuration -> Logger -> String -> Int -> IO ()
125+#ifdef PREVIEW_SERVER
126+server conf logger host port = do
127+ let destination = destinationDirectory conf
128+ staticServer logger destination host port
129+#else
130+server _ _ _ _ = previewServerDisabled
131+#endif
132+
133+
134+--------------------------------------------------------------------------------
135+-- | Upload the site
136+deploy :: Configuration -> IO ExitCode
137+deploy conf = deploySite conf conf
138+
139+
140+--------------------------------------------------------------------------------
141+-- | Print a warning message about the preview serving not being enabled
142+#ifndef PREVIEW_SERVER
143+previewServerDisabled :: IO ()
144+previewServerDisabled =
145+ mapM_ putStrLn
146+ [ "PREVIEW SERVER"
147+ , ""
148+ , "The preview server is not enabled in the version of Hakyll. To"
149+ , "enable it, set the flag to True and recompile Hakyll."
150+ , "Alternatively, use an external tool to serve your site directory."
151+ ]
152+#endif
153+
154+#ifndef WATCH_SERVER
155+watchServerDisabled :: IO ()
156+watchServerDisabled =
157+ mapM_ putStrLn
158+ [ "WATCH SERVER"
159+ , ""
160+ , "The watch server is not enabled in the version of Hakyll. To"
161+ , "enable it, set the flag to True and recompile Hakyll."
162+ , "Alternatively, use an external tool to serve your site directory."
163+ ]
164+#endif
lib/Hakyll/Core/Compiler.hsnew file+222
@@ -0,0 +1,222 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+module Hakyll.Core.Compiler
5+ ( Compiler
6+ , getUnderlying
7+ , getUnderlyingExtension
8+ , makeItem
9+ , getRoute
10+ , getResourceBody
11+ , getResourceString
12+ , getResourceLBS
13+ , getResourceFilePath
14+
15+ , Internal.Snapshot
16+ , saveSnapshot
17+ , Internal.load
18+ , Internal.loadSnapshot
19+ , Internal.loadBody
20+ , Internal.loadSnapshotBody
21+ , Internal.loadAll
22+ , Internal.loadAllSnapshots
23+
24+ , cached
25+ , unsafeCompiler
26+ , debugCompiler
27+ , noResult
28+ , withErrorMessage
29+ ) where
30+
31+
32+--------------------------------------------------------------------------------
33+import Control.Monad (unless, when, (>=>))
34+import Data.Binary (Binary)
35+import Data.ByteString.Lazy (ByteString)
36+import qualified Data.List.NonEmpty as NonEmpty
37+import Data.Typeable (Typeable)
38+import System.Environment (getProgName)
39+import System.FilePath (takeExtension)
40+
41+
42+--------------------------------------------------------------------------------
43+import Hakyll.Core.Compiler.Internal
44+import qualified Hakyll.Core.Compiler.Require as Internal
45+import Hakyll.Core.Dependencies
46+import Hakyll.Core.Identifier
47+import Hakyll.Core.Item
48+import Hakyll.Core.Logger as Logger
49+import Hakyll.Core.Provider
50+import Hakyll.Core.Routes
51+import qualified Hakyll.Core.Store as Store
52+
53+
54+--------------------------------------------------------------------------------
55+-- | Get the underlying identifier.
56+getUnderlying :: Compiler Identifier
57+getUnderlying = compilerUnderlying <$> compilerAsk
58+
59+
60+--------------------------------------------------------------------------------
61+-- | Get the extension of the underlying identifier. Returns something like
62+-- @".html"@
63+getUnderlyingExtension :: Compiler String
64+getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
65+
66+
67+--------------------------------------------------------------------------------
68+-- | Create an item from the underlying identifier and a given value.
69+makeItem :: a -> Compiler (Item a)
70+makeItem x = do
71+ identifier <- getUnderlying
72+ return $ Item identifier x
73+
74+
75+--------------------------------------------------------------------------------
76+-- | Get the route for a specified item
77+getRoute :: Identifier -> Compiler (Maybe FilePath)
78+getRoute identifier = do
79+ provider <- compilerProvider <$> compilerAsk
80+ routes <- compilerRoutes <$> compilerAsk
81+ -- Note that this makes us dependend on that identifier: when the metadata
82+ -- of that item changes, the route may change, hence we have to recompile
83+ (mfp, um) <- compilerUnsafeIO $ runRoutes routes provider identifier
84+ when um $ compilerTellDependencies [IdentifierDependency identifier]
85+ return mfp
86+
87+
88+--------------------------------------------------------------------------------
89+-- | Get the full contents of the matched source file as a string,
90+-- but without metadata preamble, if there was one.
91+getResourceBody :: Compiler (Item String)
92+getResourceBody = getResourceWith resourceBody
93+
94+
95+--------------------------------------------------------------------------------
96+-- | Get the full contents of the matched source file as a string.
97+getResourceString :: Compiler (Item String)
98+getResourceString = getResourceWith resourceString
99+
100+
101+--------------------------------------------------------------------------------
102+-- | Get the full contents of the matched source file as a lazy bytestring.
103+getResourceLBS :: Compiler (Item ByteString)
104+getResourceLBS = getResourceWith resourceLBS
105+
106+
107+--------------------------------------------------------------------------------
108+-- | Get the file path of the resource we are compiling
109+getResourceFilePath :: Compiler FilePath
110+getResourceFilePath = do
111+ provider <- compilerProvider <$> compilerAsk
112+ id' <- compilerUnderlying <$> compilerAsk
113+ return $ resourceFilePath provider id'
114+
115+
116+--------------------------------------------------------------------------------
117+-- | Overloadable function for 'getResourceString' and 'getResourceLBS'
118+getResourceWith :: (Provider -> Identifier -> IO a) -> Compiler (Item a)
119+getResourceWith reader = do
120+ provider <- compilerProvider <$> compilerAsk
121+ id' <- compilerUnderlying <$> compilerAsk
122+ let filePath = toFilePath id'
123+ if resourceExists provider id'
124+ then compilerUnsafeIO $ Item id' <$> reader provider id'
125+ else fail $ error' filePath
126+ where
127+ error' fp = "Hakyll.Core.Compiler.getResourceWith: resource " ++
128+ show fp ++ " not found"
129+
130+
131+--------------------------------------------------------------------------------
132+-- | Save a snapshot of the item. This function returns the same item, which
133+-- convenient for building '>>=' chains.
134+saveSnapshot :: (Binary a, Typeable a)
135+ => Internal.Snapshot -> Item a -> Compiler (Item a)
136+saveSnapshot snapshot item = do
137+ store <- compilerStore <$> compilerAsk
138+ logger <- compilerLogger <$> compilerAsk
139+ compilerUnsafeIO $ do
140+ Logger.debug logger $ "Storing snapshot: " ++ snapshot
141+ Internal.saveSnapshot store snapshot item
142+
143+ -- Signal that we saved the snapshot.
144+ Compiler $ \_ -> return $ CompilerSnapshot snapshot (return item)
145+
146+
147+--------------------------------------------------------------------------------
148+-- | Turn on caching for a compilation value to avoid recomputing it
149+-- on subsequent Hakyll runs.
150+-- The storage key consists of the underlying identifier of the compiled
151+-- ressource and the given name.
152+cached :: (Binary a, Typeable a)
153+ => String
154+ -> Compiler a
155+ -> Compiler a
156+cached name compiler = do
157+ id' <- compilerUnderlying <$> compilerAsk
158+ store <- compilerStore <$> compilerAsk
159+ 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+
164+ let modified = resourceModified provider id'
165+ if modified
166+ then do
167+ x <- compiler
168+ compilerUnsafeIO $ Store.set store [name, show id'] x
169+ return x
170+ else do
171+ compilerTellCacheHits 1
172+ x <- compilerUnsafeIO $ Store.get store [name, show id']
173+ progName <- compilerUnsafeIO getProgName
174+ case x of Store.Found x' -> return x'
175+ _ -> fail $ error' progName
176+ where
177+ error' progName =
178+ "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
179+ "Try running: " ++ progName ++ " clean"
180+
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+
186+
187+--------------------------------------------------------------------------------
188+-- | Run an IO computation without dependencies in a Compiler
189+unsafeCompiler :: IO a -> Compiler a
190+unsafeCompiler = compilerUnsafeIO
191+
192+
193+--------------------------------------------------------------------------------
194+-- | Fail so that it is treated as non-defined in an @\$if()\$@ branching
195+-- "Hakyll.Web.Template" macro, and alternative
196+-- 'Hakyll.Web.Template.Context.Context's are tried
197+--
198+-- @since 4.13.0
199+noResult :: String -> Compiler a
200+noResult = compilerNoResult . return
201+
202+
203+--------------------------------------------------------------------------------
204+-- | Prepend an error line to the error, if there is one. This allows you to
205+-- add helpful context to error messages.
206+--
207+-- @since 4.13.0
208+withErrorMessage :: String -> Compiler a -> Compiler a
209+withErrorMessage x = do
210+ compilerTry >=> either (compilerResult . CompilerError . prepend) return
211+ where
212+ prepend (CompilationFailure es) = CompilationFailure (x `NonEmpty.cons` es)
213+ prepend (CompilationNoResult es) = CompilationNoResult (x : es)
214+
215+
216+--------------------------------------------------------------------------------
217+-- | Compiler for debugging purposes.
218+-- Passes a message to the debug logger that is printed in verbose mode.
219+debugCompiler :: String -> Compiler ()
220+debugCompiler msg = do
221+ logger <- compilerLogger <$> compilerAsk
222+ compilerUnsafeIO $ Logger.debug logger msg
lib/Hakyll/Core/Compiler/Internal.hsnew file+364
@@ -0,0 +1,364 @@
1+--------------------------------------------------------------------------------
2+-- | Internally used compiler module
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GADTs #-}
7+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
9+module Hakyll.Core.Compiler.Internal
10+ ( -- * Types
11+ Snapshot
12+ , CompilerRead (..)
13+ , CompilerWrite (..)
14+ , CompilerErrors (..)
15+ , CompilerResult (..)
16+ , Compiler (..)
17+ , runCompiler
18+
19+ -- * Core operations
20+ , compilerResult
21+ , compilerTell
22+ , compilerAsk
23+ , compilerUnsafeIO
24+
25+ -- * Error operations
26+ , compilerThrow
27+ , compilerNoResult
28+ , compilerCatch
29+ , compilerTry
30+ , compilerErrorMessages
31+
32+ -- * Utilities
33+ , compilerDebugEntries
34+ , compilerTellDependencies
35+ , compilerTellCacheHits
36+ ) where
37+
38+
39+--------------------------------------------------------------------------------
40+import Control.Applicative (Alternative (..))
41+import Control.Exception (SomeException, handle)
42+import 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
49+import Data.Set (Set)
50+import qualified Data.Set as S
51+
52+
53+--------------------------------------------------------------------------------
54+import Hakyll.Core.Configuration
55+import Hakyll.Core.Dependencies
56+import Hakyll.Core.Identifier
57+import Hakyll.Core.Identifier.Pattern
58+import qualified Hakyll.Core.Logger as Logger
59+import Hakyll.Core.Metadata
60+import Hakyll.Core.Provider
61+import Hakyll.Core.Routes
62+import Hakyll.Core.Store
63+
64+
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+
71+--------------------------------------------------------------------------------
72+-- | Environment in which a compiler runs
73+data CompilerRead = CompilerRead
74+ { -- | Main configuration
75+ compilerConfig :: Configuration
76+ , -- | Underlying identifier
77+ compilerUnderlying :: Identifier
78+ , -- | Resource provider
79+ compilerProvider :: Provider
80+ , -- | List of all known identifiers
81+ compilerUniverse :: Set Identifier
82+ , -- | Site routes
83+ compilerRoutes :: Routes
84+ , -- | Compiler store
85+ compilerStore :: Store
86+ , -- | Logger
87+ compilerLogger :: Logger.Logger
88+ }
89+
90+
91+--------------------------------------------------------------------------------
92+data CompilerWrite = CompilerWrite
93+ { compilerDependencies :: [Dependency]
94+ , compilerCacheHits :: Int
95+ } deriving (Show)
96+
97+
98+--------------------------------------------------------------------------------
99+#if MIN_VERSION_base(4,9,0)
100+instance Semigroup CompilerWrite where
101+ (<>) (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
102+ CompilerWrite (d1 ++ d2) (h1 + h2)
103+
104+instance Monoid CompilerWrite where
105+ mempty = CompilerWrite [] 0
106+ mappend = (<>)
107+#else
108+instance Monoid CompilerWrite where
109+ mempty = CompilerWrite [] 0
110+ mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
111+ 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
130+
131+
132+--------------------------------------------------------------------------------
133+-- | An intermediate result of a compilation step
134+data CompilerResult a
135+ = CompilerDone a CompilerWrite
136+ | CompilerSnapshot Snapshot (Compiler a)
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
139+
140+
141+--------------------------------------------------------------------------------
142+-- | A monad which lets you compile items and takes care of dependency tracking
143+-- for you.
144+newtype Compiler a = Compiler
145+ { unCompiler :: CompilerRead -> IO (CompilerResult a)
146+ }
147+
148+
149+--------------------------------------------------------------------------------
150+instance Functor Compiler where
151+ fmap f (Compiler c) = Compiler $ \r -> do
152+ res <- c r
153+ 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
158+ {-# INLINE fmap #-}
159+
160+
161+--------------------------------------------------------------------------------
162+instance Monad Compiler where
163+ return x = compilerResult $ CompilerDone x mempty
164+ {-# INLINE return #-}
165+
166+ Compiler c >>= f = Compiler $ \r -> do
167+ res <- c r
168+ case res of
169+ CompilerDone x w -> do
170+ res' <- unCompiler (f x) r
171+ return $ case res' of
172+ CompilerDone y w' -> CompilerDone y (w `mappend` w')
173+ CompilerSnapshot s c' -> CompilerSnapshot s $ do
174+ compilerTell w -- Save dependencies!
175+ c'
176+ CompilerRequire i c' -> CompilerRequire i $ do
177+ compilerTell w -- Save dependencies!
178+ c'
179+ CompilerError e -> CompilerError e
180+
181+ CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
182+ CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
184+ {-# INLINE (>>=) #-}
185+
186+ fail = compilerThrow . return
187+ {-# INLINE fail #-}
188+
189+
190+--------------------------------------------------------------------------------
191+instance Applicative Compiler where
192+ pure x = return x
193+ {-# INLINE pure #-}
194+
195+ f <*> x = f >>= \f' -> fmap f' x
196+ {-# INLINE (<*>) #-}
197+
198+
199+--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
201+instance MonadMetadata Compiler where
202+ getMetadata = compilerGetMetadata
203+ getMatches = compilerGetMatches
204+
205+
206+--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
209+instance MonadError [String] Compiler where
210+ throwError = compilerThrow
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
212+
213+
214+--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
216+runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
217+runCompiler compiler read' = handle handler $ unCompiler compiler read'
218+ where
219+ handler :: SomeException -> IO (CompilerResult a)
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
221+
222+
223+--------------------------------------------------------------------------------
224+-- | Trying alternative compilers if the first fails, regardless whether through
225+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226+-- Aggregates error messages if all fail.
227+instance 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"
241+ {-# INLINE (<|>) #-}
242+
243+
244+--------------------------------------------------------------------------------
245+-- | Put the result back in a compiler
246+compilerResult :: CompilerResult a -> Compiler a
247+compilerResult x = Compiler $ \_ -> return x
248+{-# INLINE compilerResult #-}
249+
250+
251+--------------------------------------------------------------------------------
252+-- | Get the current environment
253+compilerAsk :: Compiler CompilerRead
254+compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
255+{-# INLINE compilerAsk #-}
256+
257+
258+--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
260+compilerTell :: CompilerWrite -> Compiler ()
261+compilerTell = compilerResult . CompilerDone ()
262+{-# INLINE compilerTell #-}
263+
264+
265+--------------------------------------------------------------------------------
266+-- | Run an IO computation without dependencies in a Compiler
267+compilerUnsafeIO :: IO a -> Compiler a
268+compilerUnsafeIO io = Compiler $ \_ -> do
269+ x <- io
270+ return $ CompilerDone x mempty
271+{-# INLINE compilerUnsafeIO #-}
272+
273+
274+--------------------------------------------------------------------------------
275+-- | Throw errors in the 'Compiler'.
276+--
277+-- If no messages are given, this is considered a 'CompilationNoResult' error.
278+-- Otherwise, it is treated as a proper compilation failure.
279+compilerThrow :: [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 #-}
302+
303+
304+--------------------------------------------------------------------------------
305+-- | Allows you to recover from 'CompilerError's.
306+-- Uses the same parameter order as 'catchError' so that it can be used infix.
307+--
308+-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
310+compilerCatch (Compiler x) f = Compiler $ \r -> do
311+ res <- x r
312+ 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
317+{-# INLINE compilerCatch #-}
318+
319+
320+--------------------------------------------------------------------------------
321+compilerDebugLog :: [String] -> Compiler ()
322+compilerDebugLog ms = do
323+ logger <- compilerLogger <$> compilerAsk
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
325+
326+--------------------------------------------------------------------------------
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
332+
333+
334+--------------------------------------------------------------------------------
335+compilerTellDependencies :: [Dependency] -> Compiler ()
336+compilerTellDependencies ds = do
337+ compilerDebugLog $ map (\d ->
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
339+ compilerTell mempty {compilerDependencies = ds}
340+{-# INLINE compilerTellDependencies #-}
341+
342+
343+--------------------------------------------------------------------------------
344+compilerTellCacheHits :: Int -> Compiler ()
345+compilerTellCacheHits ch = compilerTell mempty {compilerCacheHits = ch}
346+{-# INLINE compilerTellCacheHits #-}
347+
348+
349+--------------------------------------------------------------------------------
350+compilerGetMetadata :: Identifier -> Compiler Metadata
351+compilerGetMetadata identifier = do
352+ provider <- compilerProvider <$> compilerAsk
353+ compilerTellDependencies [IdentifierDependency identifier]
354+ compilerUnsafeIO $ resourceMetadata provider identifier
355+
356+
357+--------------------------------------------------------------------------------
358+compilerGetMatches :: Pattern -> Compiler [Identifier]
359+compilerGetMatches pattern = do
360+ universe <- compilerUniverse <$> compilerAsk
361+ let matching = filterMatches pattern $ S.toList universe
362+ set' = S.fromList matching
363+ compilerTellDependencies [PatternDependency pattern set']
364+ return matching
lib/Hakyll/Core/Compiler/Require.hsnew file+123
@@ -0,0 +1,123 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Compiler.Require
3+ ( Snapshot
4+ , save
5+ , saveSnapshot
6+ , load
7+ , loadSnapshot
8+ , loadBody
9+ , loadSnapshotBody
10+ , loadAll
11+ , loadAllSnapshots
12+ ) where
13+
14+
15+--------------------------------------------------------------------------------
16+import Control.Monad (when)
17+import Data.Binary (Binary)
18+import qualified Data.Set as S
19+import Data.Typeable
20+
21+
22+--------------------------------------------------------------------------------
23+import Hakyll.Core.Compiler.Internal
24+import Hakyll.Core.Dependencies
25+import Hakyll.Core.Identifier
26+import Hakyll.Core.Identifier.Pattern
27+import Hakyll.Core.Item
28+import Hakyll.Core.Metadata
29+import Hakyll.Core.Store (Store)
30+import qualified Hakyll.Core.Store as Store
31+
32+
33+--------------------------------------------------------------------------------
34+save :: (Binary a, Typeable a) => Store -> Item a -> IO ()
35+save store item = saveSnapshot store final item
36+
37+
38+--------------------------------------------------------------------------------
39+-- | Save a specific snapshot of an item, so you can load it later using
40+-- 'loadSnapshot'.
41+saveSnapshot :: (Binary a, Typeable a)
42+ => Store -> Snapshot -> Item a -> IO ()
43+saveSnapshot store snapshot item =
44+ Store.set store (key (itemIdentifier item) snapshot) (itemBody item)
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Load an item compiled elsewhere. If the required item is not yet compiled,
49+-- the build system will take care of that automatically.
50+load :: (Binary a, Typeable a) => Identifier -> Compiler (Item a)
51+load id' = loadSnapshot id' final
52+
53+
54+--------------------------------------------------------------------------------
55+-- | Require a specific snapshot of an item.
56+loadSnapshot :: (Binary a, Typeable a)
57+ => Identifier -> Snapshot -> Compiler (Item a)
58+loadSnapshot id' snapshot = do
59+ store <- compilerStore <$> compilerAsk
60+ universe <- compilerUniverse <$> compilerAsk
61+
62+ -- Quick check for better error messages
63+ when (id' `S.notMember` universe) $ fail notFound
64+
65+ compilerTellDependencies [IdentifierDependency id']
66+ compilerResult $ CompilerRequire (id', snapshot) $ do
67+ result <- compilerUnsafeIO $ Store.get store (key id' snapshot)
68+ case result of
69+ Store.NotFound -> fail notFound
70+ Store.WrongType e r -> fail $ wrongType e r
71+ Store.Found x -> return $ Item id' x
72+ where
73+ notFound =
74+ "Hakyll.Core.Compiler.Require.load: " ++ show id' ++
75+ " (snapshot " ++ snapshot ++ ") was not found in the cache, " ++
76+ "the cache might be corrupted or " ++
77+ "the item you are referring to might not exist"
78+ wrongType e r =
79+ "Hakyll.Core.Compiler.Require.load: " ++ show id' ++
80+ " (snapshot " ++ snapshot ++ ") was found in the cache, " ++
81+ "but does not have the right type: expected " ++ show e ++
82+ " but got " ++ show r
83+
84+
85+--------------------------------------------------------------------------------
86+-- | A shortcut for only requiring the body of an item.
87+--
88+-- > loadBody = fmap itemBody . load
89+loadBody :: (Binary a, Typeable a) => Identifier -> Compiler a
90+loadBody id' = loadSnapshotBody id' final
91+
92+
93+--------------------------------------------------------------------------------
94+-- | A shortcut for only requiring the body for a specific snapshot of an item
95+loadSnapshotBody :: (Binary a, Typeable a)
96+ => Identifier -> Snapshot -> Compiler a
97+loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
98+
99+
100+--------------------------------------------------------------------------------
101+-- | This function allows you to 'load' a dynamic list of items
102+loadAll :: (Binary a, Typeable a) => Pattern -> Compiler [Item a]
103+loadAll pattern = loadAllSnapshots pattern final
104+
105+
106+--------------------------------------------------------------------------------
107+-- | Load a specific snapshot for each of dynamic list of items
108+loadAllSnapshots :: (Binary a, Typeable a)
109+ => Pattern -> Snapshot -> Compiler [Item a]
110+loadAllSnapshots pattern snapshot = do
111+ matching <- getMatches pattern
112+ mapM (\i -> loadSnapshot i snapshot) matching
113+
114+
115+--------------------------------------------------------------------------------
116+key :: Identifier -> String -> [String]
117+key identifier snapshot =
118+ ["Hakyll.Core.Compiler.Require", show identifier, snapshot]
119+
120+
121+--------------------------------------------------------------------------------
122+final :: Snapshot
123+final = "_final"
lib/Hakyll/Core/Configuration.hsnew file+134
@@ -0,0 +1,134 @@
1+--------------------------------------------------------------------------------
2+-- | Exports a datastructure for the top-level hakyll configuration
3+module Hakyll.Core.Configuration
4+ ( Configuration (..)
5+ , shouldIgnoreFile
6+ , defaultConfiguration
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Data.Default (Default (..))
12+import Data.List (isPrefixOf, isSuffixOf)
13+import System.Directory (canonicalizePath)
14+import System.Exit (ExitCode)
15+import System.FilePath (isAbsolute, normalise, takeFileName)
16+import System.IO.Error (catchIOError)
17+import System.Process (system)
18+
19+
20+--------------------------------------------------------------------------------
21+data Configuration = Configuration
22+ { -- | Directory in which the output written
23+ destinationDirectory :: FilePath
24+ , -- | Directory where hakyll's internal store is kept
25+ storeDirectory :: FilePath
26+ , -- | Directory in which some temporary files will be kept
27+ tmpDirectory :: FilePath
28+ , -- | Directory where hakyll finds the files to compile. This is @.@ by
29+ -- default.
30+ providerDirectory :: FilePath
31+ , -- | Function to determine ignored files
32+ --
33+ -- In 'defaultConfiguration', the following files are ignored:
34+ --
35+ -- * files starting with a @.@
36+ --
37+ -- * files starting with a @#@
38+ --
39+ -- * files ending with a @~@
40+ --
41+ -- * files ending with @.swp@
42+ --
43+ -- Note that the files in 'destinationDirectory' and 'storeDirectory' will
44+ -- also be ignored. Note that this is the configuration parameter, if you
45+ -- want to use the test, you should use 'shouldIgnoreFile'.
46+ --
47+ ignoreFile :: FilePath -> Bool
48+ , -- | Here, you can plug in a system command to upload/deploy your site.
49+ --
50+ -- Example:
51+ --
52+ -- > rsync -ave 'ssh -p 2217' _site jaspervdj@jaspervdj.be:hakyll
53+ --
54+ -- You can execute this by using
55+ --
56+ -- > ./site deploy
57+ --
58+ deployCommand :: String
59+ , -- | Function to deploy the site from Haskell.
60+ --
61+ -- By default, this command executes the shell command stored in
62+ -- 'deployCommand'. If you override it, 'deployCommand' will not
63+ -- be used implicitely.
64+ --
65+ -- The 'Configuration' object is passed as a parameter to this
66+ -- function.
67+ --
68+ deploySite :: Configuration -> IO ExitCode
69+ , -- | Use an in-memory cache for items. This is faster but uses more
70+ -- memory.
71+ 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
77+ , -- | Override default port for preview server. Default is 8000.
78+ -- One can also override the port as a command line argument:
79+ -- ./site preview -p 1234
80+ previewPort :: Int
81+ }
82+
83+--------------------------------------------------------------------------------
84+instance Default Configuration where
85+ def = defaultConfiguration
86+
87+--------------------------------------------------------------------------------
88+-- | Default configuration for a hakyll application
89+defaultConfiguration :: Configuration
90+defaultConfiguration = Configuration
91+ { destinationDirectory = "_site"
92+ , storeDirectory = "_cache"
93+ , tmpDirectory = "_cache/tmp"
94+ , providerDirectory = "."
95+ , ignoreFile = ignoreFile'
96+ , deployCommand = "echo 'No deploy command specified' && exit 1"
97+ , deploySite = system . deployCommand
98+ , inMemoryCache = True
99+ , previewHost = "127.0.0.1"
100+ , previewPort = 8000
101+ }
102+ where
103+ ignoreFile' path
104+ | "." `isPrefixOf` fileName = True
105+ | "#" `isPrefixOf` fileName = True
106+ | "~" `isSuffixOf` fileName = True
107+ | ".swp" `isSuffixOf` fileName = True
108+ | otherwise = False
109+ where
110+ fileName = takeFileName path
111+
112+
113+--------------------------------------------------------------------------------
114+-- | Check if a file should be ignored
115+shouldIgnoreFile :: Configuration -> FilePath -> IO Bool
116+shouldIgnoreFile conf path = orM
117+ [ inDir (destinationDirectory conf)
118+ , inDir (storeDirectory conf)
119+ , inDir (tmpDirectory conf)
120+ , return (ignoreFile conf path')
121+ ]
122+ where
123+ path' = normalise path
124+ absolute = isAbsolute path
125+
126+ inDir dir
127+ | absolute = do
128+ dir' <- catchIOError (canonicalizePath dir) (const $ return dir)
129+ return $ dir' `isPrefixOf` path'
130+ | otherwise = return $ dir `isPrefixOf` path'
131+
132+ orM :: [IO Bool] -> IO Bool
133+ orM [] = return False
134+ orM (x : xs) = x >>= \b -> if b then return True else orM xs
lib/Hakyll/Core/Dependencies.hsnew file+146
@@ -0,0 +1,146 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE DeriveDataTypeable #-}
3+module Hakyll.Core.Dependencies
4+ ( Dependency (..)
5+ , DependencyFacts
6+ , outOfDate
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Control.Monad (foldM, forM_, unless, when)
12+import Control.Monad.Reader (ask)
13+import Control.Monad.RWS (RWS, runRWS)
14+import qualified Control.Monad.State as State
15+import Control.Monad.Writer (tell)
16+import Data.Binary (Binary (..), getWord8,
17+ putWord8)
18+import Data.List (find)
19+import Data.Map (Map)
20+import qualified Data.Map as M
21+import Data.Maybe (fromMaybe)
22+import Data.Set (Set)
23+import qualified Data.Set as S
24+import Data.Typeable (Typeable)
25+
26+
27+--------------------------------------------------------------------------------
28+import Hakyll.Core.Identifier
29+import Hakyll.Core.Identifier.Pattern
30+
31+
32+--------------------------------------------------------------------------------
33+data Dependency
34+ = PatternDependency Pattern (Set Identifier)
35+ | IdentifierDependency Identifier
36+ deriving (Show, Typeable)
37+
38+
39+--------------------------------------------------------------------------------
40+instance Binary Dependency where
41+ put (PatternDependency p is) = putWord8 0 >> put p >> put is
42+ put (IdentifierDependency i) = putWord8 1 >> put i
43+ get = getWord8 >>= \t -> case t of
44+ 0 -> PatternDependency <$> get <*> get
45+ 1 -> IdentifierDependency <$> get
46+ _ -> error "Data.Binary.get: Invalid Dependency"
47+
48+
49+--------------------------------------------------------------------------------
50+type DependencyFacts = Map Identifier [Dependency]
51+
52+
53+--------------------------------------------------------------------------------
54+outOfDate
55+ :: [Identifier] -- ^ All known identifiers
56+ -> Set Identifier -- ^ Initially out-of-date resources
57+ -> DependencyFacts -- ^ Old dependency facts
58+ -> (Set Identifier, DependencyFacts, [String])
59+outOfDate universe ood oldFacts =
60+ let (_, state, logs) = runRWS rws universe (DependencyState oldFacts ood)
61+ in (dependencyOod state, dependencyFacts state, logs)
62+ where
63+ rws = do
64+ checkNew
65+ checkChangedPatterns
66+ bruteForce
67+
68+
69+--------------------------------------------------------------------------------
70+data DependencyState = DependencyState
71+ { dependencyFacts :: DependencyFacts
72+ , dependencyOod :: Set Identifier
73+ } deriving (Show)
74+
75+
76+--------------------------------------------------------------------------------
77+type DependencyM a = RWS [Identifier] [String] DependencyState a
78+
79+
80+--------------------------------------------------------------------------------
81+markOod :: Identifier -> DependencyM ()
82+markOod id' = State.modify $ \s ->
83+ s {dependencyOod = S.insert id' $ dependencyOod s}
84+
85+
86+--------------------------------------------------------------------------------
87+dependenciesFor :: Identifier -> DependencyM [Identifier]
88+dependenciesFor id' = do
89+ facts <- dependencyFacts <$> State.get
90+ return $ concatMap dependenciesFor' $ fromMaybe [] $ M.lookup id' facts
91+ where
92+ dependenciesFor' (IdentifierDependency i) = [i]
93+ dependenciesFor' (PatternDependency _ is) = S.toList is
94+
95+
96+--------------------------------------------------------------------------------
97+checkNew :: DependencyM ()
98+checkNew = do
99+ universe <- ask
100+ facts <- dependencyFacts <$> State.get
101+ forM_ universe $ \id' -> unless (id' `M.member` facts) $ do
102+ tell [show id' ++ " is out-of-date because it is new"]
103+ markOod id'
104+
105+
106+--------------------------------------------------------------------------------
107+checkChangedPatterns :: DependencyM ()
108+checkChangedPatterns = do
109+ facts <- M.toList . dependencyFacts <$> State.get
110+ forM_ facts $ \(id', deps) -> do
111+ deps' <- foldM (go id') [] deps
112+ State.modify $ \s -> s
113+ {dependencyFacts = M.insert id' deps' $ dependencyFacts s}
114+ where
115+ go _ ds (IdentifierDependency i) = return $ IdentifierDependency i : ds
116+ go id' ds (PatternDependency p ls) = do
117+ universe <- ask
118+ let ls' = S.fromList $ filterMatches p universe
119+ if ls == ls'
120+ then return $ PatternDependency p ls : ds
121+ else do
122+ tell [show id' ++ " is out-of-date because a pattern changed"]
123+ markOod id'
124+ return $ PatternDependency p ls' : ds
125+
126+
127+--------------------------------------------------------------------------------
128+bruteForce :: DependencyM ()
129+bruteForce = do
130+ todo <- ask
131+ go todo
132+ where
133+ go todo = do
134+ (todo', changed) <- foldM check ([], False) todo
135+ when changed (go todo')
136+
137+ check (todo, changed) id' = do
138+ deps <- dependenciesFor id'
139+ ood <- dependencyOod <$> State.get
140+ case find (`S.member` ood) deps of
141+ Nothing -> return (id' : todo, changed)
142+ Just d -> do
143+ tell [show id' ++ " is out-of-date because " ++
144+ show d ++ " is out-of-date"]
145+ markOod id'
146+ return (todo, True)
lib/Hakyll/Core/File.hsnew file+93
@@ -0,0 +1,93 @@
1+--------------------------------------------------------------------------------
2+-- | Exports simple compilers to just copy files
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveDataTypeable #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+module Hakyll.Core.File
7+ ( CopyFile (..)
8+ , copyFileCompiler
9+ , TmpFile (..)
10+ , newTmpFile
11+ ) where
12+
13+
14+--------------------------------------------------------------------------------
15+import Data.Binary (Binary (..))
16+import 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,
23+ renameFile)
24+import System.FilePath ((</>))
25+import System.Random (randomIO)
26+
27+
28+--------------------------------------------------------------------------------
29+import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
31+import Hakyll.Core.Configuration
32+import Hakyll.Core.Item
33+import Hakyll.Core.Provider
34+import qualified Hakyll.Core.Store as Store
35+import Hakyll.Core.Util.File
36+import Hakyll.Core.Writable
37+
38+
39+--------------------------------------------------------------------------------
40+-- | This will copy any file directly by using a system call
41+newtype CopyFile = CopyFile FilePath
42+ deriving (Binary, Eq, Ord, Show, Typeable)
43+
44+
45+--------------------------------------------------------------------------------
46+instance Writable CopyFile where
47+#if MIN_VERSION_directory(1,2,6)
48+ write dst (Item _ (CopyFile src)) = copyFileWithMetadata src dst
49+#else
50+ write dst (Item _ (CopyFile src)) = copyFile src dst
51+#endif
52+--------------------------------------------------------------------------------
53+copyFileCompiler :: Compiler (Item CopyFile)
54+copyFileCompiler = do
55+ identifier <- getUnderlying
56+ provider <- compilerProvider <$> compilerAsk
57+ makeItem $ CopyFile $ resourceFilePath provider identifier
58+
59+
60+--------------------------------------------------------------------------------
61+newtype TmpFile = TmpFile FilePath
62+ deriving (Typeable)
63+
64+
65+--------------------------------------------------------------------------------
66+instance Binary TmpFile where
67+ put _ = return ()
68+ get = error $
69+ "Hakyll.Core.File.TmpFile: You tried to load a TmpFile, however, " ++
70+ "this is not possible since these are deleted as soon as possible."
71+
72+
73+--------------------------------------------------------------------------------
74+instance Writable TmpFile where
75+ write dst (Item _ (TmpFile fp)) = renameFile fp dst
76+
77+
78+--------------------------------------------------------------------------------
79+-- | Create a tmp file
80+newTmpFile :: String -- ^ Suffix and extension
81+ -> Compiler TmpFile -- ^ Resulting tmp path
82+newTmpFile suffix = do
83+ path <- mkPath
84+ compilerUnsafeIO $ makeDirectories path
85+ debugCompiler $ "newTmpFile " ++ path
86+ return $ TmpFile path
87+ where
88+ mkPath = do
89+ rand <- compilerUnsafeIO $ randomIO :: Compiler Int
90+ tmp <- tmpDirectory . compilerConfig <$> compilerAsk
91+ let path = tmp </> Store.hash [show rand] ++ "-" ++ suffix
92+ exists <- compilerUnsafeIO $ doesFileExist path
93+ if exists then mkPath else return path
lib/Hakyll/Core/Identifier.hsnew file+80
@@ -0,0 +1,80 @@
1+--------------------------------------------------------------------------------
2+-- | An identifier is a type used to uniquely identify an item. An identifier is
3+-- conceptually similar to a file path. Examples of identifiers are:
4+--
5+-- * @posts/foo.markdown@
6+--
7+-- * @index@
8+--
9+-- * @error/404@
10+{-# LANGUAGE DeriveDataTypeable #-}
11+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
12+module Hakyll.Core.Identifier
13+ ( Identifier
14+ , fromFilePath
15+ , toFilePath
16+ , identifierVersion
17+ , setVersion
18+ ) where
19+
20+
21+--------------------------------------------------------------------------------
22+import Control.DeepSeq (NFData (..))
23+import Data.List (intercalate)
24+import System.FilePath (dropTrailingPathSeparator, splitPath)
25+
26+
27+--------------------------------------------------------------------------------
28+import Data.Binary (Binary (..))
29+import Data.Typeable (Typeable)
30+import GHC.Exts (IsString, fromString)
31+
32+
33+--------------------------------------------------------------------------------
34+data Identifier = Identifier
35+ { identifierVersion :: Maybe String
36+ , identifierPath :: String
37+ } deriving (Eq, Ord, Typeable)
38+
39+
40+--------------------------------------------------------------------------------
41+instance Binary Identifier where
42+ put (Identifier v p) = put v >> put p
43+ get = Identifier <$> get <*> get
44+
45+
46+--------------------------------------------------------------------------------
47+instance IsString Identifier where
48+ fromString = fromFilePath
49+
50+
51+--------------------------------------------------------------------------------
52+instance NFData Identifier where
53+ rnf (Identifier v p) = rnf v `seq` rnf p `seq` ()
54+
55+
56+--------------------------------------------------------------------------------
57+instance Show Identifier where
58+ show i = case identifierVersion i of
59+ Nothing -> toFilePath i
60+ Just v -> toFilePath i ++ " (" ++ v ++ ")"
61+
62+
63+--------------------------------------------------------------------------------
64+-- | Parse an identifier from a string
65+fromFilePath :: FilePath -> Identifier
66+fromFilePath = Identifier Nothing .
67+ intercalate "/" . filter (not . null) . split'
68+ where
69+ split' = map dropTrailingPathSeparator . splitPath
70+
71+
72+--------------------------------------------------------------------------------
73+-- | Convert an identifier to a relative 'FilePath'
74+toFilePath :: Identifier -> FilePath
75+toFilePath = identifierPath
76+
77+
78+--------------------------------------------------------------------------------
79+setVersion :: Maybe String -> Identifier -> Identifier
80+setVersion v i = i {identifierVersion = v}
lib/Hakyll/Core/Identifier/Pattern.hsnew file+264
@@ -0,0 +1,264 @@
1+--------------------------------------------------------------------------------
2+-- | As 'Identifier' is used to specify a single item, a 'Pattern' is used to
3+-- specify a list of items.
4+--
5+-- In most cases, globs are used for patterns.
6+--
7+-- A very simple pattern of such a pattern is @\"foo\/bar\"@. This pattern will
8+-- only match the exact @foo\/bar@ identifier.
9+--
10+-- To match more than one identifier, there are different captures that one can
11+-- use:
12+--
13+-- * @\"*\"@: matches at most one element of an identifier;
14+--
15+-- * @\"**\"@: matches one or more elements of an identifier.
16+--
17+-- Some examples:
18+--
19+-- * @\"foo\/*\"@ will match @\"foo\/bar\"@ and @\"foo\/foo\"@, but not
20+-- @\"foo\/bar\/qux\"@;
21+--
22+-- * @\"**\"@ will match any identifier;
23+--
24+-- * @\"foo\/**\"@ will match @\"foo\/bar\"@ and @\"foo\/bar\/qux\"@, but not
25+-- @\"bar\/foo\"@;
26+--
27+-- * @\"foo\/*.html\"@ will match all HTML files in the @\"foo\/\"@ directory.
28+--
29+-- The 'capture' function allows the user to get access to the elements captured
30+-- by the capture elements in a glob or regex pattern.
31+{-# OPTIONS_GHC -fno-warn-orphans #-}
32+module Hakyll.Core.Identifier.Pattern
33+ ( -- * The pattern type
34+ Pattern
35+
36+ -- * Creating patterns
37+ , fromGlob
38+ , fromList
39+ , fromRegex
40+ , fromVersion
41+ , hasVersion
42+ , hasNoVersion
43+
44+ -- * Composing patterns
45+ , (.&&.)
46+ , (.||.)
47+ , complement
48+
49+ -- * Applying patterns
50+ , matches
51+ , filterMatches
52+
53+ -- * Capturing strings
54+ , capture
55+ , fromCapture
56+ , fromCaptures
57+ ) where
58+
59+
60+--------------------------------------------------------------------------------
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
67+
68+
69+--------------------------------------------------------------------------------
70+import GHC.Exts (IsString, fromString)
71+import Text.Regex.TDFA ((=~))
72+
73+
74+--------------------------------------------------------------------------------
75+import Hakyll.Core.Identifier
76+import Hakyll.Core.Identifier.Pattern.Internal
77+
78+
79+--------------------------------------------------------------------------------
80+instance IsString Pattern where
81+ fromString = fromGlob
82+
83+
84+--------------------------------------------------------------------------------
85+-- | Parse a pattern from a string
86+fromGlob :: String -> Pattern
87+fromGlob = Glob . parse'
88+ where
89+ parse' str =
90+ let (chunk, rest) = break (`elem` "\\*") str
91+ in case rest of
92+ ('\\' : x : xs) -> Literal (chunk ++ [x]) : parse' xs
93+ ('*' : '*' : xs) -> Literal chunk : CaptureMany : parse' xs
94+ ('*' : xs) -> Literal chunk : Capture : parse' xs
95+ xs -> Literal chunk : Literal xs : []
96+
97+
98+--------------------------------------------------------------------------------
99+-- | Create a 'Pattern' from a list of 'Identifier's it should match.
100+--
101+-- /Warning/: use this carefully with 'hasNoVersion' and 'hasVersion'. The
102+-- 'Identifier's in the list /already/ have versions assigned, and the pattern
103+-- will then only match the intersection of both versions.
104+--
105+-- A more concrete example,
106+--
107+-- > fromList ["foo.markdown"] .&&. hasVersion "pdf"
108+--
109+-- will not match anything! The @"foo.markdown"@ 'Identifier' has no version
110+-- assigned, so the LHS of '.&&.' will only match this 'Identifier' with no
111+-- version. The RHS only matches 'Identifier's with version set to @"pdf"@ --
112+-- hence, this pattern matches nothing.
113+--
114+-- The correct way to use this is:
115+--
116+-- > fromList $ map (setVersion $ Just "pdf") ["foo.markdown"]
117+fromList :: [Identifier] -> Pattern
118+fromList = List . S.fromList
119+
120+
121+--------------------------------------------------------------------------------
122+-- | Create a 'Pattern' from a regex
123+--
124+-- Example:
125+--
126+-- > regex "^foo/[^x]*$
127+fromRegex :: String -> Pattern
128+fromRegex = Regex
129+
130+
131+--------------------------------------------------------------------------------
132+-- | Create a pattern which matches all items with the given version.
133+fromVersion :: Maybe String -> Pattern
134+fromVersion = Version
135+
136+
137+--------------------------------------------------------------------------------
138+-- | Specify a version, e.g.
139+--
140+-- > "foo/*.markdown" .&&. hasVersion "pdf"
141+hasVersion :: String -> Pattern
142+hasVersion = fromVersion . Just
143+
144+
145+--------------------------------------------------------------------------------
146+-- | Match only if the identifier has no version set, e.g.
147+--
148+-- > "foo/*.markdown" .&&. hasNoVersion
149+hasNoVersion :: Pattern
150+hasNoVersion = fromVersion Nothing
151+
152+
153+--------------------------------------------------------------------------------
154+-- | '&&' for patterns: the given identifier must match both subterms
155+(.&&.) :: Pattern -> Pattern -> Pattern
156+x .&&. y = And x y
157+infixr 3 .&&.
158+
159+
160+--------------------------------------------------------------------------------
161+-- | '||' for patterns: the given identifier must match any subterm
162+(.||.) :: Pattern -> Pattern -> Pattern
163+x .||. y = complement (complement x `And` complement y) -- De Morgan's law
164+infixr 2 .||.
165+
166+
167+--------------------------------------------------------------------------------
168+-- | Inverts a pattern, e.g.
169+--
170+-- > complement "foo/bar.html"
171+--
172+-- will match /anything/ except @\"foo\/bar.html\"@
173+complement :: Pattern -> Pattern
174+complement = Complement
175+
176+
177+--------------------------------------------------------------------------------
178+-- | Check if an identifier matches a pattern
179+matches :: Pattern -> Identifier -> Bool
180+matches Everything _ = True
181+matches (Complement p) i = not $ matches p i
182+matches (And x y) i = matches x i && matches y i
183+matches (Glob p) i = isJust $ capture (Glob p) i
184+matches (List l) i = i `S.member` l
185+matches (Regex r) i = toFilePath i =~ r
186+matches (Version v) i = identifierVersion i == v
187+
188+
189+--------------------------------------------------------------------------------
190+-- | Given a list of identifiers, retain only those who match the given pattern
191+filterMatches :: Pattern -> [Identifier] -> [Identifier]
192+filterMatches = filter . matches
193+
194+
195+--------------------------------------------------------------------------------
196+-- | Split a list at every possible point, generate a list of (init, tail)
197+-- cases. The result is sorted with inits decreasing in length.
198+splits :: [a] -> [([a], [a])]
199+splits = inits &&& tails >>> uncurry zip >>> reverse
200+
201+
202+--------------------------------------------------------------------------------
203+-- | Match a glob or regex pattern against an identifier, generating a list of captures
204+capture :: Pattern -> Identifier -> Maybe [String]
205+capture (Glob p) i = capture' p (toFilePath i)
206+capture (Regex pat) i = Just groups
207+ where (_, _, _, groups) = ((toFilePath i) =~ pat) :: (String, String, String, [String])
208+capture _ _ = Nothing
209+
210+
211+--------------------------------------------------------------------------------
212+-- | Internal verion of 'capture'
213+capture' :: [GlobComponent] -> String -> Maybe [String]
214+capture' [] [] = Just [] -- An empty match
215+capture' [] _ = Nothing -- No match
216+capture' (Literal l : ms) str
217+ -- Match the literal against the string
218+ | l `isPrefixOf` str = capture' ms $ drop (length l) str
219+ | otherwise = Nothing
220+capture' (Capture : ms) str =
221+ -- Match until the next /
222+ let (chunk, rest) = break (== '/') str
223+ in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
224+capture' (CaptureMany : ms) str =
225+ -- Match everything
226+ msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
227+
228+
229+--------------------------------------------------------------------------------
230+-- | Create an identifier from a pattern by filling in the captures with a given
231+-- string
232+--
233+-- Example:
234+--
235+-- > fromCapture (fromGlob "tags/*") "foo"
236+--
237+-- Result:
238+--
239+-- > "tags/foo"
240+fromCapture :: Pattern -> String -> Identifier
241+fromCapture pattern = fromCaptures pattern . repeat
242+
243+
244+--------------------------------------------------------------------------------
245+-- | Create an identifier from a pattern by filling in the captures with the
246+-- given list of strings
247+fromCaptures :: Pattern -> [String] -> Identifier
248+fromCaptures (Glob p) = fromFilePath . fromCaptures' p
249+fromCaptures _ = error $
250+ "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures only works " ++
251+ "on simple globs!"
252+
253+
254+--------------------------------------------------------------------------------
255+-- | Internally used version of 'fromCaptures'
256+fromCaptures' :: [GlobComponent] -> [String] -> String
257+fromCaptures' [] _ = mempty
258+fromCaptures' (m : ms) [] = case m of
259+ Literal l -> l `mappend` fromCaptures' ms []
260+ _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
261+ ++ "identifier list exhausted"
262+fromCaptures' (m : ms) ids@(i : is) = case m of
263+ Literal l -> l `mappend` fromCaptures' ms ids
264+ _ -> i `mappend` fromCaptures' ms is
lib/Hakyll/Core/Identifier/Pattern/Internal.hsnew file+92
@@ -0,0 +1,92 @@
1+-- | This internal module is mostly here to prevent CPP conflicting with Haskell
2+-- comments.
3+{-# LANGUAGE CPP #-}
4+module Hakyll.Core.Identifier.Pattern.Internal
5+ ( GlobComponent (..)
6+ , Pattern (..)
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Data.Binary (Binary (..), getWord8, putWord8)
12+import Data.Set (Set)
13+
14+
15+--------------------------------------------------------------------------------
16+#if MIN_VERSION_base(4,9,0)
17+import Data.Semigroup (Semigroup (..))
18+#endif
19+
20+
21+--------------------------------------------------------------------------------
22+import Hakyll.Core.Identifier
23+
24+
25+--------------------------------------------------------------------------------
26+-- | Elements of a glob pattern
27+data GlobComponent
28+ = Capture
29+ | CaptureMany
30+ | Literal String
31+ deriving (Eq, Show)
32+
33+
34+--------------------------------------------------------------------------------
35+instance Binary GlobComponent where
36+ put Capture = putWord8 0
37+ put CaptureMany = putWord8 1
38+ put (Literal s) = putWord8 2 >> put s
39+
40+ get = getWord8 >>= \t -> case t of
41+ 0 -> pure Capture
42+ 1 -> pure CaptureMany
43+ 2 -> Literal <$> get
44+ _ -> error "Data.Binary.get: Invalid GlobComponent"
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Type that allows matching on identifiers
49+data Pattern
50+ = Everything
51+ | Complement Pattern
52+ | And Pattern Pattern
53+ | Glob [GlobComponent]
54+ | List (Set Identifier)
55+ | Regex String
56+ | Version (Maybe String)
57+ deriving (Show)
58+
59+
60+--------------------------------------------------------------------------------
61+instance Binary Pattern where
62+ put Everything = putWord8 0
63+ put (Complement p) = putWord8 1 >> put p
64+ put (And x y) = putWord8 2 >> put x >> put y
65+ put (Glob g) = putWord8 3 >> put g
66+ put (List is) = putWord8 4 >> put is
67+ put (Regex r) = putWord8 5 >> put r
68+ put (Version v) = putWord8 6 >> put v
69+
70+ get = getWord8 >>= \t -> case t of
71+ 0 -> pure Everything
72+ 1 -> Complement <$> get
73+ 2 -> And <$> get <*> get
74+ 3 -> Glob <$> get
75+ 4 -> List <$> get
76+ 5 -> Regex <$> get
77+ _ -> Version <$> get
78+
79+
80+--------------------------------------------------------------------------------
81+#if MIN_VERSION_base(4,9,0)
82+instance Semigroup Pattern where
83+ (<>) = And
84+
85+instance Monoid Pattern where
86+ mempty = Everything
87+ mappend = (<>)
88+#else
89+instance Monoid Pattern where
90+ mempty = Everything
91+ mappend = And
92+#endif
lib/Hakyll/Core/Item.hsnew file+63
@@ -0,0 +1,63 @@
1+--------------------------------------------------------------------------------
2+-- | An item is a combination of some content and its 'Identifier'. This way, we
3+-- can still use the 'Identifier' to access metadata.
4+{-# LANGUAGE DeriveDataTypeable #-}
5+module Hakyll.Core.Item
6+ ( Item (..)
7+ , itemSetBody
8+ , withItemBody
9+ ) where
10+
11+
12+--------------------------------------------------------------------------------
13+import Data.Binary (Binary (..))
14+import Data.Foldable (Foldable (..))
15+import Data.Typeable (Typeable)
16+import Prelude hiding (foldr)
17+
18+
19+--------------------------------------------------------------------------------
20+import Hakyll.Core.Compiler.Internal
21+import Hakyll.Core.Identifier
22+
23+
24+--------------------------------------------------------------------------------
25+data Item a = Item
26+ { itemIdentifier :: Identifier
27+ , itemBody :: a
28+ } deriving (Show, Typeable)
29+
30+
31+--------------------------------------------------------------------------------
32+instance Functor Item where
33+ fmap f (Item i x) = Item i (f x)
34+
35+
36+--------------------------------------------------------------------------------
37+instance Foldable Item where
38+ foldr f z (Item _ x) = f x z
39+
40+
41+--------------------------------------------------------------------------------
42+instance Traversable Item where
43+ traverse f (Item i x) = Item i <$> f x
44+
45+
46+--------------------------------------------------------------------------------
47+instance Binary a => Binary (Item a) where
48+ put (Item i x) = put i >> put x
49+ get = Item <$> get <*> get
50+
51+
52+--------------------------------------------------------------------------------
53+itemSetBody :: a -> Item b -> Item a
54+itemSetBody x (Item i _) = Item i x
55+
56+
57+--------------------------------------------------------------------------------
58+-- | Perform a compiler action on the item body. This is the same as 'traverse',
59+-- but looks less intimidating.
60+--
61+-- > withItemBody = traverse
62+withItemBody :: (a -> Compiler b) -> Item a -> Compiler (Item b)
63+withItemBody = traverse
lib/Hakyll/Core/Item/SomeItem.hsnew file+23
@@ -0,0 +1,23 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+module Hakyll.Core.Item.SomeItem
5+ ( SomeItem (..)
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Data.Binary (Binary)
11+import Data.Typeable (Typeable)
12+
13+
14+--------------------------------------------------------------------------------
15+import Hakyll.Core.Item
16+import Hakyll.Core.Writable
17+
18+
19+--------------------------------------------------------------------------------
20+-- | An existential type, mostly for internal usage.
21+data SomeItem = forall a.
22+ (Binary a, Typeable a, Writable a) => SomeItem (Item a)
23+ deriving (Typeable)
lib/Hakyll/Core/Logger.hsnew file+103
@@ -0,0 +1,103 @@
1+--------------------------------------------------------------------------------
2+-- | Produce pretty, thread-safe logs
3+module Hakyll.Core.Logger
4+ ( Verbosity (..)
5+ , Logger
6+ , new
7+ , flush
8+ , error
9+ , header
10+ , message
11+ , debug
12+ ) where
13+
14+
15+--------------------------------------------------------------------------------
16+import Control.Concurrent (forkIO)
17+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
18+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
19+import Control.Monad (forever)
20+import Control.Monad.Trans (MonadIO, liftIO)
21+import Data.List (intercalate)
22+import Prelude hiding (error)
23+
24+
25+--------------------------------------------------------------------------------
26+data Verbosity
27+ = Error
28+ | Message
29+ | Debug
30+ deriving (Eq, Ord, Show)
31+
32+
33+--------------------------------------------------------------------------------
34+-- | Logger structure. Very complicated.
35+data Logger = Logger
36+ { loggerChan :: Chan (Maybe String) -- ^ Nothing marks the end
37+ , loggerSync :: MVar () -- ^ Used for sync on quit
38+ , loggerSink :: String -> IO () -- ^ Out sink
39+ , loggerVerbosity :: Verbosity -- ^ Verbosity
40+ }
41+
42+
43+--------------------------------------------------------------------------------
44+-- | Create a new logger
45+new :: Verbosity -> IO Logger
46+new vbty = do
47+ logger <- Logger <$>
48+ newChan <*> newEmptyMVar <*> pure putStrLn <*> pure vbty
49+ _ <- forkIO $ loggerThread logger
50+ return logger
51+ where
52+ loggerThread logger = forever $ do
53+ msg <- readChan $ loggerChan logger
54+ case msg of
55+ -- Stop: sync
56+ Nothing -> putMVar (loggerSync logger) ()
57+ -- Print and continue
58+ Just m -> loggerSink logger m
59+
60+
61+--------------------------------------------------------------------------------
62+-- | Flush the logger (blocks until flushed)
63+flush :: Logger -> IO ()
64+flush logger = do
65+ writeChan (loggerChan logger) Nothing
66+ () <- takeMVar $ loggerSync logger
67+ return ()
68+
69+
70+--------------------------------------------------------------------------------
71+string :: MonadIO m
72+ => Logger -- ^ Logger
73+ -> Verbosity -- ^ Verbosity of the string
74+ -> String -- ^ Section name
75+ -> m () -- ^ No result
76+string l v m
77+ | loggerVerbosity l >= v = liftIO $ writeChan (loggerChan l) (Just m)
78+ | otherwise = return ()
79+
80+
81+--------------------------------------------------------------------------------
82+error :: MonadIO m => Logger -> String -> m ()
83+error l m = string l Error $ " [ERROR] " ++ indent m
84+
85+
86+--------------------------------------------------------------------------------
87+header :: MonadIO m => Logger -> String -> m ()
88+header l = string l Message
89+
90+
91+--------------------------------------------------------------------------------
92+message :: MonadIO m => Logger -> String -> m ()
93+message l m = string l Message $ " " ++ indent m
94+
95+
96+--------------------------------------------------------------------------------
97+debug :: 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
lib/Hakyll/Core/Metadata.hsnew file+138
@@ -0,0 +1,138 @@
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.hsnew file+43
@@ -0,0 +1,43 @@
1+--------------------------------------------------------------------------------
2+-- | This module provides an wrapper API around the file system which does some
3+-- caching.
4+module Hakyll.Core.Provider
5+ ( -- * Constructing resource providers
6+ Internal.Provider
7+ , newProvider
8+
9+ -- * Querying resource properties
10+ , Internal.resourceList
11+ , Internal.resourceExists
12+ , Internal.resourceFilePath
13+ , Internal.resourceModified
14+ , Internal.resourceModificationTime
15+
16+ -- * Access to raw resource content
17+ , Internal.resourceString
18+ , Internal.resourceLBS
19+
20+ -- * Access to metadata and body content
21+ , Internal.resourceMetadata
22+ , Internal.resourceBody
23+ ) where
24+
25+
26+--------------------------------------------------------------------------------
27+import qualified Hakyll.Core.Provider.Internal as Internal
28+import qualified Hakyll.Core.Provider.MetadataCache as Internal
29+import Hakyll.Core.Store (Store)
30+
31+
32+--------------------------------------------------------------------------------
33+-- | Create a resource provider
34+newProvider :: Store -- ^ Store to use
35+ -> (FilePath -> IO Bool) -- ^ Should we ignore this file?
36+ -> FilePath -- ^ Search directory
37+ -> IO Internal.Provider -- ^ Resulting provider
38+newProvider store ignore directory = do
39+ -- Delete metadata cache where necessary
40+ p <- Internal.newProvider store ignore directory
41+ mapM_ (Internal.resourceInvalidateMetadataCache p) $
42+ filter (Internal.resourceModified p) $ Internal.resourceList p
43+ return p
lib/Hakyll/Core/Provider/Internal.hsnew file+202
@@ -0,0 +1,202 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+module Hakyll.Core.Provider.Internal
6+ ( ResourceInfo (..)
7+ , Provider (..)
8+ , newProvider
9+
10+ , resourceList
11+ , resourceExists
12+
13+ , resourceFilePath
14+ , resourceString
15+ , resourceLBS
16+
17+ , resourceModified
18+ , resourceModificationTime
19+ ) where
20+
21+
22+--------------------------------------------------------------------------------
23+import Control.DeepSeq (NFData (..), deepseq)
24+import Control.Monad (forM)
25+import Data.Binary (Binary (..))
26+import qualified Data.ByteString.Lazy as BL
27+import Data.Map (Map)
28+import qualified Data.Map as M
29+import Data.Maybe (fromMaybe)
30+import Data.Set (Set)
31+import qualified Data.Set as S
32+import Data.Time (Day (..), UTCTime (..))
33+import Data.Typeable (Typeable)
34+import System.Directory (getModificationTime)
35+import System.FilePath (addExtension, (</>))
36+
37+
38+--------------------------------------------------------------------------------
39+#if !MIN_VERSION_directory(1,2,0)
40+import Data.Time (readTime)
41+import System.Locale (defaultTimeLocale)
42+import System.Time (formatCalendarTime, toCalendarTime)
43+#endif
44+
45+
46+--------------------------------------------------------------------------------
47+import Hakyll.Core.Identifier
48+import Hakyll.Core.Store (Store)
49+import qualified Hakyll.Core.Store as Store
50+import Hakyll.Core.Util.File
51+
52+
53+--------------------------------------------------------------------------------
54+-- | Because UTCTime doesn't have a Binary instance...
55+newtype BinaryTime = BinaryTime {unBinaryTime :: UTCTime}
56+ deriving (Eq, NFData, Ord, Show, Typeable)
57+
58+
59+--------------------------------------------------------------------------------
60+instance Binary BinaryTime where
61+ put (BinaryTime (UTCTime (ModifiedJulianDay d) dt)) =
62+ put d >> put (toRational dt)
63+
64+ get = fmap BinaryTime $ UTCTime
65+ <$> (ModifiedJulianDay <$> get)
66+ <*> (fromRational <$> get)
67+
68+
69+--------------------------------------------------------------------------------
70+data ResourceInfo = ResourceInfo
71+ { resourceInfoModified :: BinaryTime
72+ , resourceInfoMetadata :: Maybe Identifier
73+ } deriving (Show, Typeable)
74+
75+
76+--------------------------------------------------------------------------------
77+instance Binary ResourceInfo where
78+ put (ResourceInfo mtime meta) = put mtime >> put meta
79+ get = ResourceInfo <$> get <*> get
80+
81+
82+--------------------------------------------------------------------------------
83+instance NFData ResourceInfo where
84+ rnf (ResourceInfo mtime meta) = rnf mtime `seq` rnf meta `seq` ()
85+
86+
87+--------------------------------------------------------------------------------
88+-- | Responsible for retrieving and listing resources
89+data Provider = Provider
90+ { -- Top of the provided directory
91+ providerDirectory :: FilePath
92+ , -- | A list of all files found
93+ providerFiles :: Map Identifier ResourceInfo
94+ , -- | A list of the files from the previous run
95+ providerOldFiles :: Map Identifier ResourceInfo
96+ , -- | Underlying persistent store for caching
97+ providerStore :: Store
98+ } deriving (Show)
99+
100+
101+--------------------------------------------------------------------------------
102+-- | Create a resource provider
103+newProvider :: Store -- ^ Store to use
104+ -> (FilePath -> IO Bool) -- ^ Should we ignore this file?
105+ -> FilePath -- ^ Search directory
106+ -> IO Provider -- ^ Resulting provider
107+newProvider store ignore directory = do
108+ list <- map fromFilePath <$> getRecursiveContents ignore directory
109+ let universe = S.fromList list
110+ files <- fmap (maxmtime . M.fromList) $ forM list $ \identifier -> do
111+ rInfo <- getResourceInfo directory universe identifier
112+ return (identifier, rInfo)
113+
114+ -- Get the old files from the store, and then immediately replace them by
115+ -- the new files.
116+ oldFiles <- fromMaybe mempty . Store.toMaybe <$> Store.get store oldKey
117+ oldFiles `deepseq` Store.set store oldKey files
118+
119+ return $ Provider directory files oldFiles store
120+ where
121+ oldKey = ["Hakyll.Core.Provider.Internal.newProvider", "oldFiles"]
122+
123+ -- Update modified if metadata is modified
124+ maxmtime files = flip M.map files $ \rInfo@(ResourceInfo mtime meta) ->
125+ let metaMod = fmap resourceInfoModified $ meta >>= flip M.lookup files
126+ in rInfo {resourceInfoModified = maybe mtime (max mtime) metaMod}
127+
128+
129+--------------------------------------------------------------------------------
130+getResourceInfo :: FilePath -> Set Identifier -> Identifier -> IO ResourceInfo
131+getResourceInfo directory universe identifier = do
132+ mtime <- fileModificationTime $ directory </> toFilePath identifier
133+ return $ ResourceInfo (BinaryTime mtime) $
134+ if mdRsc `S.member` universe then Just mdRsc else Nothing
135+ where
136+ mdRsc = fromFilePath $ flip addExtension "metadata" $ toFilePath identifier
137+
138+
139+--------------------------------------------------------------------------------
140+resourceList :: Provider -> [Identifier]
141+resourceList = M.keys . providerFiles
142+
143+
144+--------------------------------------------------------------------------------
145+-- | Check if a given resource exists
146+resourceExists :: Provider -> Identifier -> Bool
147+resourceExists provider =
148+ (`M.member` providerFiles provider) . setVersion Nothing
149+
150+
151+--------------------------------------------------------------------------------
152+resourceFilePath :: Provider -> Identifier -> FilePath
153+resourceFilePath p i = providerDirectory p </> toFilePath i
154+
155+
156+--------------------------------------------------------------------------------
157+-- | Get the raw body of a resource as string
158+resourceString :: Provider -> Identifier -> IO String
159+resourceString p i = readFile $ resourceFilePath p i
160+
161+
162+--------------------------------------------------------------------------------
163+-- | Get the raw body of a resource of a lazy bytestring
164+resourceLBS :: Provider -> Identifier -> IO BL.ByteString
165+resourceLBS p i = BL.readFile $ resourceFilePath p i
166+
167+
168+--------------------------------------------------------------------------------
169+-- | A resource is modified if it or its metadata has changed
170+resourceModified :: Provider -> Identifier -> Bool
171+resourceModified p r = case (ri, oldRi) of
172+ (Nothing, _) -> False
173+ (Just _, Nothing) -> True
174+ (Just n, Just o) ->
175+ resourceInfoModified n > resourceInfoModified o ||
176+ resourceInfoMetadata n /= resourceInfoMetadata o
177+ where
178+ normal = setVersion Nothing r
179+ ri = M.lookup normal (providerFiles p)
180+ oldRi = M.lookup normal (providerOldFiles p)
181+
182+
183+--------------------------------------------------------------------------------
184+resourceModificationTime :: Provider -> Identifier -> UTCTime
185+resourceModificationTime p i =
186+ case M.lookup (setVersion Nothing i) (providerFiles p) of
187+ Just ri -> unBinaryTime $ resourceInfoModified ri
188+ Nothing -> error $
189+ "Hakyll.Core.Provider.Internal.resourceModificationTime: " ++
190+ "resource " ++ show i ++ " does not exist"
191+
192+
193+--------------------------------------------------------------------------------
194+fileModificationTime :: FilePath -> IO UTCTime
195+fileModificationTime fp = do
196+#if MIN_VERSION_directory(1,2,0)
197+ getModificationTime fp
198+#else
199+ ct <- toCalendarTime =<< getModificationTime fp
200+ let str = formatCalendarTime defaultTimeLocale "%s" ct
201+ return $ readTime defaultTimeLocale "%s" str
202+#endif
lib/Hakyll/Core/Provider/Metadata.hsnew file+152
@@ -0,0 +1,152 @@
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/Core/Provider/MetadataCache.hsnew file+62
@@ -0,0 +1,62 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Provider.MetadataCache
3+ ( resourceMetadata
4+ , resourceBody
5+ , resourceInvalidateMetadataCache
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Monad (unless)
11+import Hakyll.Core.Identifier
12+import Hakyll.Core.Metadata
13+import Hakyll.Core.Provider.Internal
14+import Hakyll.Core.Provider.Metadata
15+import qualified Hakyll.Core.Store as Store
16+
17+
18+--------------------------------------------------------------------------------
19+resourceMetadata :: Provider -> Identifier -> IO Metadata
20+resourceMetadata p r
21+ | not (resourceExists p r) = return mempty
22+ | otherwise = do
23+ -- TODO keep time in md cache
24+ load p r
25+ Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
26+ [name, toFilePath r, "metadata"]
27+ return md
28+
29+
30+--------------------------------------------------------------------------------
31+resourceBody :: Provider -> Identifier -> IO String
32+resourceBody p r = do
33+ load p r
34+ Store.Found bd <- Store.get (providerStore p)
35+ [name, toFilePath r, "body"]
36+ maybe (resourceString p r) return bd
37+
38+
39+--------------------------------------------------------------------------------
40+resourceInvalidateMetadataCache :: Provider -> Identifier -> IO ()
41+resourceInvalidateMetadataCache p r = do
42+ Store.delete (providerStore p) [name, toFilePath r, "metadata"]
43+ Store.delete (providerStore p) [name, toFilePath r, "body"]
44+
45+
46+--------------------------------------------------------------------------------
47+load :: Provider -> Identifier -> IO ()
48+load p r = do
49+ mmof <- Store.isMember store mdk
50+ unless mmof $ do
51+ (md, body) <- loadMetadata p r
52+ Store.set store mdk (BinaryMetadata md)
53+ Store.set store bk body
54+ where
55+ store = providerStore p
56+ mdk = [name, toFilePath r, "metadata"]
57+ bk = [name, toFilePath r, "body"]
58+
59+
60+--------------------------------------------------------------------------------
61+name :: String
62+name = "Hakyll.Core.Resource.Provider.MetadataCache"
lib/Hakyll/Core/Routes.hsnew file+211
@@ -0,0 +1,211 @@
1+--------------------------------------------------------------------------------
2+-- | Once a target is compiled, the user usually wants to save it to the disk.
3+-- This is where the 'Routes' type comes in; it determines where a certain
4+-- target should be written.
5+--
6+-- Suppose we have an item @foo\/bar.markdown@. We can render this to
7+-- @foo\/bar.html@ using:
8+--
9+-- > route "foo/bar.markdown" (setExtension ".html")
10+--
11+-- If we do not want to change the extension, we can use 'idRoute', the simplest
12+-- route available:
13+--
14+-- > route "foo/bar.markdown" idRoute
15+--
16+-- That will route @foo\/bar.markdown@ to @foo\/bar.markdown@.
17+--
18+-- Note that the extension says nothing about the content! If you set the
19+-- extension to @.html@, it is your own responsibility to ensure that the
20+-- content is indeed HTML.
21+--
22+-- Finally, some special cases:
23+--
24+-- * If there is no route for an item, this item will not be routed, so it will
25+-- not appear in your site directory.
26+--
27+-- * If an item matches multiple routes, the first rule will be chosen.
28+{-# LANGUAGE CPP #-}
29+{-# LANGUAGE Rank2Types #-}
30+module Hakyll.Core.Routes
31+ ( UsedMetadata
32+ , Routes
33+ , runRoutes
34+ , idRoute
35+ , setExtension
36+ , matchRoute
37+ , customRoute
38+ , constRoute
39+ , gsubRoute
40+ , metadataRoute
41+ , composeRoutes
42+ ) where
43+
44+
45+--------------------------------------------------------------------------------
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
49+import System.FilePath (replaceExtension)
50+
51+
52+--------------------------------------------------------------------------------
53+import Hakyll.Core.Identifier
54+import Hakyll.Core.Identifier.Pattern
55+import Hakyll.Core.Metadata
56+import Hakyll.Core.Provider
57+import Hakyll.Core.Util.String
58+
59+
60+--------------------------------------------------------------------------------
61+-- | When you ran a route, it's useful to know whether or not this used
62+-- metadata. This allows us to do more granular dependency analysis.
63+type UsedMetadata = Bool
64+
65+
66+--------------------------------------------------------------------------------
67+data RoutesRead = RoutesRead
68+ { routesProvider :: Provider
69+ , routesUnderlying :: Identifier
70+ }
71+
72+
73+--------------------------------------------------------------------------------
74+-- | Type used for a route
75+newtype Routes = Routes
76+ { unRoutes :: RoutesRead -> Identifier -> IO (Maybe FilePath, UsedMetadata)
77+ }
78+
79+
80+--------------------------------------------------------------------------------
81+#if MIN_VERSION_base(4,9,0)
82+instance Semigroup Routes where
83+ (<>) (Routes f) (Routes g) = Routes $ \p id' -> do
84+ (mfp, um) <- f p id'
85+ case mfp of
86+ Nothing -> g p id'
87+ Just _ -> return (mfp, um)
88+
89+instance Monoid Routes where
90+ mempty = Routes $ \_ _ -> return (Nothing, False)
91+ mappend = (<>)
92+#else
93+instance Monoid Routes where
94+ mempty = Routes $ \_ _ -> return (Nothing, False)
95+ mappend (Routes f) (Routes g) = Routes $ \p id' -> do
96+ (mfp, um) <- f p id'
97+ case mfp of
98+ Nothing -> g p id'
99+ Just _ -> return (mfp, um)
100+#endif
101+
102+
103+--------------------------------------------------------------------------------
104+-- | Apply a route to an identifier
105+runRoutes :: Routes -> Provider -> Identifier
106+ -> IO (Maybe FilePath, UsedMetadata)
107+runRoutes routes provider identifier =
108+ unRoutes routes (RoutesRead provider identifier) identifier
109+
110+
111+--------------------------------------------------------------------------------
112+-- | A route that uses the identifier as filepath. For example, the target with
113+-- ID @foo\/bar@ will be written to the file @foo\/bar@.
114+idRoute :: Routes
115+idRoute = customRoute toFilePath
116+
117+
118+--------------------------------------------------------------------------------
119+-- | Set (or replace) the extension of a route.
120+--
121+-- Example:
122+--
123+-- > runRoutes (setExtension "html") "foo/bar"
124+--
125+-- Result:
126+--
127+-- > Just "foo/bar.html"
128+--
129+-- Example:
130+--
131+-- > runRoutes (setExtension "html") "posts/the-art-of-trolling.markdown"
132+--
133+-- Result:
134+--
135+-- > Just "posts/the-art-of-trolling.html"
136+setExtension :: String -> Routes
137+setExtension extension = customRoute $
138+ (`replaceExtension` extension) . toFilePath
139+
140+
141+--------------------------------------------------------------------------------
142+-- | Apply the route if the identifier matches the given pattern, fail
143+-- otherwise
144+matchRoute :: Pattern -> Routes -> Routes
145+matchRoute pattern (Routes route) = Routes $ \p id' ->
146+ if matches pattern id' then route p id' else return (Nothing, False)
147+
148+
149+--------------------------------------------------------------------------------
150+-- | Create a custom route. This should almost always be used with
151+-- 'matchRoute'
152+customRoute :: (Identifier -> FilePath) -> Routes
153+customRoute f = Routes $ const $ \id' -> return (Just (f id'), False)
154+
155+
156+--------------------------------------------------------------------------------
157+-- | A route that always gives the same result. Obviously, you should only use
158+-- this for a single compilation rule.
159+constRoute :: FilePath -> Routes
160+constRoute = customRoute . const
161+
162+
163+--------------------------------------------------------------------------------
164+-- | Create a gsub route
165+--
166+-- Example:
167+--
168+-- > runRoutes (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
169+--
170+-- Result:
171+--
172+-- > Just "tags/bar.xml"
173+gsubRoute :: String -- ^ Pattern
174+ -> (String -> String) -- ^ Replacement
175+ -> Routes -- ^ Resulting route
176+gsubRoute pattern replacement = customRoute $
177+ replaceAll pattern replacement . toFilePath
178+
179+
180+--------------------------------------------------------------------------------
181+-- | Get access to the metadata in order to determine the route
182+metadataRoute :: (Metadata -> Routes) -> Routes
183+metadataRoute f = Routes $ \r i -> do
184+ metadata <- resourceMetadata (routesProvider r) (routesUnderlying r)
185+ unRoutes (f metadata) r i
186+
187+
188+--------------------------------------------------------------------------------
189+-- | Compose routes so that @f \`composeRoutes\` g@ is more or less equivalent
190+-- with @g . f@.
191+--
192+-- Example:
193+--
194+-- > let routes = gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml"
195+-- > in runRoutes routes "tags/rss/bar"
196+--
197+-- Result:
198+--
199+-- > Just "tags/bar.xml"
200+--
201+-- If the first route given fails, Hakyll will not apply the second route.
202+composeRoutes :: Routes -- ^ First route to apply
203+ -> Routes -- ^ Second route to apply
204+ -> Routes -- ^ Resulting route
205+composeRoutes (Routes f) (Routes g) = Routes $ \p i -> do
206+ (mfp, um) <- f p i
207+ case mfp of
208+ Nothing -> return (Nothing, um)
209+ Just fp -> do
210+ (mfp', um') <- g p (fromFilePath fp)
211+ return (mfp', um || um')
lib/Hakyll/Core/Rules.hsnew file+223
@@ -0,0 +1,223 @@
1+--------------------------------------------------------------------------------
2+-- | This module provides a declarative DSL in which the user can specify the
3+-- different rules used to run the compilers.
4+--
5+-- The convention is to just list all items in the 'Rules' monad, routes and
6+-- compilation rules.
7+--
8+-- A typical usage example would be:
9+--
10+-- > main = hakyll $ do
11+-- > match "posts/*" $ do
12+-- > route (setExtension "html")
13+-- > compile someCompiler
14+-- > match "css/*" $ do
15+-- > route idRoute
16+-- > compile compressCssCompiler
17+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
18+{-# LANGUAGE OverloadedStrings #-}
19+module Hakyll.Core.Rules
20+ ( Rules
21+ , match
22+ , matchMetadata
23+ , create
24+ , version
25+ , compile
26+ , route
27+
28+ -- * Advanced usage
29+ , preprocess
30+ , Dependency (..)
31+ , rulesExtraDependencies
32+ ) where
33+
34+
35+--------------------------------------------------------------------------------
36+import Control.Monad.Reader (ask, local)
37+import Control.Monad.State (get, modify, put)
38+import Control.Monad.Trans (liftIO)
39+import Control.Monad.Writer (censor, tell)
40+import Data.Maybe (fromMaybe)
41+import qualified Data.Set as S
42+
43+
44+--------------------------------------------------------------------------------
45+import Data.Binary (Binary)
46+import Data.Typeable (Typeable)
47+
48+
49+--------------------------------------------------------------------------------
50+import Hakyll.Core.Compiler.Internal
51+import Hakyll.Core.Dependencies
52+import Hakyll.Core.Identifier
53+import Hakyll.Core.Identifier.Pattern
54+import Hakyll.Core.Item
55+import Hakyll.Core.Item.SomeItem
56+import Hakyll.Core.Metadata
57+import Hakyll.Core.Routes
58+import Hakyll.Core.Rules.Internal
59+import Hakyll.Core.Writable
60+
61+
62+--------------------------------------------------------------------------------
63+-- | Add a route
64+tellRoute :: Routes -> Rules ()
65+tellRoute route' = Rules $ tell $ RuleSet route' mempty mempty mempty
66+
67+
68+--------------------------------------------------------------------------------
69+-- | Add a number of compilers
70+tellCompilers :: [(Identifier, Compiler SomeItem)] -> Rules ()
71+tellCompilers compilers = Rules $ tell $ RuleSet mempty compilers mempty mempty
72+
73+
74+--------------------------------------------------------------------------------
75+-- | Add resources
76+tellResources :: [Identifier] -> Rules ()
77+tellResources resources' = Rules $ tell $
78+ RuleSet mempty mempty (S.fromList resources') mempty
79+
80+
81+--------------------------------------------------------------------------------
82+-- | Add a pattern
83+tellPattern :: Pattern -> Rules ()
84+tellPattern pattern = Rules $ tell $ RuleSet mempty mempty mempty pattern
85+
86+
87+--------------------------------------------------------------------------------
88+flush :: Rules ()
89+flush = Rules $ do
90+ mcompiler <- rulesCompiler <$> get
91+ case mcompiler of
92+ Nothing -> return ()
93+ Just compiler -> do
94+ matches' <- rulesMatches <$> ask
95+ version' <- rulesVersion <$> ask
96+ route' <- fromMaybe mempty . rulesRoute <$> get
97+
98+ -- The version is possibly not set correctly at this point (yet)
99+ let ids = map (setVersion version') matches'
100+
101+ {-
102+ ids <- case fromLiteral pattern of
103+ Just id' -> return [setVersion version' id']
104+ Nothing -> do
105+ ids <- unRules $ getMatches pattern
106+ unRules $ tellResources ids
107+ return $ map (setVersion version') ids
108+ -}
109+
110+ -- Create a fast pattern for routing that matches exactly the
111+ -- compilers created in the block given to match
112+ let fastPattern = fromList ids
113+
114+ -- Write out the compilers and routes
115+ unRules $ tellRoute $ matchRoute fastPattern route'
116+ unRules $ tellCompilers $ [(id', compiler) | id' <- ids]
117+
118+ put $ emptyRulesState
119+
120+
121+--------------------------------------------------------------------------------
122+matchInternal :: Pattern -> Rules [Identifier] -> Rules () -> Rules ()
123+matchInternal pattern getIDs rules = do
124+ tellPattern pattern
125+ flush
126+ ids <- getIDs
127+ tellResources ids
128+ Rules $ local (setMatches ids) $ unRules $ rules >> flush
129+ where
130+ setMatches ids env = env {rulesMatches = ids}
131+
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+
142+
143+--------------------------------------------------------------------------------
144+create :: [Identifier] -> Rules () -> Rules ()
145+create ids rules = do
146+ flush
147+ -- TODO Maybe check if the resources exist and call tellResources on that
148+ Rules $ local setMatches $ unRules $ rules >> flush
149+ where
150+ setMatches env = env {rulesMatches = ids}
151+
152+
153+--------------------------------------------------------------------------------
154+version :: String -> Rules () -> Rules ()
155+version v rules = do
156+ flush
157+ Rules $ local setVersion' $ unRules $ rules >> flush
158+ where
159+ setVersion' env = env {rulesVersion = Just v}
160+
161+
162+--------------------------------------------------------------------------------
163+-- | Add a compilation rule to the rules.
164+--
165+-- This instructs all resources to be compiled using the given compiler.
166+compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -> Rules ()
167+compile compiler = Rules $ modify $ \s ->
168+ s {rulesCompiler = Just (fmap SomeItem compiler)}
169+
170+
171+--------------------------------------------------------------------------------
172+-- | Add a route.
173+--
174+-- This adds a route for all items matching the current pattern.
175+route :: Routes -> Rules ()
176+route route' = Rules $ modify $ \s -> s {rulesRoute = Just route'}
177+
178+
179+--------------------------------------------------------------------------------
180+-- | Execute an 'IO' action immediately while the rules are being evaluated.
181+-- This should be avoided if possible, but occasionally comes in useful.
182+preprocess :: IO a -> Rules a
183+preprocess = Rules . liftIO
184+
185+
186+--------------------------------------------------------------------------------
187+-- | Advanced usage: add extra dependencies to compilers. Basically this is
188+-- needed when you're doing unsafe tricky stuff in the rules monad, but you
189+-- still want correct builds.
190+--
191+-- A useful utility for this purpose is 'makePatternDependency'.
192+rulesExtraDependencies :: [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
209+ 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
219+ { rulesCompilers =
220+ [ (i, compilerTellDependencies deps >> c)
221+ | (i, c) <- rulesCompilers ruleSet
222+ ]
223+ }
lib/Hakyll/Core/Rules/Internal.hsnew file+123
@@ -0,0 +1,123 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE Rank2Types #-}
5+module Hakyll.Core.Rules.Internal
6+ ( RulesRead (..)
7+ , RuleSet (..)
8+ , RulesState (..)
9+ , emptyRulesState
10+ , Rules (..)
11+ , runRules
12+ ) where
13+
14+
15+--------------------------------------------------------------------------------
16+import Control.Monad.Reader (ask)
17+import Control.Monad.RWS (RWST, runRWST)
18+import Control.Monad.Trans (liftIO)
19+import qualified Data.Map as M
20+#if MIN_VERSION_base(4,9,0)
21+import Data.Semigroup (Semigroup (..))
22+#endif
23+import Data.Set (Set)
24+
25+
26+--------------------------------------------------------------------------------
27+import Hakyll.Core.Compiler.Internal
28+import Hakyll.Core.Identifier
29+import Hakyll.Core.Identifier.Pattern
30+import Hakyll.Core.Item.SomeItem
31+import Hakyll.Core.Metadata
32+import Hakyll.Core.Provider
33+import Hakyll.Core.Routes
34+
35+
36+--------------------------------------------------------------------------------
37+data RulesRead = RulesRead
38+ { rulesProvider :: Provider
39+ , rulesMatches :: [Identifier]
40+ , rulesVersion :: Maybe String
41+ }
42+
43+
44+--------------------------------------------------------------------------------
45+data RuleSet = RuleSet
46+ { -- | Accumulated routes
47+ rulesRoutes :: Routes
48+ , -- | Accumulated compilers
49+ rulesCompilers :: [(Identifier, Compiler SomeItem)]
50+ , -- | A set of the actually used files
51+ rulesResources :: Set Identifier
52+ , -- | A pattern we can use to check if a file *would* be used. This is
53+ -- needed for the preview server.
54+ rulesPattern :: Pattern
55+ }
56+
57+
58+--------------------------------------------------------------------------------
59+#if MIN_VERSION_base(4,9,0)
60+instance Semigroup RuleSet where
61+ (<>) (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
62+ RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
63+
64+instance Monoid RuleSet where
65+ mempty = RuleSet mempty mempty mempty mempty
66+ mappend = (<>)
67+#else
68+instance Monoid RuleSet where
69+ mempty = RuleSet mempty mempty mempty mempty
70+ mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
71+ RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72+#endif
73+
74+
75+--------------------------------------------------------------------------------
76+data RulesState = RulesState
77+ { rulesRoute :: Maybe Routes
78+ , rulesCompiler :: Maybe (Compiler SomeItem)
79+ }
80+
81+
82+--------------------------------------------------------------------------------
83+emptyRulesState :: RulesState
84+emptyRulesState = RulesState Nothing Nothing
85+
86+
87+--------------------------------------------------------------------------------
88+-- | The monad used to compose rules
89+newtype Rules a = Rules
90+ { unRules :: RWST RulesRead RuleSet RulesState IO a
91+ } deriving (Monad, Functor, Applicative)
92+
93+
94+--------------------------------------------------------------------------------
95+instance MonadMetadata Rules where
96+ getMetadata identifier = Rules $ do
97+ provider <- rulesProvider <$> ask
98+ liftIO $ resourceMetadata provider identifier
99+
100+ getMatches pattern = Rules $ do
101+ provider <- rulesProvider <$> ask
102+ return $ filterMatches pattern $ resourceList provider
103+
104+
105+--------------------------------------------------------------------------------
106+-- | Run a Rules monad, resulting in a 'RuleSet'
107+runRules :: Rules a -> Provider -> IO RuleSet
108+runRules rules provider = do
109+ (_, _, ruleSet) <- runRWST (unRules rules) env emptyRulesState
110+
111+ -- Ensure compiler uniqueness
112+ let ruleSet' = ruleSet
113+ { rulesCompilers = M.toList $
114+ M.fromListWith (flip const) (rulesCompilers ruleSet)
115+ }
116+
117+ return ruleSet'
118+ where
119+ env = RulesRead
120+ { rulesProvider = provider
121+ , rulesMatches = []
122+ , rulesVersion = Nothing
123+ }
lib/Hakyll/Core/Runtime.hsnew file+276
@@ -0,0 +1,276 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Runtime
3+ ( run
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
8+import Control.Monad (unless)
9+import Control.Monad.Except (ExceptT, runExceptT, throwError)
10+import Control.Monad.Reader (ask)
11+import Control.Monad.RWS (RWST, runRWST)
12+import Control.Monad.State (get, modify)
13+import Control.Monad.Trans (liftIO)
14+import Data.List (intercalate)
15+import Data.Map (Map)
16+import qualified Data.Map as M
17+import Data.Set (Set)
18+import qualified Data.Set as S
19+import System.Exit (ExitCode (..))
20+import System.FilePath ((</>))
21+
22+
23+--------------------------------------------------------------------------------
24+import Hakyll.Core.Compiler.Internal
25+import Hakyll.Core.Compiler.Require
26+import Hakyll.Core.Configuration
27+import Hakyll.Core.Dependencies
28+import Hakyll.Core.Identifier
29+import Hakyll.Core.Item
30+import Hakyll.Core.Item.SomeItem
31+import Hakyll.Core.Logger (Logger)
32+import qualified Hakyll.Core.Logger as Logger
33+import Hakyll.Core.Provider
34+import Hakyll.Core.Routes
35+import Hakyll.Core.Rules.Internal
36+import Hakyll.Core.Store (Store)
37+import qualified Hakyll.Core.Store as Store
38+import Hakyll.Core.Util.File
39+import Hakyll.Core.Writable
40+
41+
42+--------------------------------------------------------------------------------
43+run :: Configuration -> Logger -> Rules a -> IO (ExitCode, RuleSet)
44+run config logger rules = do
45+ -- Initialization
46+ Logger.header logger "Initialising..."
47+ Logger.message logger "Creating store..."
48+ store <- Store.new (inMemoryCache config) $ storeDirectory config
49+ Logger.message logger "Creating provider..."
50+ provider <- newProvider store (shouldIgnoreFile config) $
51+ providerDirectory config
52+ Logger.message logger "Running rules..."
53+ ruleSet <- runRules rules provider
54+
55+ -- Get old facts
56+ mOldFacts <- Store.get store factsKey
57+ let (oldFacts) = case mOldFacts of Store.Found f -> f
58+ _ -> mempty
59+
60+ -- Build runtime read/state
61+ let compilers = rulesCompilers ruleSet
62+ read' = RuntimeRead
63+ { runtimeConfiguration = config
64+ , runtimeLogger = logger
65+ , runtimeProvider = provider
66+ , runtimeStore = store
67+ , runtimeRoutes = rulesRoutes ruleSet
68+ , runtimeUniverse = M.fromList compilers
69+ }
70+ state = RuntimeState
71+ { runtimeDone = S.empty
72+ , runtimeSnapshots = S.empty
73+ , runtimeTodo = M.empty
74+ , runtimeFacts = oldFacts
75+ }
76+
77+ -- Run the program and fetch the resulting state
78+ result <- runExceptT $ runRWST build read' state
79+ case result of
80+ Left e -> do
81+ Logger.error logger e
82+ Logger.flush logger
83+ return (ExitFailure 1, ruleSet)
84+
85+ Right (_, s, _) -> do
86+ Store.set store factsKey $ runtimeFacts s
87+
88+ Logger.debug logger "Removing tmp directory..."
89+ removeDirectory $ tmpDirectory config
90+
91+ Logger.flush logger
92+ return (ExitSuccess, ruleSet)
93+ where
94+ factsKey = ["Hakyll.Core.Runtime.run", "facts"]
95+
96+
97+--------------------------------------------------------------------------------
98+data RuntimeRead = RuntimeRead
99+ { runtimeConfiguration :: Configuration
100+ , runtimeLogger :: Logger
101+ , runtimeProvider :: Provider
102+ , runtimeStore :: Store
103+ , runtimeRoutes :: Routes
104+ , runtimeUniverse :: Map Identifier (Compiler SomeItem)
105+ }
106+
107+
108+--------------------------------------------------------------------------------
109+data RuntimeState = RuntimeState
110+ { runtimeDone :: Set Identifier
111+ , runtimeSnapshots :: Set (Identifier, Snapshot)
112+ , runtimeTodo :: Map Identifier (Compiler SomeItem)
113+ , runtimeFacts :: DependencyFacts
114+ }
115+
116+
117+--------------------------------------------------------------------------------
118+type Runtime a = RWST RuntimeRead () RuntimeState (ExceptT String IO) a
119+
120+
121+--------------------------------------------------------------------------------
122+build :: Runtime ()
123+build = do
124+ logger <- runtimeLogger <$> ask
125+ Logger.header logger "Checking for out-of-date items"
126+ scheduleOutOfDate
127+ Logger.header logger "Compiling"
128+ pickAndChase
129+ Logger.header logger "Success"
130+
131+
132+--------------------------------------------------------------------------------
133+scheduleOutOfDate :: Runtime ()
134+scheduleOutOfDate = do
135+ logger <- runtimeLogger <$> ask
136+ provider <- runtimeProvider <$> ask
137+ universe <- runtimeUniverse <$> ask
138+ facts <- runtimeFacts <$> get
139+ todo <- runtimeTodo <$> get
140+
141+ let identifiers = M.keys universe
142+ modified = S.fromList $ flip filter identifiers $
143+ resourceModified provider
144+
145+ let (ood, facts', msgs) = outOfDate identifiers modified facts
146+ todo' = M.filterWithKey
147+ (\id' _ -> id' `S.member` ood) universe
148+
149+ -- Print messages
150+ mapM_ (Logger.debug logger) msgs
151+
152+ -- Update facts and todo items
153+ modify $ \s -> s
154+ { runtimeDone = runtimeDone s `S.union`
155+ (S.fromList identifiers `S.difference` ood)
156+ , runtimeTodo = todo `M.union` todo'
157+ , runtimeFacts = facts'
158+ }
159+
160+
161+--------------------------------------------------------------------------------
162+pickAndChase :: Runtime ()
163+pickAndChase = do
164+ todo <- runtimeTodo <$> get
165+ case M.minViewWithKey todo of
166+ Nothing -> return ()
167+ Just ((id', _), _) -> do
168+ chase [] id'
169+ pickAndChase
170+
171+
172+--------------------------------------------------------------------------------
173+chase :: [Identifier] -> Identifier -> Runtime ()
174+chase trail id'
175+ | id' `elem` trail = throwError $ "Hakyll.Core.Runtime.chase: " ++
176+ "Dependency cycle detected: " ++ intercalate " depends on "
177+ (map show $ dropWhile (/= id') (reverse trail) ++ [id'])
178+ | otherwise = do
179+ logger <- runtimeLogger <$> ask
180+ todo <- runtimeTodo <$> get
181+ provider <- runtimeProvider <$> ask
182+ universe <- runtimeUniverse <$> ask
183+ routes <- runtimeRoutes <$> ask
184+ store <- runtimeStore <$> ask
185+ config <- runtimeConfiguration <$> ask
186+ Logger.debug logger $ "Processing " ++ show id'
187+
188+ let compiler = todo M.! id'
189+ read' = CompilerRead
190+ { compilerConfig = config
191+ , compilerUnderlying = id'
192+ , compilerProvider = provider
193+ , compilerUniverse = M.keysSet universe
194+ , compilerRoutes = routes
195+ , compilerStore = store
196+ , compilerLogger = logger
197+ }
198+
199+ result <- liftIO $ runCompiler compiler read'
200+ case result of
201+ -- 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+ }
215+
216+ -- Huge success
217+ CompilerDone (SomeItem item) cwrite -> do
218+ -- Print some info
219+ let facts = compilerDependencies cwrite
220+ cacheHits
221+ | compilerCacheHits cwrite <= 0 = "updated"
222+ | otherwise = "cached "
223+ Logger.message logger $ cacheHits ++ " " ++ show id'
224+
225+ -- Sanity check
226+ unless (itemIdentifier item == id') $ throwError $
227+ "The compiler yielded an Item with Identifier " ++
228+ show (itemIdentifier item) ++ ", but we were expecting " ++
229+ "an Item with Identifier " ++ show id' ++ " " ++
230+ "(you probably want to call makeItem to solve this problem)"
231+
232+ -- Write if necessary
233+ (mroute, _) <- liftIO $ runRoutes routes provider id'
234+ case mroute of
235+ Nothing -> return ()
236+ Just route -> do
237+ let path = destinationDirectory config </> route
238+ liftIO $ makeDirectories path
239+ liftIO $ write path item
240+ Logger.debug logger $ "Routed to " ++ path
241+
242+ -- Save! (For load)
243+ liftIO $ save store item
244+
245+ -- Update state
246+ modify $ \s -> s
247+ { runtimeDone = S.insert id' (runtimeDone s)
248+ , runtimeTodo = M.delete id' (runtimeTodo s)
249+ , runtimeFacts = M.insert id' facts (runtimeFacts s)
250+ }
251+
252+ -- Try something else first
253+ CompilerRequire dep c -> do
254+ -- 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+
265+ modify $ \s -> s
266+ { runtimeTodo = M.insert id'
267+ (if depDone then c else compilerResult result)
268+ (runtimeTodo s)
269+ }
270+
271+ -- If the required item is already compiled, continue, or, start
272+ -- chasing that
273+ Logger.debug logger $ "Require " ++ show depId ++
274+ " (snapshot " ++ depSnapshot ++ "): " ++
275+ (if depDone then "OK" else "chasing")
276+ if depDone then chase trail id' else chase (id' : trail) depId
lib/Hakyll/Core/Store.hsnew file+200
@@ -0,0 +1,200 @@
1+--------------------------------------------------------------------------------
2+-- | A store for storing and retreiving items
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+module Hakyll.Core.Store
6+ ( Store
7+ , Result (..)
8+ , toMaybe
9+ , new
10+ , set
11+ , get
12+ , isMember
13+ , delete
14+ , hash
15+ ) where
16+
17+
18+--------------------------------------------------------------------------------
19+import qualified Crypto.Hash.MD5 as MD5
20+import Data.Binary (Binary, decode, encodeFile)
21+import qualified Data.ByteString as B
22+import qualified Data.ByteString.Lazy as BL
23+import qualified Data.Cache.LRU.IO as Lru
24+import Data.List (intercalate)
25+import Data.Maybe (isJust)
26+import qualified Data.Text as T
27+import qualified Data.Text.Encoding as T
28+import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29+import Numeric (showHex)
30+import System.Directory (createDirectoryIfMissing)
31+import System.Directory (doesFileExist, removeFile)
32+import System.FilePath ((</>))
33+import System.IO (IOMode (..), hClose, openFile)
34+import System.IO.Error (catchIOError, ioeSetFileName,
35+ ioeSetLocation, modifyIOError)
36+
37+
38+--------------------------------------------------------------------------------
39+-- | Simple wrapper type
40+data Box = forall a. Typeable a => Box a
41+
42+
43+--------------------------------------------------------------------------------
44+data Store = Store
45+ { -- | All items are stored on the filesystem
46+ storeDirectory :: FilePath
47+ , -- | Optionally, items are also kept in-memory
48+ storeMap :: Maybe (Lru.AtomicLRU FilePath Box)
49+ }
50+
51+
52+--------------------------------------------------------------------------------
53+instance Show Store where
54+ show _ = "<Store>"
55+
56+
57+--------------------------------------------------------------------------------
58+-- | Result of a store query
59+data Result a
60+ = Found a -- ^ Found, result
61+ | NotFound -- ^ Not found
62+ | WrongType TypeRep TypeRep -- ^ Expected, true type
63+ deriving (Show, Eq)
64+
65+
66+--------------------------------------------------------------------------------
67+-- | Convert result to 'Maybe'
68+toMaybe :: Result a -> Maybe a
69+toMaybe (Found x) = Just x
70+toMaybe _ = Nothing
71+
72+
73+--------------------------------------------------------------------------------
74+-- | Initialize the store
75+new :: Bool -- ^ Use in-memory caching
76+ -> FilePath -- ^ Directory to use for hard disk storage
77+ -> IO Store -- ^ Store
78+new inMemory directory = do
79+ createDirectoryIfMissing True directory
80+ ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing
81+ return Store
82+ { storeDirectory = directory
83+ , storeMap = ref
84+ }
85+ where
86+ csize = Just 500
87+
88+--------------------------------------------------------------------------------
89+withStore :: Store -> String -> (String -> FilePath -> IO a) -> [String] -> IO a
90+withStore store loc run identifier = modifyIOError handle $ run key path
91+ where
92+ key = hash identifier
93+ path = storeDirectory store </> key
94+ handle e = e `ioeSetFileName` (path ++ " for " ++ intercalate "/" identifier)
95+ `ioeSetLocation` ("Store." ++ loc)
96+
97+--------------------------------------------------------------------------------
98+-- | Auxiliary: add an item to the in-memory cache
99+cacheInsert :: Typeable a => Store -> String -> a -> IO ()
100+cacheInsert (Store _ Nothing) _ _ = return ()
101+cacheInsert (Store _ (Just lru)) key x =
102+ Lru.insert key (Box x) lru
103+
104+
105+--------------------------------------------------------------------------------
106+-- | Auxiliary: get an item from the in-memory cache
107+cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)
108+cacheLookup (Store _ Nothing) _ = return NotFound
109+cacheLookup (Store _ (Just lru)) key = do
110+ res <- Lru.lookup key lru
111+ return $ case res of
112+ Nothing -> NotFound
113+ Just (Box x) -> case cast x of
114+ Just x' -> Found x'
115+ Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
116+
117+
118+--------------------------------------------------------------------------------
119+cacheIsMember :: Store -> String -> IO Bool
120+cacheIsMember (Store _ Nothing) _ = return False
121+cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru
122+
123+
124+--------------------------------------------------------------------------------
125+-- | Auxiliary: delete an item from the in-memory cache
126+cacheDelete :: Store -> String -> IO ()
127+cacheDelete (Store _ Nothing) _ = return ()
128+cacheDelete (Store _ (Just lru)) key = do
129+ _ <- Lru.delete key lru
130+ return ()
131+
132+
133+--------------------------------------------------------------------------------
134+-- | Store an item
135+set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
136+set store identifier value = withStore store "set" (\key path -> do
137+ encodeFile path value
138+ cacheInsert store key value
139+ ) identifier
140+
141+
142+--------------------------------------------------------------------------------
143+-- | Load an item
144+get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
145+get store = withStore store "get" $ \key path -> do
146+ -- First check the in-memory map
147+ ref <- cacheLookup store key
148+ case ref of
149+ -- Not found in the map, try the filesystem
150+ NotFound -> do
151+ exists <- doesFileExist path
152+ if not exists
153+ -- Not found in the filesystem either
154+ then return NotFound
155+ -- Found in the filesystem
156+ else do
157+ v <- decodeClose path
158+ cacheInsert store key v
159+ return $ Found v
160+ -- Found in the in-memory map (or wrong type), just return
161+ s -> return s
162+ where
163+ -- 'decodeFile' from Data.Binary which closes the file ASAP
164+ decodeClose path = do
165+ h <- openFile path ReadMode
166+ lbs <- BL.hGetContents h
167+ BL.length lbs `seq` hClose h
168+ return $ decode lbs
169+
170+
171+--------------------------------------------------------------------------------
172+-- | Strict function
173+isMember :: Store -> [String] -> IO Bool
174+isMember store = withStore store "isMember" $ \key path -> do
175+ inCache <- cacheIsMember store key
176+ if inCache then return True else doesFileExist path
177+
178+
179+--------------------------------------------------------------------------------
180+-- | Delete an item
181+delete :: Store -> [String] -> IO ()
182+delete store = withStore store "delete" $ \key path -> do
183+ cacheDelete store key
184+ deleteFile path
185+
186+
187+--------------------------------------------------------------------------------
188+-- | Delete a file unless it doesn't exist...
189+deleteFile :: FilePath -> IO ()
190+deleteFile = (`catchIOError` \_ -> return ()) . removeFile
191+
192+
193+--------------------------------------------------------------------------------
194+-- | Mostly meant for internal usage
195+hash :: [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)
lib/Hakyll/Core/UnixFilter.hsnew file+160
@@ -0,0 +1,160 @@
1+{-# LANGUAGE CPP #-}
2+
3+--------------------------------------------------------------------------------
4+-- | A Compiler that supports unix filters.
5+module Hakyll.Core.UnixFilter
6+ ( unixFilter
7+ , unixFilterLBS
8+ ) where
9+
10+
11+--------------------------------------------------------------------------------
12+import Control.Concurrent (forkIO)
13+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
14+import Control.DeepSeq (deepseq)
15+import Control.Monad (forM_)
16+import Data.ByteString.Lazy (ByteString)
17+import qualified Data.ByteString.Lazy as LB
18+import Data.IORef (newIORef, readIORef, writeIORef)
19+import System.Exit (ExitCode (..))
20+import System.IO (Handle, hClose, hFlush, hGetContents,
21+ hPutStr, hSetEncoding, localeEncoding)
22+import System.Process
23+
24+--------------------------------------------------------------------------------
25+import Hakyll.Core.Compiler
26+
27+
28+--------------------------------------------------------------------------------
29+-- | Use a unix filter as compiler. For example, we could use the 'rev' program
30+-- as a compiler.
31+--
32+-- > rev :: Compiler (Item String)
33+-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
34+--
35+-- A more realistic example: one can use this to call, for example, the sass
36+-- compiler on CSS files. More information about sass can be found here:
37+--
38+-- <http://sass-lang.com/>
39+--
40+-- The code is fairly straightforward, given that we use @.scss@ for sass:
41+--
42+-- > match "style.scss" $ do
43+-- > route $ setExtension "css"
44+-- > compile $ getResourceString >>=
45+-- > withItemBody (unixFilter "sass" ["-s", "--scss"]) >>=
46+-- > return . fmap compressCss
47+unixFilter :: String -- ^ Program name
48+ -> [String] -- ^ Program args
49+ -> String -- ^ Program input
50+ -> Compiler String -- ^ Program output
51+unixFilter = unixFilterWith writer reader
52+ where
53+ writer handle input = do
54+ hSetEncoding handle localeEncoding
55+ hPutStr handle input
56+ reader handle = do
57+ hSetEncoding handle localeEncoding
58+ out <- hGetContents handle
59+ deepseq out (return out)
60+
61+
62+--------------------------------------------------------------------------------
63+-- | Variant of 'unixFilter' that should be used for binary files
64+--
65+-- > match "music.wav" $ do
66+-- > route $ setExtension "ogg"
67+-- > compile $ getResourceLBS >>= withItemBody (unixFilterLBS "oggenc" ["-"])
68+unixFilterLBS :: String -- ^ Program name
69+ -> [String] -- ^ Program args
70+ -> ByteString -- ^ Program input
71+ -> Compiler ByteString -- ^ Program output
72+unixFilterLBS = unixFilterWith LB.hPutStr $ \handle -> do
73+ out <- LB.hGetContents handle
74+ LB.length out `seq` return out
75+
76+
77+--------------------------------------------------------------------------------
78+-- | Overloaded compiler
79+unixFilterWith :: Monoid o
80+ => (Handle -> i -> IO ()) -- ^ Writer
81+ -> (Handle -> IO o) -- ^ Reader
82+ -> String -- ^ Program name
83+ -> [String] -- ^ Program args
84+ -> i -- ^ Program input
85+ -> Compiler o -- ^ Program output
86+unixFilterWith writer reader programName args input = do
87+ debugCompiler ("Executing external program " ++ programName)
88+ (output, err, exitCode) <- unsafeCompiler $
89+ unixFilterIO writer reader programName args input
90+ forM_ (lines err) debugCompiler
91+ case exitCode of
92+ ExitSuccess -> return output
93+ ExitFailure e -> fail $
94+ "Hakyll.Core.UnixFilter.unixFilterWith: " ++
95+ unwords (programName : args) ++ " gave exit code " ++ show e ++
96+ ". (Error: " ++ err ++ ")"
97+
98+
99+--------------------------------------------------------------------------------
100+-- | Internally used function
101+unixFilterIO :: Monoid o
102+ => (Handle -> i -> IO ())
103+ -> (Handle -> IO o)
104+ -> String
105+ -> [String]
106+ -> i
107+ -> IO (o, String, ExitCode)
108+unixFilterIO 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+
123+ (Just inh, Just outh, Just errh, pid) <-
124+ createProcess pr
125+ { std_in = CreatePipe
126+ , std_out = CreatePipe
127+ , std_err = CreatePipe
128+ }
129+
130+ -- Create boxes
131+ lock <- newEmptyMVar
132+ outRef <- newIORef mempty
133+ errRef <- newIORef ""
134+
135+ -- Write the input to the child pipe
136+ _ <- forkIO $ writer inh input >> hFlush inh >> hClose inh
137+
138+ -- Read from stdout
139+ _ <- forkIO $ do
140+ out <- reader outh
141+ hClose outh
142+ writeIORef outRef out
143+ putMVar lock ()
144+
145+ -- Read from stderr
146+ _ <- forkIO $ do
147+ hSetEncoding errh localeEncoding
148+ err <- hGetContents errh
149+ _ <- deepseq err (return err)
150+ hClose errh
151+ writeIORef errRef err
152+ putMVar lock ()
153+
154+ -- Get exit code & return
155+ takeMVar lock
156+ takeMVar lock
157+ exitCode <- waitForProcess pid
158+ out <- readIORef outRef
159+ err <- readIORef errRef
160+ return (out, err, exitCode)
lib/Hakyll/Core/Util/File.hsnew file+56
@@ -0,0 +1,56 @@
1+--------------------------------------------------------------------------------
2+-- | A module containing various file utility functions
3+module Hakyll.Core.Util.File
4+ ( makeDirectories
5+ , getRecursiveContents
6+ , removeDirectory
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Control.Monad (filterM, forM, when)
12+import System.Directory (createDirectoryIfMissing,
13+ doesDirectoryExist, getDirectoryContents,
14+ removeDirectoryRecursive)
15+import System.FilePath (takeDirectory, (</>))
16+
17+
18+--------------------------------------------------------------------------------
19+-- | Given a path to a file, try to make the path writable by making
20+-- all directories on the path.
21+makeDirectories :: FilePath -> IO ()
22+makeDirectories = createDirectoryIfMissing True . takeDirectory
23+
24+
25+--------------------------------------------------------------------------------
26+-- | Get all contents of a directory.
27+getRecursiveContents :: (FilePath -> IO Bool) -- ^ Ignore this file/directory
28+ -> FilePath -- ^ Directory to search
29+ -> IO [FilePath] -- ^ List of files found
30+getRecursiveContents ignore top = go ""
31+ where
32+ isProper x
33+ | x `elem` [".", ".."] = return False
34+ | otherwise = not <$> ignore x
35+
36+ go dir = do
37+ dirExists <- doesDirectoryExist (top </> dir)
38+ if not dirExists
39+ then return []
40+ else do
41+ names <- filterM isProper =<< getDirectoryContents (top </> dir)
42+ paths <- forM names $ \name -> do
43+ let rel = dir </> name
44+ isDirectory <- doesDirectoryExist (top </> rel)
45+ if isDirectory
46+ then go rel
47+ else return [rel]
48+
49+ return $ concat paths
50+
51+
52+--------------------------------------------------------------------------------
53+removeDirectory :: FilePath -> IO ()
54+removeDirectory fp = do
55+ e <- doesDirectoryExist fp
56+ when e $ removeDirectoryRecursive fp
lib/Hakyll/Core/Util/Parser.hsnew file+31
@@ -0,0 +1,31 @@
1+--------------------------------------------------------------------------------
2+-- | Parser utilities
3+module Hakyll.Core.Util.Parser
4+ ( metadataKey
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Control.Applicative ((<|>))
10+import Control.Monad (guard, mzero, void)
11+import qualified Text.Parsec as P
12+import Text.Parsec.String (Parser)
13+
14+
15+--------------------------------------------------------------------------------
16+metadataKey :: Parser String
17+metadataKey = 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)
26+ if i `elem` reservedKeys then mzero else return i
27+
28+
29+--------------------------------------------------------------------------------
30+reservedKeys :: [String]
31+reservedKeys = ["if", "else", "endif", "for", "sep", "endfor", "partial"]
src/Hakyll/Core/Util/String.hs → lib/Hakyll/Core/Util/String.hsrenamed+36−6
@@ -1,27 +1,33 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+--------------------------------------------------------------------------------
13-- | Miscellaneous string manipulation functions.
2−--
34module Hakyll.Core.Util.String
45 ( trim
56 , replaceAll
67 , splitAll
8+ , needlePrefix
79 ) where
810
11+
12+--------------------------------------------------------------------------------
913import Data.Char (isSpace)
14+import Data.List (isPrefixOf)
1015import Data.Maybe (listToMaybe)
16+import Text.Regex.TDFA ((=~~))
1117
12−import Text.Regex.PCRE ((=~~))
1318
19+--------------------------------------------------------------------------------
1420-- | Trim a string (drop spaces, tabs and newlines at both sides).
15−--
1621trim :: String -> String
1722trim = reverse . trim' . reverse . trim'
1823 where
1924 trim' = dropWhile isSpace
2025
26+
27+--------------------------------------------------------------------------------
2128-- | A simple (but inefficient) regex replace funcion
22−--
2329replaceAll :: String -- ^ Pattern
24− -> (String -> String) -- ^ Replacement (called on capture)
30+ -> (String -> String) -- ^ Replacement (called on match)
2531 -> String -- ^ Source string
2632 -> String -- ^ Result
2733replaceAll pattern f source = replaceAll' source
@@ -33,9 +39,10 @@ replaceAll pattern f source = replaceAll' source
3339 (capture, after) = splitAt l tmp
3440 in before ++ f capture ++ replaceAll' after
3541
42+
43+--------------------------------------------------------------------------------
3644-- | A simple regex split function. The resulting list will contain no empty
3745-- strings.
38−--
3946splitAll :: String -- ^ Pattern
4047 -> String -- ^ String to split
4148 -> [String] -- ^ Result
@@ -46,3 +53,26 @@ splitAll pattern = filter (not . null) . splitAll'
4653 Just (o, l) ->
4754 let (before, tmp) = splitAt o src
4855 in before : splitAll' (drop l tmp)
56+
57+
58+
59+--------------------------------------------------------------------------------
60+-- | Find the first instance of needle (must be non-empty) in haystack. We
61+-- return the prefix of haystack before needle is matched.
62+--
63+-- Examples:
64+--
65+-- > needlePrefix "cd" "abcde" = "ab"
66+--
67+-- > needlePrefix "ab" "abc" = ""
68+--
69+-- > needlePrefix "ab" "xxab" = "xx"
70+--
71+-- > needlePrefix "a" "xx" = "xx"
72+needlePrefix :: String -> String -> Maybe String
73+needlePrefix needle haystack = go [] haystack
74+ where
75+ go _ [] = Nothing
76+ go acc xss@(x:xs)
77+ | needle `isPrefixOf` xss = Just $ reverse acc
78+ | otherwise = go (x : acc) xs
lib/Hakyll/Core/Writable.hsnew file+56
@@ -0,0 +1,56 @@
1+--------------------------------------------------------------------------------
2+-- | Describes writable items; items that can be saved to the disk
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE TypeSynonymInstances #-}
5+module Hakyll.Core.Writable
6+ ( Writable (..)
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import qualified Data.ByteString as SB
12+import qualified Data.ByteString.Lazy as LB
13+import Data.Word (Word8)
14+import Text.Blaze.Html (Html)
15+import Text.Blaze.Html.Renderer.String (renderHtml)
16+
17+
18+--------------------------------------------------------------------------------
19+import Hakyll.Core.Item
20+
21+
22+--------------------------------------------------------------------------------
23+-- | Describes an item that can be saved to the disk
24+class Writable a where
25+ -- | Save an item to the given filepath
26+ write :: FilePath -> Item a -> IO ()
27+
28+
29+--------------------------------------------------------------------------------
30+instance Writable () where
31+ write _ _ = return ()
32+
33+
34+--------------------------------------------------------------------------------
35+instance Writable [Char] where
36+ write p = writeFile p . itemBody
37+
38+
39+--------------------------------------------------------------------------------
40+instance Writable SB.ByteString where
41+ write p = SB.writeFile p . itemBody
42+
43+
44+--------------------------------------------------------------------------------
45+instance Writable LB.ByteString where
46+ write p = LB.writeFile p . itemBody
47+
48+
49+--------------------------------------------------------------------------------
50+instance Writable [Word8] where
51+ write p = write p . fmap SB.pack
52+
53+
54+--------------------------------------------------------------------------------
55+instance Writable Html where
56+ write p = write p . fmap renderHtml
lib/Hakyll/Main.hsnew file+180
@@ -0,0 +1,180 @@
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.hsnew file+109
@@ -0,0 +1,109 @@
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.hsnew file+35
@@ -0,0 +1,35 @@
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.hsnew file+101
@@ -0,0 +1,101 @@
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/Feed.hsnew file+173
@@ -0,0 +1,173 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+
4+--------------------------------------------------------------------------------
5+-- | A Module that allows easy rendering of RSS feeds.
6+--
7+-- The main rendering functions (@renderRss@, @renderAtom@) all assume that
8+-- you pass the list of items so that the most recent entry in the feed is the
9+-- first item in the list.
10+--
11+-- Also note that the context should have (at least) the following fields to
12+-- produce a correct feed:
13+--
14+-- - @$title$@: Title of the item
15+--
16+-- - @$description$@: Description to appear in the feed
17+--
18+-- - @$url$@: URL to the item - this is usually set automatically.
19+--
20+-- In addition, the posts should be named according to the rules for
21+-- 'Hakyll.Web.Template.Context.dateField'
22+module Hakyll.Web.Feed
23+ ( FeedConfiguration (..)
24+ , renderRss
25+ , renderAtom
26+ , renderRssWithTemplates
27+ , renderAtomWithTemplates
28+ ) where
29+
30+
31+--------------------------------------------------------------------------------
32+import Hakyll.Core.Compiler
33+import Hakyll.Core.Item
34+import Hakyll.Core.Util.String (replaceAll)
35+import Hakyll.Web.Template
36+import Hakyll.Web.Template.Context
37+import Hakyll.Web.Template.List
38+
39+
40+--------------------------------------------------------------------------------
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)
60+
61+
62+--------------------------------------------------------------------------------
63+-- | This is a data structure to keep the configuration of a feed.
64+data FeedConfiguration = FeedConfiguration
65+ { -- | Title of the feed.
66+ feedTitle :: String
67+ , -- | Description of the feed.
68+ feedDescription :: String
69+ , -- | Name of the feed author.
70+ feedAuthorName :: String
71+ , -- | Email of the feed author.
72+ feedAuthorEmail :: String
73+ , -- | Absolute root URL of the feed site (e.g. @http://jaspervdj.be@)
74+ feedRoot :: String
75+ } deriving (Show, Eq)
76+
77+
78+--------------------------------------------------------------------------------
79+-- | Abstract function to render any feed.
80+renderFeed :: Template -- ^ Default feed template
81+ -> Template -- ^ Default item template
82+ -> FeedConfiguration -- ^ Feed configuration
83+ -> Context String -- ^ Context for the items
84+ -> [Item String] -- ^ Input items
85+ -> 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
89+ applyTemplate feedTpl feedContext body
90+ 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;")
95+
96+ itemContext' = mconcat
97+ [ itemContext
98+ , constField "root" (feedRoot config)
99+ , constField "authorName" (feedAuthorName config)
100+ , constField "authorEmail" (feedAuthorEmail config)
101+ ]
102+
103+ feedContext = mconcat
104+ [ bodyField "body"
105+ , constField "title" (feedTitle config)
106+ , constField "description" (feedDescription config)
107+ , constField "authorName" (feedAuthorName config)
108+ , constField "authorEmail" (feedAuthorEmail config)
109+ , constField "root" (feedRoot config)
110+ , urlField "url"
111+ , updatedField
112+ , missingField
113+ ]
114+
115+ -- Take the first "updated" field from all items -- this should be the most
116+ -- recent.
117+ updatedField = field "updated" $ \_ -> case items of
118+ [] -> return "Unknown"
119+ (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
120+ 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)
149+
150+
151+--------------------------------------------------------------------------------
152+-- | Render an RSS feed with a number of items.
153+renderRss :: FeedConfiguration -- ^ Feed configuration
154+ -> Context String -- ^ Item context
155+ -> [Item String] -- ^ Feed items
156+ -> Compiler (Item String) -- ^ Resulting feed
157+renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
158+
159+
160+--------------------------------------------------------------------------------
161+-- | Render an Atom feed with a number of items.
162+renderAtom :: FeedConfiguration -- ^ Feed configuration
163+ -> Context String -- ^ Item context
164+ -> [Item String] -- ^ Feed items
165+ -> Compiler (Item String) -- ^ Resulting feed
166+renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
167+
168+
169+--------------------------------------------------------------------------------
170+-- | Copies @$updated$@ from @$published$@ if it is not already set.
171+makeItemContext :: String -> Context a -> Context a
172+makeItemContext fmt context = mconcat
173+ [context, dateField "published" fmt, dateField "updated" fmt]
lib/Hakyll/Web/Html.hsnew file+188
@@ -0,0 +1,188 @@
1+--------------------------------------------------------------------------------
2+-- | Provides utilities to manipulate HTML pages
3+module Hakyll.Web.Html
4+ ( -- * Generic
5+ withTags
6+ , withTagList
7+
8+ -- * Headers
9+ , demoteHeaders
10+
11+ -- * Url manipulation
12+ , getUrls
13+ , withUrls
14+ , toUrl
15+ , toSiteRoot
16+ , isExternal
17+
18+ -- * Stripping/escaping
19+ , stripTags
20+ , escapeHtml
21+ ) where
22+
23+
24+--------------------------------------------------------------------------------
25+import Data.Char (digitToInt, intToDigit,
26+ isDigit, toLower)
27+import Data.List (isPrefixOf)
28+import qualified Data.Set as S
29+import System.FilePath.Posix (joinPath, splitPath,
30+ takeDirectory)
31+import Text.Blaze.Html (toHtml)
32+import Text.Blaze.Html.Renderer.String (renderHtml)
33+import qualified Text.HTML.TagSoup as TS
34+import Network.URI (isUnreserved, escapeURIString)
35+
36+
37+--------------------------------------------------------------------------------
38+-- | Map over all tags in the document
39+withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
40+withTags = withTagList . map
41+
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'
45+
46+--------------------------------------------------------------------------------
47+-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
48+demoteHeaders :: String -> String
49+demoteHeaders = withTags $ \tag -> case tag of
50+ TS.TagOpen t a -> TS.TagOpen (demote t) a
51+ TS.TagClose t -> TS.TagClose (demote t)
52+ t -> t
53+ where
54+ demote t@['h', n]
55+ | isDigit n = ['h', intToDigit (min 6 $ digitToInt n + 1)]
56+ | otherwise = t
57+ demote t = t
58+
59+
60+--------------------------------------------------------------------------------
61+isUrlAttribute :: String -> Bool
62+isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
63+
64+
65+--------------------------------------------------------------------------------
66+getUrls :: [TS.Tag String] -> [String]
67+getUrls tags = [v | TS.TagOpen _ as <- tags, (k, v) <- as, isUrlAttribute k]
68+
69+
70+--------------------------------------------------------------------------------
71+-- | Apply a function to each URL on a webpage
72+withUrls :: (String -> String) -> String -> String
73+withUrls f = withTags tag
74+ where
75+ tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
76+ tag x = x
77+ attr (k, v) = (k, if isUrlAttribute k then f v else v)
78+
79+
80+--------------------------------------------------------------------------------
81+-- | Customized TagSoup renderer. The default TagSoup renderer escape CSS
82+-- within style tags, and doesn't properly minimize.
83+renderTags' :: [TS.Tag String] -> String
84+renderTags' = TS.renderTagsOptions TS.RenderOptions
85+ { TS.optRawTag = (`elem` ["script", "style"]) . map toLower
86+ , TS.optMinimize = (`S.member` minimize) . map toLower
87+ , TS.optEscape = id
88+ }
89+ where
90+ -- A list of elements which must be minimized
91+ minimize = S.fromList
92+ [ "area", "br", "col", "embed", "hr", "img", "input", "meta", "link"
93+ , "param"
94+ ]
95+
96+
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+
106+--------------------------------------------------------------------------------
107+-- | Convert a filepath to an URL starting from the site root
108+--
109+-- Example:
110+--
111+-- > toUrl "foo/bar.html"
112+--
113+-- Result:
114+--
115+-- > "/foo/bar.html"
116+--
117+-- This also sanitizes the URL, e.g. converting spaces into '%20'
118+toUrl :: FilePath -> String
119+toUrl url = case url of
120+ ('/' : xs) -> '/' : sanitize xs
121+ xs -> '/' : sanitize xs
122+ where
123+ -- Everything but unreserved characters should be escaped as we are
124+ -- sanitising the path therefore reserved characters which have a
125+ -- meaning in URI does not appear. Special casing for `/`, because it has
126+ -- a special meaning in FilePath as well as in URI.
127+ sanitize = escapeURIString (\c -> c == '/' || isUnreserved c)
128+
129+
130+--------------------------------------------------------------------------------
131+-- | Get the relative url to the site root, for a given (absolute) url
132+toSiteRoot :: String -> String
133+toSiteRoot = emptyException . joinPath . map parent
134+ . filter relevant . splitPath . takeDirectory
135+ where
136+ parent = const ".."
137+ emptyException [] = "."
138+ emptyException x = x
139+ relevant "." = False
140+ relevant "/" = False
141+ relevant "./" = False
142+ relevant _ = True
143+
144+
145+--------------------------------------------------------------------------------
146+-- | Check if an URL links to an external HTTP(S) source
147+isExternal :: String -> Bool
148+isExternal url = any (flip isPrefixOf url) ["http://", "https://", "//"]
149+
150+
151+--------------------------------------------------------------------------------
152+-- | Strip all HTML tags from a string
153+--
154+-- Example:
155+--
156+-- > stripTags "<p>foo</p>"
157+--
158+-- Result:
159+--
160+-- > "foo"
161+--
162+-- This also works for incomplete tags
163+--
164+-- Example:
165+--
166+-- > stripTags "<p>foo</p"
167+--
168+-- Result:
169+--
170+-- > "foo"
171+stripTags :: String -> String
172+stripTags [] = []
173+stripTags ('<' : xs) = stripTags $ drop 1 $ dropWhile (/= '>') xs
174+stripTags (x : xs) = x : stripTags xs
175+
176+
177+--------------------------------------------------------------------------------
178+-- | HTML-escape a string
179+--
180+-- Example:
181+--
182+-- > escapeHtml "Me & Dean"
183+--
184+-- Result:
185+--
186+-- > "Me &amp; Dean"
187+escapeHtml :: String -> String
188+escapeHtml = renderHtml . toHtml
lib/Hakyll/Web/Html/RelativizeUrls.hsnew file+52
@@ -0,0 +1,52 @@
1+--------------------------------------------------------------------------------
2+-- | This module exposes a function which can relativize URL's on a webpage.
3+--
4+-- This means that one can deploy the resulting site on
5+-- @http:\/\/example.com\/@, but also on @http:\/\/example.com\/some-folder\/@
6+-- without having to change anything (simply copy over the files).
7+--
8+-- To use it, you should use absolute URL's from the site root everywhere. For
9+-- example, use
10+--
11+-- > <img src="/images/lolcat.png" alt="Funny zomgroflcopter" />
12+--
13+-- in a blogpost. When running this through the relativize URL's module, this
14+-- will result in (suppose your blogpost is located at @\/posts\/foo.html@:
15+--
16+-- > <img src="../images/lolcat.png" alt="Funny zomgroflcopter" />
17+module Hakyll.Web.Html.RelativizeUrls
18+ ( relativizeUrls
19+ , relativizeUrlsWith
20+ ) where
21+
22+
23+--------------------------------------------------------------------------------
24+import Data.List (isPrefixOf)
25+
26+
27+--------------------------------------------------------------------------------
28+import Hakyll.Core.Compiler
29+import Hakyll.Core.Item
30+import Hakyll.Web.Html
31+
32+
33+--------------------------------------------------------------------------------
34+-- | Compiler form of 'relativizeUrls' which automatically picks the right root
35+-- path
36+relativizeUrls :: Item String -> Compiler (Item String)
37+relativizeUrls item = do
38+ route <- getRoute $ itemIdentifier item
39+ return $ case route of
40+ Nothing -> item
41+ Just r -> fmap (relativizeUrlsWith $ toSiteRoot r) item
42+
43+
44+--------------------------------------------------------------------------------
45+-- | Relativize URL's in HTML
46+relativizeUrlsWith :: String -- ^ Path to the site root
47+ -> String -- ^ HTML to relativize
48+ -> String -- ^ Resulting HTML
49+relativizeUrlsWith root = withUrls rel
50+ where
51+ isRel x = "/" `isPrefixOf` x && not ("//" `isPrefixOf` x)
52+ rel x = if isRel x then root ++ x else x
lib/Hakyll/Web/Paginate.hsnew file+153
@@ -0,0 +1,153 @@
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.hsnew file+165
@@ -0,0 +1,165 @@
1+--------------------------------------------------------------------------------
2+-- | Module exporting convenient pandoc bindings
3+module Hakyll.Web.Pandoc
4+ ( -- * The basic building blocks
5+ readPandoc
6+ , readPandocWith
7+ , writePandoc
8+ , writePandocWith
9+ , renderPandoc
10+ , renderPandocWith
11+
12+ -- * Derived compilers
13+ , pandocCompiler
14+ , pandocCompilerWith
15+ , pandocCompilerWithTransform
16+ , pandocCompilerWithTransformM
17+
18+ -- * Default options
19+ , defaultHakyllReaderOptions
20+ , defaultHakyllWriterOptions
21+ ) where
22+
23+
24+--------------------------------------------------------------------------------
25+import qualified Data.Text as T
26+import Text.Pandoc
27+import Text.Pandoc.Highlighting (pygments)
28+
29+
30+--------------------------------------------------------------------------------
31+import Hakyll.Core.Compiler
32+import Hakyll.Core.Item
33+import Hakyll.Web.Pandoc.FileType
34+
35+
36+--------------------------------------------------------------------------------
37+-- | Read a string using pandoc, with the default options
38+readPandoc
39+ :: Item String -- ^ String to read
40+ -> Compiler (Item Pandoc) -- ^ Resulting document
41+readPandoc = readPandocWith defaultHakyllReaderOptions
42+
43+
44+--------------------------------------------------------------------------------
45+-- | 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'
55+ where
56+ reader ro t = case t of
57+ DocBook -> readDocBook ro
58+ Html -> readHtml ro
59+ LaTeX -> readLaTeX ro
60+ LiterateHaskell t' -> reader (addExt ro Ext_literate_haskell) t'
61+ Markdown -> readMarkdown ro
62+ MediaWiki -> readMediaWiki ro
63+ OrgMode -> readOrg ro
64+ Rst -> readRST ro
65+ Textile -> readTextile ro
66+ _ -> error $
67+ "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
68+ "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
69+
70+ addExt ro e = ro {readerExtensions = enableExtension e $ readerExtensions ro}
71+
72+
73+--------------------------------------------------------------------------------
74+-- | Write a document (as HTML) using pandoc, with the default options
75+writePandoc :: Item Pandoc -- ^ Document to write
76+ -> Item String -- ^ Resulting HTML
77+writePandoc = writePandocWith defaultHakyllWriterOptions
78+
79+
80+--------------------------------------------------------------------------------
81+-- | Write a document (as HTML) using pandoc, with the supplied options
82+writePandocWith :: WriterOptions -- ^ Writer options for pandoc
83+ -> Item Pandoc -- ^ Document to write
84+ -> 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'
89+
90+
91+--------------------------------------------------------------------------------
92+-- | Render the resource using pandoc
93+renderPandoc :: Item String -> Compiler (Item String)
94+renderPandoc =
95+ renderPandocWith defaultHakyllReaderOptions defaultHakyllWriterOptions
96+
97+
98+--------------------------------------------------------------------------------
99+-- | 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
104+
105+
106+--------------------------------------------------------------------------------
107+-- | Read a page render using pandoc
108+pandocCompiler :: Compiler (Item String)
109+pandocCompiler =
110+ pandocCompilerWith defaultHakyllReaderOptions defaultHakyllWriterOptions
111+
112+
113+--------------------------------------------------------------------------------
114+-- | A version of 'pandocCompiler' which allows you to specify your own pandoc
115+-- options
116+pandocCompilerWith :: ReaderOptions -> WriterOptions -> Compiler (Item String)
117+pandocCompilerWith ropt wopt =
118+ cached "Hakyll.Web.Pandoc.pandocCompilerWith" $
119+ pandocCompilerWithTransform ropt wopt id
120+
121+
122+--------------------------------------------------------------------------------
123+-- | An extension of 'pandocCompilerWith' which allows you to specify a custom
124+-- pandoc transformation for the content
125+pandocCompilerWithTransform :: ReaderOptions -> WriterOptions
126+ -> (Pandoc -> Pandoc)
127+ -> 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)
143+
144+
145+--------------------------------------------------------------------------------
146+-- | The default reader options for pandoc parsing in hakyll
147+defaultHakyllReaderOptions :: ReaderOptions
148+defaultHakyllReaderOptions = def
149+ { -- The following option causes pandoc to read smart typography, a nice
150+ -- and free bonus.
151+ readerExtensions = enableExtension Ext_smart pandocExtensions
152+ }
153+
154+
155+--------------------------------------------------------------------------------
156+-- | The default writer options for pandoc rendering in hakyll
157+defaultHakyllWriterOptions :: WriterOptions
158+defaultHakyllWriterOptions = def
159+ { -- This option causes literate haskell to be written using '>' marks in
160+ -- 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
165+ }
lib/Hakyll/Web/Pandoc/Biblio.hsnew file+126
@@ -0,0 +1,126 @@
1+--------------------------------------------------------------------------------
2+-- | Wraps pandocs bibiliography handling
3+--
4+-- In order to add a bibliography, you will need a bibliography file (e.g.
5+-- @.bib@) and a CSL file (@.csl@). Both need to be compiled with their
6+-- respective compilers ('biblioCompiler' and 'cslCompiler'). Then, you can
7+-- refer to these files when you use 'readPandocBiblio'. This function also
8+-- takes the reader options for completeness -- you can use
9+-- '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.
12+{-# LANGUAGE Arrows #-}
13+{-# LANGUAGE DeriveDataTypeable #-}
14+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
15+module Hakyll.Web.Pandoc.Biblio
16+ ( CSL
17+ , cslCompiler
18+ , Biblio (..)
19+ , biblioCompiler
20+ , readPandocBiblio
21+ , pandocBiblioCompiler
22+ ) where
23+
24+
25+--------------------------------------------------------------------------------
26+import Control.Monad (liftM, replicateM)
27+import Data.Binary (Binary (..))
28+import Data.Typeable (Typeable)
29+import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
31+import Hakyll.Core.Identifier
32+import Hakyll.Core.Item
33+import Hakyll.Core.Provider
34+import Hakyll.Core.Writable
35+import 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 (..))
41+
42+
43+--------------------------------------------------------------------------------
44+data CSL = CSL
45+ deriving (Show, Typeable)
46+
47+
48+--------------------------------------------------------------------------------
49+instance Binary CSL where
50+ put CSL = return ()
51+ get = return CSL
52+
53+
54+--------------------------------------------------------------------------------
55+instance Writable CSL where
56+ -- Shouldn't be written.
57+ write _ _ = return ()
58+
59+
60+--------------------------------------------------------------------------------
61+cslCompiler :: Compiler (Item CSL)
62+cslCompiler = makeItem CSL
63+
64+
65+--------------------------------------------------------------------------------
66+newtype Biblio = Biblio [CSL.Reference]
67+ deriving (Show, Typeable)
68+
69+
70+--------------------------------------------------------------------------------
71+instance Binary Biblio where
72+ -- Ugly.
73+ get = do
74+ len <- get
75+ Biblio <$> replicateM len get
76+ put (Biblio rs) = put (length rs) >> mapM_ put rs
77+
78+
79+--------------------------------------------------------------------------------
80+instance Writable Biblio where
81+ -- Shouldn't be written.
82+ write _ _ = return ()
83+
84+
85+--------------------------------------------------------------------------------
86+biblioCompiler :: Compiler (Item Biblio)
87+biblioCompiler = 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
93+
94+
95+--------------------------------------------------------------------------------
96+readPandocBiblio :: ReaderOptions
97+ -> Item CSL
98+ -> Item Biblio
99+ -> (Item String)
100+ -> Compiler (Item Pandoc)
101+readPandocBiblio ropt csl biblio item = do
102+ -- Parse CSL file, if given
103+ provider <- compilerProvider <$> compilerAsk
104+ style <- unsafeCompiler $
105+ CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
106+
107+ -- We need to know the citation keys, add then *before* actually parsing the
108+ -- actual page. If we don't do this, pandoc won't even consider them
109+ -- citations!
110+ let Biblio refs = itemBody biblio
111+ pandoc <- itemBody <$> readPandocWith ropt item
112+ let pandoc' = processCites style refs pandoc
113+
114+ return $ fmap (const pandoc') item
115+
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/Binary.hsnew file+33
@@ -0,0 +1,33 @@
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/Pandoc/FileType.hsnew file+74
@@ -0,0 +1,74 @@
1+--------------------------------------------------------------------------------
2+-- | A module dealing with pandoc file extensions and associated file types
3+module Hakyll.Web.Pandoc.FileType
4+ ( FileType (..)
5+ , fileType
6+ , itemFileType
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import System.FilePath (splitExtension)
12+
13+
14+--------------------------------------------------------------------------------
15+import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
17+
18+
19+--------------------------------------------------------------------------------
20+-- | Datatype to represent the different file types Hakyll can deal with by
21+-- default
22+data FileType
23+ = Binary
24+ | Css
25+ | DocBook
26+ | Html
27+ | LaTeX
28+ | LiterateHaskell FileType
29+ | Markdown
30+ | MediaWiki
31+ | OrgMode
32+ | PlainText
33+ | Rst
34+ | Textile
35+ deriving (Eq, Ord, Show, Read)
36+
37+
38+--------------------------------------------------------------------------------
39+-- | Get the file type for a certain file. The type is determined by extension.
40+fileType :: FilePath -> FileType
41+fileType = uncurry fileType' . splitExtension
42+ 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
48+ -- If no extension is given, default to Markdown + LiterateHaskell
49+ Binary -> Markdown
50+ -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
51+ 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
69+
70+
71+--------------------------------------------------------------------------------
72+-- | Get the file type for the current file
73+itemFileType :: Item a -> FileType
74+itemFileType = fileType . toFilePath . itemIdentifier
lib/Hakyll/Web/Redirect.hsnew file+100
@@ -0,0 +1,100 @@
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/Tags.hsnew file+345
@@ -0,0 +1,345 @@
1+--------------------------------------------------------------------------------
2+-- | This module containing some specialized functions to deal with tags. It
3+-- assumes you follow some conventions.
4+--
5+-- We support two types of tags: tags and categories.
6+--
7+-- To use default tags, use 'buildTags'. Tags are placed in a comma-separated
8+-- metadata field like this:
9+--
10+-- > ---
11+-- > author: Philip K. Dick
12+-- > title: Do androids dream of electric sheep?
13+-- > tags: future, science fiction, humanoid
14+-- > ---
15+-- > The novel is set in a post-apocalyptic near future, where the Earth and
16+-- > its populations have been damaged greatly by Nuclear...
17+--
18+-- To use categories, use the 'buildCategories' function. Categories are
19+-- determined by the directory a page is in, for example, the post
20+--
21+-- > posts/coding/2010-01-28-hakyll-categories.markdown
22+--
23+-- will receive the @coding@ category.
24+--
25+-- Advanced users may implement custom systems using 'buildTagsWith' if desired.
26+--
27+-- In the above example, we would want to create a page which lists all pages in
28+-- the @coding@ category, for example, with the 'Identifier':
29+--
30+-- > tags/coding.html
31+--
32+-- This is where the first parameter of 'buildTags' and 'buildCategories' comes
33+-- in. In the above case, we used the function:
34+--
35+-- > fromCapture "tags/*.html" :: String -> Identifier
36+--
37+-- The 'tagsRules' function lets you generate such a page for each tag in the
38+-- 'Rules' monad.
39+{-# LANGUAGE Arrows #-}
40+{-# LANGUAGE DeriveDataTypeable #-}
41+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
42+{-# LANGUAGE OverloadedStrings #-}
43+module Hakyll.Web.Tags
44+ ( Tags (..)
45+ , getTags
46+ , getCategory
47+ , buildTagsWith
48+ , buildTags
49+ , buildCategories
50+ , tagsRules
51+ , renderTags
52+ , renderTagCloud
53+ , renderTagCloudWith
54+ , tagCloudField
55+ , tagCloudFieldWith
56+ , renderTagList
57+ , tagsField
58+ , tagsFieldWith
59+ , categoryField
60+ , sortTagsBy
61+ , caseInsensitiveTags
62+ ) where
63+
64+
65+--------------------------------------------------------------------------------
66+import Control.Arrow ((&&&))
67+import Control.Monad (foldM, forM, forM_, mplus)
68+import Data.Char (toLower)
69+import Data.List (intercalate, intersperse,
70+ sortBy)
71+import qualified Data.Map as M
72+import Data.Maybe (catMaybes, fromMaybe)
73+import Data.Ord (comparing)
74+import qualified Data.Set as S
75+import System.FilePath (takeBaseName, takeDirectory)
76+import Text.Blaze.Html (toHtml, toValue, (!))
77+import Text.Blaze.Html.Renderer.String (renderHtml)
78+import qualified Text.Blaze.Html5 as H
79+import qualified Text.Blaze.Html5.Attributes as A
80+
81+
82+--------------------------------------------------------------------------------
83+import Hakyll.Core.Compiler
84+import Hakyll.Core.Dependencies
85+import Hakyll.Core.Identifier
86+import Hakyll.Core.Identifier.Pattern
87+import Hakyll.Core.Item
88+import Hakyll.Core.Metadata
89+import Hakyll.Core.Rules
90+import Hakyll.Core.Util.String
91+import Hakyll.Web.Html
92+import Hakyll.Web.Template.Context
93+
94+
95+--------------------------------------------------------------------------------
96+-- | Data about tags
97+data Tags = Tags
98+ { tagsMap :: [(String, [Identifier])]
99+ , tagsMakeId :: String -> Identifier
100+ , tagsDependency :: Dependency
101+ }
102+
103+
104+--------------------------------------------------------------------------------
105+-- | 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.
107+getTags :: MonadMetadata m => Identifier -> m [String]
108+getTags identifier = do
109+ metadata <- getMetadata identifier
110+ return $ fromMaybe [] $
111+ (lookupStringList "tags" metadata) `mplus`
112+ (map trim . splitAll "," <$> lookupString "tags" metadata)
113+
114+
115+--------------------------------------------------------------------------------
116+-- | Obtain category from a page.
117+getCategory :: MonadMetadata m => Identifier -> m [String]
118+getCategory = return . return . takeBaseName . takeDirectory . toFilePath
119+
120+
121+--------------------------------------------------------------------------------
122+-- | Higher-order function to read tags
123+buildTagsWith :: MonadMetadata m
124+ => (Identifier -> m [String])
125+ -> Pattern
126+ -> (String -> Identifier)
127+ -> m Tags
128+buildTagsWith f pattern makeId = do
129+ ids <- getMatches pattern
130+ tagMap <- foldM addTags M.empty ids
131+ let set' = S.fromList ids
132+ return $ Tags (M.toList tagMap) makeId (PatternDependency pattern set')
133+ where
134+ -- Create a tag map for one page
135+ addTags tagMap id' = do
136+ tags <- f id'
137+ let tagMap' = M.fromList $ zip tags $ repeat [id']
138+ return $ M.unionWith (++) tagMap tagMap'
139+
140+
141+--------------------------------------------------------------------------------
142+buildTags :: MonadMetadata m => Pattern -> (String -> Identifier) -> m Tags
143+buildTags = buildTagsWith getTags
144+
145+
146+--------------------------------------------------------------------------------
147+buildCategories :: MonadMetadata m => Pattern -> (String -> Identifier)
148+ -> m Tags
149+buildCategories = buildTagsWith getCategory
150+
151+
152+--------------------------------------------------------------------------------
153+tagsRules :: Tags -> (String -> Pattern -> Rules ()) -> Rules ()
154+tagsRules tags rules =
155+ forM_ (tagsMap tags) $ \(tag, identifiers) ->
156+ rulesExtraDependencies [tagsDependency tags] $
157+ create [tagsMakeId tags tag] $
158+ rules tag $ fromList identifiers
159+
160+
161+--------------------------------------------------------------------------------
162+-- | Render tags in HTML (the flexible higher-order function)
163+renderTags :: (String -> String -> Int -> Int -> Int -> String)
164+ -- ^ Produce a tag item: tag, url, count, min count, max count
165+ -> ([String] -> String)
166+ -- ^ Join items
167+ -> Tags
168+ -- ^ Tag cloud renderer
169+ -> Compiler String
170+renderTags makeHtml concatHtml tags = do
171+ -- In tags' we create a list: [((tag, route), count)]
172+ tags' <- forM (tagsMap tags) $ \(tag, ids) -> do
173+ route' <- getRoute $ tagsMakeId tags tag
174+ return ((tag, route'), length ids)
175+
176+ -- TODO: We actually need to tell a dependency here!
177+
178+ let -- Absolute frequencies of the pages
179+ freqs = map snd tags'
180+
181+ -- The minimum and maximum count found
182+ (min', max')
183+ | null freqs = (0, 1)
184+ | otherwise = (minimum &&& maximum) freqs
185+
186+ -- Create a link for one item
187+ makeHtml' ((tag, url), count) =
188+ makeHtml tag (toUrl $ fromMaybe "/" url) count min' max'
189+
190+ -- Render and return the HTML
191+ return $ concatHtml $ map makeHtml' tags'
192+
193+
194+--------------------------------------------------------------------------------
195+-- | Render a tag cloud in HTML
196+renderTagCloud :: Double
197+ -- ^ Smallest font size, in percent
198+ -> Double
199+ -- ^ Biggest font size, in percent
200+ -> Tags
201+ -- ^ Input tags
202+ -> Compiler String
203+ -- ^ Rendered cloud
204+renderTagCloud = renderTagCloudWith makeLink (intercalate " ")
205+ where
206+ makeLink minSize maxSize tag url count min' max' =
207+ -- Show the relative size of one 'count' in percent
208+ let diff = 1 + fromIntegral max' - fromIntegral min'
209+ relative = (fromIntegral count - fromIntegral min') / diff
210+ size = floor $ minSize + relative * (maxSize - minSize) :: Int
211+ in renderHtml $
212+ H.a ! A.style (toValue $ "font-size: " ++ show size ++ "%")
213+ ! A.href (toValue url)
214+ $ toHtml tag
215+
216+
217+--------------------------------------------------------------------------------
218+-- | Render a tag cloud in HTML
219+renderTagCloudWith :: (Double -> Double ->
220+ String -> String -> Int -> Int -> Int -> String)
221+ -- ^ Render a single tag link
222+ -> ([String] -> String)
223+ -- ^ Concatenate links
224+ -> Double
225+ -- ^ Smallest font size, in percent
226+ -> Double
227+ -- ^ Biggest font size, in percent
228+ -> Tags
229+ -- ^ Input tags
230+ -> Compiler String
231+ -- ^ Rendered cloud
232+renderTagCloudWith makeLink cat minSize maxSize =
233+ renderTags (makeLink minSize maxSize) cat
234+
235+
236+--------------------------------------------------------------------------------
237+-- | Render a tag cloud in HTML as a context
238+tagCloudField :: String
239+ -- ^ Destination key
240+ -> Double
241+ -- ^ Smallest font size, in percent
242+ -> Double
243+ -- ^ Biggest font size, in percent
244+ -> Tags
245+ -- ^ Input tags
246+ -> Context a
247+ -- ^ Context
248+tagCloudField key minSize maxSize tags =
249+ field key $ \_ -> renderTagCloud minSize maxSize tags
250+
251+
252+--------------------------------------------------------------------------------
253+-- | Render a tag cloud in HTML as a context
254+tagCloudFieldWith :: String
255+ -- ^ Destination key
256+ -> (Double -> Double ->
257+ String -> String -> Int -> Int -> Int -> String)
258+ -- ^ Render a single tag link
259+ -> ([String] -> String)
260+ -- ^ Concatenate links
261+ -> Double
262+ -- ^ Smallest font size, in percent
263+ -> Double
264+ -- ^ Biggest font size, in percent
265+ -> Tags
266+ -- ^ Input tags
267+ -> Context a
268+ -- ^ Context
269+tagCloudFieldWith key makeLink cat minSize maxSize tags =
270+ field key $ \_ -> renderTagCloudWith makeLink cat minSize maxSize tags
271+
272+
273+--------------------------------------------------------------------------------
274+-- | Render a simple tag list in HTML, with the tag count next to the item
275+-- TODO: Maybe produce a Context here
276+renderTagList :: Tags -> Compiler (String)
277+renderTagList = renderTags makeLink (intercalate ", ")
278+ where
279+ makeLink tag url count _ _ = renderHtml $
280+ H.a ! A.href (toValue url) $ toHtml (tag ++ " (" ++ show count ++ ")")
281+
282+
283+--------------------------------------------------------------------------------
284+-- | Render tags with links with custom functions to get tags and to
285+-- render links
286+tagsFieldWith :: (Identifier -> Compiler [String])
287+ -- ^ Get the tags
288+ -> (String -> (Maybe FilePath) -> Maybe H.Html)
289+ -- ^ Render link for one tag
290+ -> ([H.Html] -> H.Html)
291+ -- ^ Concatenate tag links
292+ -> String
293+ -- ^ Destination field
294+ -> Tags
295+ -- ^ Tags structure
296+ -> Context a
297+ -- ^ Resulting context
298+tagsFieldWith getTags' renderLink cat key tags = field key $ \item -> do
299+ tags' <- getTags' $ itemIdentifier item
300+ links <- forM tags' $ \tag -> do
301+ route' <- getRoute $ tagsMakeId tags tag
302+ return $ renderLink tag route'
303+
304+ return $ renderHtml $ cat $ catMaybes $ links
305+
306+
307+--------------------------------------------------------------------------------
308+-- | Render tags with links
309+tagsField :: String -- ^ Destination key
310+ -> Tags -- ^ Tags
311+ -> Context a -- ^ Context
312+tagsField =
313+ tagsFieldWith getTags simpleRenderLink (mconcat . intersperse ", ")
314+
315+
316+--------------------------------------------------------------------------------
317+-- | Render the category in a link
318+categoryField :: String -- ^ Destination key
319+ -> Tags -- ^ Tags
320+ -> Context a -- ^ Context
321+categoryField =
322+ tagsFieldWith getCategory simpleRenderLink (mconcat . intersperse ", ")
323+
324+
325+--------------------------------------------------------------------------------
326+-- | Render one tag link
327+simpleRenderLink :: String -> (Maybe FilePath) -> Maybe H.Html
328+simpleRenderLink _ Nothing = Nothing
329+simpleRenderLink tag (Just filePath) =
330+ Just $ H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag
331+
332+
333+--------------------------------------------------------------------------------
334+-- | Sort tags using supplied function. First element of the tuple passed to
335+-- the comparing function is the actual tag name.
336+sortTagsBy :: ((String, [Identifier]) -> (String, [Identifier]) -> Ordering)
337+ -> Tags -> Tags
338+sortTagsBy f t = t {tagsMap = sortBy f (tagsMap t)}
339+
340+
341+--------------------------------------------------------------------------------
342+-- | Sample sorting function that compares tags case insensitively.
343+caseInsensitiveTags :: (String, [Identifier]) -> (String, [Identifier])
344+ -> Ordering
345+caseInsensitiveTags = comparing $ map toLower . fst
lib/Hakyll/Web/Template.hsnew file+178
@@ -0,0 +1,178 @@
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.hsnew file+481
@@ -0,0 +1,481 @@
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.hsnew file+261
@@ -0,0 +1,261 @@
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.hsnew file+291
@@ -0,0 +1,291 @@
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.hsnew file+95
@@ -0,0 +1,95 @@
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/Web/Template/List.hsnew file+91
@@ -0,0 +1,91 @@
1+--------------------------------------------------------------------------------
2+-- | Provides an easy way to combine several items in a list. The applications
3+-- are obvious:
4+--
5+-- * A post list on a blog
6+--
7+-- * An image list in a gallery
8+--
9+-- * A sitemap
10+{-# LANGUAGE TupleSections #-}
11+module Hakyll.Web.Template.List
12+ ( applyTemplateList
13+ , applyJoinTemplateList
14+ , chronological
15+ , recentFirst
16+ , sortChronological
17+ , sortRecentFirst
18+ ) where
19+
20+
21+--------------------------------------------------------------------------------
22+import Control.Monad (liftM)
23+import Data.List (intersperse, sortBy)
24+import Data.Ord (comparing)
25+import Data.Time.Locale.Compat (defaultTimeLocale)
26+
27+
28+--------------------------------------------------------------------------------
29+import Hakyll.Core.Compiler
30+import Hakyll.Core.Identifier
31+import Hakyll.Core.Item
32+import Hakyll.Core.Metadata
33+import Hakyll.Web.Template
34+import Hakyll.Web.Template.Context
35+
36+
37+--------------------------------------------------------------------------------
38+-- | Generate a string of a listing of pages, after applying a template to each
39+-- page.
40+applyTemplateList :: Template
41+ -> Context a
42+ -> [Item a]
43+ -> Compiler String
44+applyTemplateList = applyJoinTemplateList ""
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Join a listing of pages with a string in between, after applying a template
49+-- to each page.
50+applyJoinTemplateList :: String
51+ -> Template
52+ -> Context a
53+ -> [Item a]
54+ -> Compiler String
55+applyJoinTemplateList delimiter tpl context items = do
56+ items' <- mapM (applyTemplate tpl context) items
57+ return $ concat $ intersperse delimiter $ map itemBody items'
58+
59+
60+--------------------------------------------------------------------------------
61+-- | Sort pages chronologically. Uses the same method as 'dateField' for
62+-- extracting the date.
63+chronological :: MonadMetadata m => [Item a] -> m [Item a]
64+chronological =
65+ sortByM $ getItemUTC defaultTimeLocale . itemIdentifier
66+ where
67+ sortByM :: (Monad m, Ord k) => (a -> m k) -> [a] -> m [a]
68+ sortByM f xs = liftM (map fst . sortBy (comparing snd)) $
69+ mapM (\x -> liftM (x,) (f x)) xs
70+
71+
72+--------------------------------------------------------------------------------
73+-- | 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]
src-inotify/Hakyll/Web/Preview/Poll.hsdeleted−50
@@ -1,50 +0,0 @@
1−-- | Filesystem polling with an inotify backend. Works only on linux.
2−--
3−module Hakyll.Web.Preview.Poll
4− ( previewPoll
5− ) where
6−
7−import Control.Monad (forM_, when)
8−import Data.Set (Set)
9−import qualified Data.Set as S
10−import System.FilePath (takeDirectory, (</>))
11−import Data.List (isPrefixOf)
12−
13−import System.INotify
14−
15−import Hakyll.Core.Configuration
16−import Hakyll.Core.Resource
17−
18−-- | Calls the given callback when the directory tree changes
19−--
20−previewPoll :: HakyllConfiguration -- ^ Configuration
21− -> Set Resource -- ^ Resources to watch
22− -> IO () -- ^ Action called when something changes
23− -> IO () -- ^ Can block forever
24−previewPoll _ resources callback = do
25− -- Initialize inotify
26− inotify <- initINotify
27−
28− let -- A set of file paths
29− paths = S.map unResource resources
30−
31− -- A list of directories. Run it through a set so we have every
32− -- directory only once.
33− directories = S.toList $ S.map (notEmpty . takeDirectory) paths
34−
35− -- Problem: we can't add a watcher for "". So we make sure a directory
36− -- name is not empty
37− notEmpty "" = "."
38− notEmpty x = x
39−
40− -- Execute the callback when path is known
41− ifResource path =
42− let path' = if "./" `isPrefixOf` path then drop 2 path else path
43− in when (path' `S.member` paths) callback
44−
45− -- Add a watcher for every directory
46− forM_ directories $ \directory -> do
47− _ <- addWatch inotify [Modify] directory $ \e -> case e of
48− (Modified _ (Just p)) -> ifResource $ directory </> p
49− _ -> return ()
50− return ()
src-interval/Hakyll/Web/Preview/Poll.hsdeleted−36
@@ -1,36 +0,0 @@
1−-- | Interval-based implementation of preview polling, for the platforms which
2−-- are not supported by inotify.
3−--
4−module Hakyll.Web.Preview.Poll
5− ( previewPoll
6− ) where
7−
8−import Control.Applicative ((<$>))
9−import Control.Concurrent (threadDelay)
10−import Control.Monad (when, filterM)
11−import System.Time (getClockTime)
12−import Data.Set (Set)
13−import qualified Data.Set as S
14−import System.Directory (getModificationTime, doesFileExist)
15−
16−import Hakyll.Core.Configuration
17−import Hakyll.Core.Resource
18−
19−-- | A preview thread that periodically recompiles the site.
20−--
21−previewPoll :: HakyllConfiguration -- ^ Configuration
22− -> Set Resource -- ^ Resources to watch
23− -> IO () -- ^ Action called when something changes
24− -> IO () -- ^ Can block forever
25−previewPoll _ resources callback = do
26− let files = map unResource $ S.toList resources
27− time <- getClockTime
28− loop files time
29− where
30− delay = 1000000
31− loop files time = do
32− threadDelay delay
33− files' <- filterM doesFileExist files
34− modified <- any (time <) <$> mapM getModificationTime files'
35− when (modified || files' /= files) callback
36− loop files' =<< getClockTime
src/Hakyll.hsdeleted−61
@@ -1,61 +0,0 @@
1−-- | Top-level module exporting all modules that are interesting for the user
2−--
3−module Hakyll
4− ( module Hakyll.Core.Compiler
5− , module Hakyll.Core.Configuration
6− , module Hakyll.Core.Identifier
7− , module Hakyll.Core.Identifier.Pattern
8− , module Hakyll.Core.Resource
9− , module Hakyll.Core.Resource.Provider
10− , module Hakyll.Core.Routes
11− , module Hakyll.Core.Rules
12− , module Hakyll.Core.UnixFilter
13− , module Hakyll.Core.Util.Arrow
14− , module Hakyll.Core.Util.File
15− , module Hakyll.Core.Util.String
16− , module Hakyll.Core.Writable
17− , module Hakyll.Core.Writable.CopyFile
18− , module Hakyll.Core.Writable.WritableTuple
19− , module Hakyll.Main
20− , module Hakyll.Web.Blaze
21− , module Hakyll.Web.CompressCss
22− , module Hakyll.Web.Feed
23− , module Hakyll.Web.Page
24− , module Hakyll.Web.Page.Metadata
25− , module Hakyll.Web.Page.Read
26− , module Hakyll.Web.Pandoc
27− , module Hakyll.Web.Pandoc.FileType
28− , module Hakyll.Web.RelativizeUrls
29− , module Hakyll.Web.Tags
30− , module Hakyll.Web.Template
31− , module Hakyll.Web.Util.Url
32− ) where
33−
34−import Hakyll.Core.Compiler
35−import Hakyll.Core.Configuration
36−import Hakyll.Core.Identifier
37−import Hakyll.Core.Identifier.Pattern
38−import Hakyll.Core.Resource
39−import Hakyll.Core.Resource.Provider
40−import Hakyll.Core.Routes
41−import Hakyll.Core.Rules
42−import Hakyll.Core.UnixFilter
43−import Hakyll.Core.Util.Arrow
44−import Hakyll.Core.Util.File
45−import Hakyll.Core.Util.String
46−import Hakyll.Core.Writable
47−import Hakyll.Core.Writable.CopyFile
48−import Hakyll.Core.Writable.WritableTuple
49−import Hakyll.Main
50−import Hakyll.Web.Blaze
51−import Hakyll.Web.CompressCss
52−import Hakyll.Web.Feed
53−import Hakyll.Web.Page
54−import Hakyll.Web.Page.Metadata
55−import Hakyll.Web.Page.Read
56−import Hakyll.Web.Pandoc
57−import Hakyll.Web.Pandoc.FileType
58−import Hakyll.Web.RelativizeUrls
59−import Hakyll.Web.Tags
60−import Hakyll.Web.Template
61−import Hakyll.Web.Util.Url
src/Hakyll/Core/CompiledItem.hsdeleted−47
@@ -1,47 +0,0 @@
1−-- | A module containing a box datatype representing a compiled item. This
2−-- item can be of any type, given that a few restrictions hold:
3−--
4−-- * we need a 'Typeable' instance to perform type-safe casts;
5−--
6−-- * we need a 'Binary' instance so we can serialize these items to the cache;
7−--
8−-- * we need a 'Writable' instance so the results can be saved.
9−--
10−{-# LANGUAGE ExistentialQuantification, DeriveDataTypeable #-}
11−module Hakyll.Core.CompiledItem
12− ( CompiledItem (..)
13− , compiledItem
14− , unCompiledItem
15− ) where
16−
17−import Data.Binary (Binary)
18−import Data.Typeable (Typeable, cast, typeOf)
19−import Data.Maybe (fromMaybe)
20−
21−import Hakyll.Core.Writable
22−
23−-- | Box type for a compiled item
24−--
25−data CompiledItem = forall a. (Binary a, Typeable a, Writable a)
26− => CompiledItem a
27− deriving (Typeable)
28−
29−instance Writable CompiledItem where
30− write p (CompiledItem x) = write p x
31−
32−-- | Box a value into a 'CompiledItem'
33−--
34−compiledItem :: (Binary a, Typeable a, Writable a)
35− => a
36− -> CompiledItem
37−compiledItem = CompiledItem
38−
39−-- | Unbox a value from a 'CompiledItem'
40−--
41−unCompiledItem :: (Binary a, Typeable a, Writable a)
42− => CompiledItem
43− -> a
44−unCompiledItem (CompiledItem x) = fromMaybe error' $ cast x
45− where
46− error' = error $ "Hakyll.Core.CompiledItem.unCompiledItem: "
47− ++ "unsupported type (got " ++ show (typeOf x) ++ ")"
src/Hakyll/Core/Compiler.hsdeleted−342
@@ -1,342 +0,0 @@
1−-- | A Compiler manages targets and dependencies between targets
2−--
3−-- The most distinguishing property of a 'Compiler' is that it is an Arrow. A
4−-- compiler of the type @Compiler a b@ is simply a compilation phase which takes
5−-- an @a@ as input, and produces a @b@ as output.
6−--
7−-- Compilers are chained using the '>>>' arrow operation. If we have a compiler
8−--
9−-- > getResourceString :: Compiler Resource String
10−--
11−-- which reads the resource, and a compiler
12−--
13−-- > readPage :: Compiler String (Page String)
14−--
15−-- we can chain these two compilers to get a
16−--
17−-- > (getResourceString >>> readPage) :: Compiler Resource (Page String)
18−--
19−-- Most compilers can be created by combining smaller compilers using '>>>'.
20−--
21−-- More advanced constructions are also possible using arrow, and sometimes
22−-- these are needed. For a good introduction to arrow, you can refer to
23−--
24−-- <http://en.wikibooks.org/wiki/Haskell/Understanding_arrows>
25−--
26−-- A construction worth writing a few paragraphs about here are the 'require'
27−-- functions. Different variants of this function are exported here, but they
28−-- all serve more or less the same goal.
29−--
30−-- When you use only '>>>' to chain your compilers, you get a linear pipeline --
31−-- it is not possible to add extra items from other compilers along the way.
32−-- This is where the 'require' functions come in.
33−--
34−-- This function allows you to reference other items, which are then added to
35−-- the pipeline. Let's look at this crappy ASCII illustration which represents
36−-- a pretty common scenario:
37−--
38−-- > read resource >>> pandoc render >>> layout >>> relativize URL's
39−-- >
40−-- > @templates/fancy.html@
41−--
42−-- We want to construct a pipeline of compilers to go from our resource to a
43−-- proper webpage. However, the @layout@ compiler takes more than just the
44−-- rendered page as input: it needs the @templates/fancy.html@ template as well.
45−--
46−-- This is an example of where we need the @require@ function. We can solve
47−-- this using a construction that looks like:
48−--
49−-- > ... >>> pandoc render >>> require >>> layout >>> ...
50−-- > |
51−-- > @templates/fancy.html@ ------/
52−--
53−-- This illustration can help us understand the type signature of 'require'.
54−--
55−-- > require :: (Binary a, Typeable a, Writable a)
56−-- > => Identifier
57−-- > -> (b -> a -> c)
58−-- > -> Compiler b c
59−--
60−-- Let's look at it in detail:
61−--
62−-- > (Binary a, Typeable a, Writable a)
63−--
64−-- These are constraints for the @a@ type. @a@ (the template) needs to have
65−-- certain properties for it to be required.
66−--
67−-- > Identifier
68−--
69−-- This is simply @templates/fancy.html@: the 'Identifier' of the item we want
70−-- to 'require', in other words, the name of the item we want to add to the
71−-- pipeline somehow.
72−--
73−-- > (b -> a -> c)
74−--
75−-- This is a function given by the user, specifying /how/ the two items shall be
76−-- merged. @b@ is the output of the previous compiler, and @a@ is the item we
77−-- just required -- the template. This means @c@ will be the final output of the
78−-- 'require' combinator.
79−--
80−-- > Compiler b c
81−--
82−-- Indeed, we have now constructed a compiler which takes a @b@ and produces a
83−-- @c@. This means that we have a linear pipeline again, thanks to the 'require'
84−-- function. So, the 'require' function actually helps to reduce to complexity
85−-- of Hakyll applications!
86−--
87−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
88−module Hakyll.Core.Compiler
89− ( Compiler
90− , runCompiler
91− , getIdentifier
92− , getRoute
93− , getRouteFor
94− , getResourceString
95− , fromDependency
96− , require_
97− , require
98− , requireA
99− , requireAll_
100− , requireAll
101− , requireAllA
102− , cached
103− , unsafeCompiler
104− , traceShowCompiler
105− , mapCompiler
106− , timedCompiler
107− , byExtension
108− ) where
109−
110−import Prelude hiding ((.), id)
111−import Control.Arrow ((>>>), (&&&), arr)
112−import Control.Applicative ((<$>))
113−import Control.Monad.Reader (ask)
114−import Control.Monad.Trans (liftIO)
115−import Control.Monad.Error (throwError)
116−import Control.Category (Category, (.), id)
117−import Data.Maybe (fromMaybe)
118−import System.FilePath (takeExtension)
119−
120−import Data.Binary (Binary)
121−import Data.Typeable (Typeable)
122−
123−import Hakyll.Core.Identifier
124−import Hakyll.Core.Identifier.Pattern
125−import Hakyll.Core.CompiledItem
126−import Hakyll.Core.Writable
127−import Hakyll.Core.Resource
128−import Hakyll.Core.Resource.Provider
129−import Hakyll.Core.Compiler.Internal
130−import Hakyll.Core.Store
131−import Hakyll.Core.Rules.Internal
132−import Hakyll.Core.Routes
133−import Hakyll.Core.Logger
134−
135−-- | Run a compiler, yielding the resulting target and it's dependencies. This
136−-- version of 'runCompilerJob' also stores the result
137−--
138−runCompiler :: Compiler () CompileRule -- ^ Compiler to run
139− -> Identifier -- ^ Target identifier
140− -> ResourceProvider -- ^ Resource provider
141− -> [Identifier] -- ^ Universe
142− -> Routes -- ^ Route
143− -> Store -- ^ Store
144− -> Bool -- ^ Was the resource modified?
145− -> Logger -- ^ Logger
146− -> IO (Throwing CompileRule) -- ^ Resulting item
147−runCompiler compiler id' provider universe routes store modified logger = do
148− -- Run the compiler job
149− result <- runCompilerJob compiler id' provider universe
150− routes store modified logger
151−
152− -- Inspect the result
153− case result of
154− -- In case we compiled an item, we will store a copy in the cache first,
155− -- before we return control. This makes sure the compiled item can later
156− -- be accessed by e.g. require.
157− Right (CompileRule (CompiledItem x)) ->
158− storeSet store "Hakyll.Core.Compiler.runCompiler" id' x
159−
160− -- Otherwise, we do nothing here
161− _ -> return ()
162−
163− return result
164−
165−-- | Get the identifier of the item that is currently being compiled
166−--
167−getIdentifier :: Compiler a Identifier
168−getIdentifier = fromJob $ const $ CompilerM $ compilerIdentifier <$> ask
169−
170−-- | Get the route we are using for this item
171−--
172−getRoute :: Compiler a (Maybe FilePath)
173−getRoute = getIdentifier >>> getRouteFor
174−
175−-- | Get the route for a specified item
176−--
177−getRouteFor :: Compiler Identifier (Maybe FilePath)
178−getRouteFor = fromJob $ \identifier -> CompilerM $ do
179− routes <- compilerRoutes <$> ask
180− return $ runRoutes routes identifier
181−
182−-- | Get the resource we are compiling as a string
183−--
184−getResourceString :: Compiler Resource String
185−getResourceString = fromJob $ \resource -> CompilerM $ do
186− let identifier = unResource resource
187− provider <- compilerResourceProvider <$> ask
188− if resourceExists provider resource
189− then liftIO $ resourceString provider resource
190− else throwError $ error' identifier
191− where
192− error' id' = "Hakyll.Core.Compiler.getResourceString: resource "
193− ++ show id' ++ " not found"
194−
195−-- | Auxiliary: get a dependency
196−--
197−getDependency :: (Binary a, Writable a, Typeable a)
198− => Identifier -> CompilerM a
199−getDependency id' = CompilerM $ do
200− store <- compilerStore <$> ask
201− result <- liftIO $ storeGet store "Hakyll.Core.Compiler.runCompiler" id'
202− case result of
203− Nothing -> throwError error'
204− Just x -> return x
205− where
206− error' = "Hakyll.Core.Compiler.getDependency: " ++ show id'
207− ++ " not found in the cache, the cache might be corrupted or"
208− ++ " the item you are referring to might not exist"
209−
210−-- | Variant of 'require' which drops the current value
211−--
212−require_ :: (Binary a, Typeable a, Writable a)
213− => Identifier
214− -> Compiler b a
215−require_ identifier =
216− fromDependency identifier >>> fromJob (const $ getDependency identifier)
217−
218−-- | Require another target. Using this function ensures automatic handling of
219−-- dependencies
220−--
221−require :: (Binary a, Typeable a, Writable a)
222− => Identifier
223− -> (b -> a -> c)
224− -> Compiler b c
225−require identifier = requireA identifier . arr . uncurry
226−
227−-- | Arrow-based variant of 'require'
228−--
229−requireA :: (Binary a, Typeable a, Writable a)
230− => Identifier
231− -> Compiler (b, a) c
232− -> Compiler b c
233−requireA identifier = (id &&& require_ identifier >>>)
234−
235−-- | Variant of 'requireAll' which drops the current value
236−--
237−requireAll_ :: (Binary a, Typeable a, Writable a)
238− => Pattern
239− -> Compiler b [a]
240−requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
241− where
242− getDeps = filterMatches pattern
243− requireAll_' = const $ CompilerM $ do
244− deps <- getDeps . compilerUniverse <$> ask
245− mapM (unCompilerM . getDependency) deps
246−
247−-- | Require a number of targets. Using this function ensures automatic handling
248−-- of dependencies
249−--
250−requireAll :: (Binary a, Typeable a, Writable a)
251− => Pattern
252− -> (b -> [a] -> c)
253− -> Compiler b c
254−requireAll pattern = requireAllA pattern . arr . uncurry
255−
256−-- | Arrow-based variant of 'requireAll'
257−--
258−requireAllA :: (Binary a, Typeable a, Writable a)
259− => Pattern
260− -> Compiler (b, [a]) c
261− -> Compiler b c
262−requireAllA pattern = (id &&& requireAll_ pattern >>>)
263−
264−cached :: (Binary a, Typeable a, Writable a)
265− => String
266− -> Compiler Resource a
267− -> Compiler Resource a
268−cached name (Compiler d j) = Compiler d $ const $ CompilerM $ do
269− logger <- compilerLogger <$> ask
270− identifier <- compilerIdentifier <$> ask
271− store <- compilerStore <$> ask
272− modified <- compilerResourceModified <$> ask
273− report logger $ "Checking cache: " ++ if modified then "modified" else "OK"
274− if modified
275− then do v <- unCompilerM $ j $ fromIdentifier identifier
276− liftIO $ storeSet store name identifier v
277− return v
278− else do v <- liftIO $ storeGet store name identifier
279− case v of Just v' -> return v'
280− Nothing -> throwError error'
281− where
282− error' = "Hakyll.Core.Compiler.cached: Cache corrupt!"
283−
284−-- | Create an unsafe compiler from a function in IO
285−--
286−unsafeCompiler :: (a -> IO b) -- ^ Function to lift
287− -> Compiler a b -- ^ Resulting compiler
288−unsafeCompiler f = fromJob $ CompilerM . liftIO . f
289−
290−-- | Compiler for debugging purposes
291−--
292−traceShowCompiler :: Show a => Compiler a a
293−traceShowCompiler = fromJob $ \x -> CompilerM $ do
294− logger <- compilerLogger <$> ask
295− report logger $ show x
296− return x
297−
298−-- | Map over a compiler
299−--
300−mapCompiler :: Compiler a b
301− -> Compiler [a] [b]
302−mapCompiler (Compiler d j) = Compiler d $ mapM j
303−
304−-- | Log and time a compiler
305−--
306−timedCompiler :: String -- ^ Message
307− -> Compiler a b -- ^ Compiler to time
308− -> Compiler a b -- ^ Resulting compiler
309−timedCompiler msg (Compiler d j) = Compiler d $ \x -> CompilerM $ do
310− logger <- compilerLogger <$> ask
311− timed logger msg $ unCompilerM $ j x
312−
313−-- | Choose a compiler by extension
314−--
315−-- Example:
316−--
317−-- > route "css/*" $ setExtension "css"
318−-- > compile "css/*" $ byExtension (error "Not a (S)CSS file")
319−-- > [ (".css", compressCssCompiler)
320−-- > , (".scss", sass)
321−-- > ]
322−--
323−-- This piece of code will select the @compressCssCompiler@ for @.css@ files,
324−-- and the @sass@ compiler (defined elsewhere) for @.scss@ files.
325−--
326−byExtension :: Compiler a b -- ^ Default compiler
327− -> [(String, Compiler a b)] -- ^ Choices
328− -> Compiler a b -- ^ Resulting compiler
329−byExtension defaultCompiler choices = Compiler deps job
330− where
331− -- Lookup the compiler, give an error when it is not found
332− lookup' identifier =
333− let extension = takeExtension $ toFilePath identifier
334− in fromMaybe defaultCompiler $ lookup extension choices
335− -- Collect the dependencies of the choice
336− deps = do
337− identifier <- dependencyIdentifier <$> ask
338− compilerDependencies $ lookup' identifier
339− -- Collect the job of the choice
340− job x = CompilerM $ do
341− identifier <- compilerIdentifier <$> ask
342− unCompilerM $ compilerJob (lookup' identifier) x
src/Hakyll/Core/Compiler/Internal.hsdeleted−156
@@ -1,156 +0,0 @@
1−-- | Internally used compiler module
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−module Hakyll.Core.Compiler.Internal
5− ( Dependencies
6− , DependencyEnvironment (..)
7− , CompilerEnvironment (..)
8− , Throwing
9− , CompilerM (..)
10− , Compiler (..)
11− , runCompilerJob
12− , runCompilerDependencies
13− , fromJob
14− , fromDependencies
15− , fromDependency
16− ) where
17−
18−import Prelude hiding ((.), id)
19−import Control.Applicative (Applicative, pure, (<*>), (<$>))
20−import Control.Monad.Reader (ReaderT, Reader, ask, runReaderT, runReader)
21−import Control.Monad.Error (ErrorT, runErrorT)
22−import Control.Monad ((<=<), liftM2)
23−import Data.Set (Set)
24−import qualified Data.Set as S
25−import Control.Category (Category, (.), id)
26−import Control.Arrow (Arrow, ArrowChoice, arr, first, left)
27−
28−import Hakyll.Core.Identifier
29−import Hakyll.Core.Resource.Provider
30−import Hakyll.Core.Store
31−import Hakyll.Core.Routes
32−import Hakyll.Core.Logger
33−
34−-- | A set of dependencies
35−--
36−type Dependencies = Set Identifier
37−
38−-- | Environment in which the dependency analyzer runs
39−--
40−data DependencyEnvironment = DependencyEnvironment
41− { -- | Target identifier
42− dependencyIdentifier :: Identifier
43− , -- | List of available identifiers we can depend upon
44− dependencyUniverse :: [Identifier]
45− }
46−
47−-- | Environment in which a compiler runs
48−--
49−data CompilerEnvironment = CompilerEnvironment
50− { -- | Target identifier
51− compilerIdentifier :: Identifier
52− , -- | Resource provider
53− compilerResourceProvider :: ResourceProvider
54− , -- | List of all known identifiers
55− compilerUniverse :: [Identifier]
56− , -- | Site routes
57− compilerRoutes :: Routes
58− , -- | Compiler store
59− compilerStore :: Store
60− , -- | Flag indicating if the underlying resource was modified
61− compilerResourceModified :: Bool
62− , -- | Logger
63− compilerLogger :: Logger
64− }
65−
66−-- | A calculation possibly throwing an error
67−--
68−type Throwing a = Either String a
69−
70−-- | The compiler monad
71−--
72−newtype CompilerM a = CompilerM
73− { unCompilerM :: ErrorT String (ReaderT CompilerEnvironment IO) a
74− } deriving (Monad, Functor, Applicative)
75−
76−-- | The compiler arrow
77−--
78−data Compiler a b = Compiler
79− { compilerDependencies :: Reader DependencyEnvironment Dependencies
80− , compilerJob :: a -> CompilerM b
81− }
82−
83−instance Functor (Compiler a) where
84− fmap f ~(Compiler d j) = Compiler d $ fmap f . j
85−
86−instance Applicative (Compiler a) where
87− pure = Compiler (return S.empty) . const . return
88− ~(Compiler d1 f) <*> ~(Compiler d2 j) =
89− Compiler (liftM2 S.union d1 d2) $ \x -> f x <*> j x
90−
91−instance Category Compiler where
92− id = Compiler (return S.empty) return
93− ~(Compiler d1 j1) . ~(Compiler d2 j2) =
94− Compiler (liftM2 S.union d1 d2) (j1 <=< j2)
95−
96−instance Arrow Compiler where
97− arr f = Compiler (return S.empty) (return . f)
98− first ~(Compiler d j) = Compiler d $ \(x, y) -> do
99− x' <- j x
100− return (x', y)
101−
102−instance ArrowChoice Compiler where
103− left ~(Compiler d j) = Compiler d $ \e -> case e of
104− Left l -> Left <$> j l
105− Right r -> Right <$> return r
106−
107−-- | Run a compiler, yielding the resulting target
108−--
109−runCompilerJob :: Compiler () a -- ^ Compiler to run
110− -> Identifier -- ^ Target identifier
111− -> ResourceProvider -- ^ Resource provider
112− -> [Identifier] -- ^ Universe
113− -> Routes -- ^ Route
114− -> Store -- ^ Store
115− -> Bool -- ^ Was the resource modified?
116− -> Logger -- ^ Logger
117− -> IO (Throwing a) -- ^ Result
118−runCompilerJob compiler id' provider universe route store modified logger =
119− runReaderT (runErrorT $ unCompilerM $ compilerJob compiler ()) env
120− where
121− env = CompilerEnvironment
122− { compilerIdentifier = id'
123− , compilerResourceProvider = provider
124− , compilerUniverse = universe
125− , compilerRoutes = route
126− , compilerStore = store
127− , compilerResourceModified = modified
128− , compilerLogger = logger
129− }
130−
131−runCompilerDependencies :: Compiler () a
132− -> Identifier
133− -> [Identifier]
134− -> Dependencies
135−runCompilerDependencies compiler identifier universe =
136− runReader (compilerDependencies compiler) env
137− where
138− env = DependencyEnvironment
139− { dependencyIdentifier = identifier
140− , dependencyUniverse = universe
141− }
142−
143−fromJob :: (a -> CompilerM b)
144− -> Compiler a b
145−fromJob = Compiler (return S.empty)
146−
147−fromDependencies :: (Identifier -> [Identifier] -> [Identifier])
148− -> Compiler b b
149−fromDependencies collectDeps = flip Compiler return $ do
150− DependencyEnvironment identifier universe <- ask
151− return $ S.fromList $ collectDeps identifier universe
152−
153−-- | Wait until another compiler has finished before running this compiler
154−--
155−fromDependency :: Identifier -> Compiler a a
156−fromDependency = fromDependencies . const . const . return
src/Hakyll/Core/Configuration.hsdeleted−44
@@ -1,44 +0,0 @@
1−-- | Exports a datastructure for the top-level hakyll configuration
2−--
3−module Hakyll.Core.Configuration
4− ( HakyllConfiguration (..)
5− , defaultHakyllConfiguration
6− ) where
7−
8−import System.FilePath (takeFileName)
9−import Data.List (isPrefixOf, isSuffixOf)
10−
11−data HakyllConfiguration = HakyllConfiguration
12− { -- | Directory in which the output written
13− destinationDirectory :: FilePath
14− , -- | Directory where hakyll's internal store is kept
15− storeDirectory :: FilePath
16− , -- | Function to determine ignored files
17− --
18− -- In 'defaultHakyllConfiguration', the following files are ignored:
19− --
20− -- * files starting with a @.@
21− --
22− -- * files ending with a @~@
23− --
24− -- * files ending with @.swp@
25− --
26− ignoreFile :: FilePath -> Bool
27− }
28−
29−-- | Default configuration for a hakyll application
30−--
31−defaultHakyllConfiguration :: HakyllConfiguration
32−defaultHakyllConfiguration = HakyllConfiguration
33− { destinationDirectory = "_site"
34− , storeDirectory = "_cache"
35− , ignoreFile = ignoreFile'
36− }
37− where
38− ignoreFile' path
39− | "." `isPrefixOf` fileName = True
40− | "~" `isSuffixOf` fileName = True
41− | ".swp" `isSuffixOf` fileName = True
42− | otherwise = False
43− where
44− fileName = takeFileName path
src/Hakyll/Core/DependencyAnalyzer.hsdeleted−155
@@ -1,155 +0,0 @@
1−module Hakyll.Core.DependencyAnalyzer
2− ( DependencyAnalyzer (..)
3− , Signal (..)
4− , makeDependencyAnalyzer
5− , step
6− , stepAll
7− ) where
8−
9−import Prelude hiding (reverse)
10−import qualified Prelude as P (reverse)
11−import Control.Arrow (first)
12−import Data.Set (Set)
13−import qualified Data.Set as S
14−import Data.Monoid (Monoid, mappend, mempty)
15−
16−import Hakyll.Core.DirectedGraph
17−
18−-- | This data structure represents the state of the dependency analyzer. It
19−-- holds a complete graph in 'analyzerGraph', which always contains all items,
20−-- whether they are to be compiled or not.
21−--
22−-- The 'analyzerRemains' fields holds the items that still need to be compiled,
23−-- and 'analyzerDone' holds the items which are already compiled. This means
24−-- that initally, 'analyzerDone' is empty and 'analyzerRemains' contains the
25−-- items which are out-of-date (or items which have out-of-date dependencies).
26−--
27−-- We also hold the dependency graph from the previous run because we need it
28−-- when we want to determine when an item is out-of-date. An item is out-of-date
29−-- when:
30−--
31−-- * the resource from which it compiles is out-of-date, or;
32−--
33−-- * any of it's dependencies is out-of-date, or;
34−--
35−-- * it's set of dependencies has changed since the previous run.
36−--
37−data DependencyAnalyzer a = DependencyAnalyzer
38− { -- | The complete dependency graph
39− analyzerGraph :: DirectedGraph a
40− , -- | A set of items yet to be compiled
41− analyzerRemains :: Set a
42− , -- | A set of items already compiled
43− analyzerDone :: Set a
44− , -- | The dependency graph from the previous run
45− analyzerPreviousGraph :: DirectedGraph a
46− } deriving (Show)
47−
48−data Signal a = Build a
49− | Cycle [a]
50− | Done
51−
52−instance (Ord a, Show a) => Monoid (DependencyAnalyzer a) where
53− mempty = DependencyAnalyzer mempty mempty mempty mempty
54− mappend x y = growRemains $ DependencyAnalyzer
55− (analyzerGraph x `mappend` analyzerGraph y)
56− (analyzerRemains x `mappend` analyzerRemains y)
57− (analyzerDone x `mappend` analyzerDone y)
58− (analyzerPreviousGraph x `mappend` analyzerPreviousGraph y)
59−
60−-- | Construct a dependency analyzer
61−--
62−makeDependencyAnalyzer :: (Ord a, Show a)
63− => DirectedGraph a -- ^ The dependency graph
64− -> (a -> Bool) -- ^ Is an item out-of-date?
65− -> DirectedGraph a -- ^ The old dependency graph
66− -> DependencyAnalyzer a -- ^ Resulting analyzer
67−makeDependencyAnalyzer graph isOutOfDate prev =
68− growRemains $ DependencyAnalyzer graph remains S.empty prev
69− where
70− -- Construct the remains set by filtering using the given predicate
71− remains = S.fromList $ filter isOutOfDate $ map fst $ toList graph
72−
73−-- | The 'analyzerRemains' field of a 'DependencyAnalyzer' is supposed to
74−-- contain all out-of-date items, including the items with out-of-date
75−-- dependencies. However, it is easier to just set up the directly out-of-date
76−-- items initially -- and then grow the remains fields.
77−--
78−-- This function assumes the 'analyzerRemains' fields in incomplete, and tries
79−-- to correct it. Running it when the field is complete has no effect -- but it
80−-- is a pretty expensive function, and it should be used with care.
81−--
82−growRemains :: (Ord a, Show a) => DependencyAnalyzer a -> DependencyAnalyzer a
83−growRemains (DependencyAnalyzer graph remains done prev) =
84− (DependencyAnalyzer graph remains' done prev)
85− where
86− -- Grow the remains set using the indirect and changedDeps values, then
87− -- filter out the items already done
88− remains' = S.filter (`S.notMember` done) indirect
89−
90− -- Select the nodes which are reachable from the remaining nodes in the
91− -- reversed dependency graph: these are the indirectly out-of-date items
92− indirect = reachableNodes (remains `S.union` changedDeps) $ reverse graph
93−
94− -- For all nodes in the graph, check which items have a different dependency
95− -- set compared to the previous run
96− changedDeps = S.fromList $ map fst $
97− filter (uncurry (/=) . first (`neighbours` prev)) $ toList graph
98−
99−-- | Step a dependency analyzer
100−--
101−step :: (Ord a, Show a) => DependencyAnalyzer a -> (Signal a, DependencyAnalyzer a)
102−step analyzer@(DependencyAnalyzer graph remains done prev)
103− -- No remaining items
104− | S.null remains = (Done, analyzer)
105− -- An item remains, let's find a ready item
106− | otherwise =
107− let item = S.findMin remains
108− in case findReady analyzer item of
109− Done -> (Done, analyzer)
110− Cycle c -> (Cycle c, analyzer)
111− -- A ready item was found, signal a build
112− Build build ->
113− let remains' = S.delete build remains
114− done' = S.insert build done
115− in (Build build, DependencyAnalyzer graph remains' done' prev)
116−
117−-- | Step until done, creating a set of items we need to build -- mostly used
118−-- for debugging purposes
119−--
120−stepAll :: (Ord a, Show a) => DependencyAnalyzer a -> Maybe (Set a)
121−stepAll = stepAll' S.empty
122− where
123− stepAll' xs analyzer = case step analyzer of
124− (Build x, analyzer') -> stepAll' (S.insert x xs) analyzer'
125− (Done, _) -> Just xs
126− (Cycle _, _) -> Nothing
127−
128−-- | Find an item ready to be compiled
129−--
130−findReady :: (Ord a, Show a) => DependencyAnalyzer a -> a -> Signal a
131−findReady analyzer = findReady' [] S.empty
132− where
133− -- The dependency graph
134− graph = analyzerGraph analyzer
135−
136− -- Items to do
137− todo = analyzerRemains analyzer `S.difference` analyzerDone analyzer
138−
139− -- Worker
140− findReady' stack visited item
141− -- We already visited this item, the cycle is the reversed stack
142− | item `S.member` visited = Cycle $ P.reverse stack'
143− -- Look at the neighbours we to do
144− | otherwise = case filter (`S.member` todo) neighbours' of
145− -- No neighbours available to be done: it's ready!
146− [] -> Build item
147− -- At least one neighbour is available, search for that one
148− (x : _) -> findReady' stack' visited' x
149− where
150− -- Our neighbours
151− neighbours' = S.toList $ neighbours item graph
152−
153− -- The new visited stack/set
154− stack' = item : stack
155− visited' = S.insert item visited
src/Hakyll/Core/DirectedGraph.hsdeleted−84
@@ -1,84 +0,0 @@
1−-- | Representation of a directed graph. In Hakyll, this is used for dependency
2−-- tracking.
3−--
4−module Hakyll.Core.DirectedGraph
5− ( DirectedGraph
6− , fromList
7− , toList
8− , member
9− , nodes
10− , neighbours
11− , reverse
12− , reachableNodes
13− ) where
14−
15−import Prelude hiding (reverse)
16−import Control.Arrow (second)
17−import Data.Monoid (mconcat)
18−import Data.Set (Set)
19−import Data.Maybe (fromMaybe)
20−import qualified Data.Map as M
21−import qualified Data.Set as S
22−
23−import Hakyll.Core.DirectedGraph.Internal
24−
25−-- | Construction of directed graphs
26−--
27−fromList :: Ord a
28− => [(a, Set a)] -- ^ List of (node, reachable neighbours)
29− -> DirectedGraph a -- ^ Resulting directed graph
30−fromList = DirectedGraph . M.fromList . map (\(t, d) -> (t, Node t d))
31−
32−-- | Deconstruction of directed graphs
33−--
34−toList :: DirectedGraph a
35− -> [(a, Set a)]
36−toList = map (second nodeNeighbours) . M.toList . unDirectedGraph
37−
38−-- | Check if a node lies in the given graph
39−--
40−member :: Ord a
41− => a -- ^ Node to check for
42− -> DirectedGraph a -- ^ Directed graph to check in
43− -> Bool -- ^ If the node lies in the graph
44−member n = M.member n . unDirectedGraph
45−
46−-- | Get all nodes in the graph
47−--
48−nodes :: Ord a
49− => DirectedGraph a -- ^ Graph to get the nodes from
50− -> Set a -- ^ All nodes in the graph
51−nodes = M.keysSet . unDirectedGraph
52−
53−-- | Get a set of reachable neighbours from a directed graph
54−--
55−neighbours :: Ord a
56− => a -- ^ Node to get the neighbours of
57− -> DirectedGraph a -- ^ Graph to search in
58− -> Set a -- ^ Set containing the neighbours
59−neighbours x = fromMaybe S.empty . fmap nodeNeighbours
60− . M.lookup x . unDirectedGraph
61−
62−-- | Reverse a directed graph (i.e. flip all edges)
63−--
64−reverse :: Ord a
65− => DirectedGraph a
66− -> DirectedGraph a
67−reverse = mconcat . map reverse' . M.toList . unDirectedGraph
68− where
69− reverse' (id', Node _ neighbours') = fromList $
70− zip (S.toList neighbours') $ repeat $ S.singleton id'
71−
72−-- | Find all reachable nodes from a given set of nodes in the directed graph
73−--
74−reachableNodes :: Ord a => Set a -> DirectedGraph a -> Set a
75−reachableNodes set graph = reachable (setNeighbours set) set
76− where
77− reachable next visited
78− | S.null next = visited
79− | otherwise = reachable (sanitize neighbours') (next `S.union` visited)
80− where
81− sanitize = S.filter (`S.notMember` visited)
82− neighbours' = setNeighbours (sanitize next)
83−
84− setNeighbours = S.unions . map (`neighbours` graph) . S.toList
src/Hakyll/Core/DirectedGraph/Dot.hsdeleted−32
@@ -1,32 +0,0 @@
1−-- | Dump a directed graph in dot format. Used for debugging purposes
2−--
3−module Hakyll.Core.DirectedGraph.Dot
4− ( toDot
5− , writeDot
6− ) where
7−
8−import Hakyll.Core.DirectedGraph
9−import qualified Data.Set as S
10−
11−-- | Convert a directed graph into dot format for debugging purposes
12−--
13−toDot :: Ord a
14− => (a -> String) -- ^ Convert nodes to dot names
15− -> DirectedGraph a -- ^ Graph to dump
16− -> String -- ^ Resulting string
17−toDot showTag graph = unlines $ concat
18− [ return "digraph dependencies {"
19− , map showNode (S.toList $ nodes graph)
20− , concatMap showEdges (S.toList $ nodes graph)
21− , return "}"
22− ]
23− where
24− showNode node = " \"" ++ showTag node ++ "\";"
25− showEdges node = map (showEdge node) $ S.toList $ neighbours node graph
26− showEdge x y = " \"" ++ showTag x ++ "\" -> \"" ++ showTag y ++ "\";"
27−
28−-- | Write out the @.dot@ file to a given file path. See 'toDot' for more
29−-- information.
30−--
31−writeDot :: Ord a => FilePath -> (a -> String) -> DirectedGraph a -> IO ()
32−writeDot path showTag = writeFile path . toDot showTag
src/Hakyll/Core/DirectedGraph/Internal.hsdeleted−52
@@ -1,52 +0,0 @@
1−-- | Internal structure of the DirectedGraph type. Not exported outside of the
2−-- library.
3−--
4−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
5−module Hakyll.Core.DirectedGraph.Internal
6− ( Node (..)
7− , DirectedGraph (..)
8− ) where
9−
10−import Prelude hiding (reverse, filter)
11−import Control.Applicative ((<$>), (<*>))
12−import Data.Monoid (Monoid, mempty, mappend)
13−import Data.Set (Set)
14−import Data.Map (Map)
15−import qualified Data.Map as M
16−import qualified Data.Set as S
17−
18−import Data.Binary (Binary, put, get)
19−import Data.Typeable (Typeable)
20−
21−-- | A node in the directed graph
22−--
23−data Node a = Node
24− { nodeTag :: a -- ^ Tag identifying the node
25− , nodeNeighbours :: Set a -- ^ Edges starting at this node
26− } deriving (Show, Typeable)
27−
28−instance (Binary a, Ord a) => Binary (Node a) where
29− put (Node t n) = put t >> put n
30− get = Node <$> get <*> get
31−
32−-- | Append two nodes. Useful for joining graphs.
33−--
34−appendNodes :: Ord a => Node a -> Node a -> Node a
35−appendNodes (Node t1 n1) (Node t2 n2)
36− | t1 /= t2 = error'
37− | otherwise = Node t1 (n1 `S.union` n2)
38− where
39− error' = error $ "Hakyll.Core.DirectedGraph.Internal.appendNodes: "
40− ++ "Appending differently tagged nodes"
41−
42−-- | Type used to represent a directed graph
43−--
44−newtype DirectedGraph a = DirectedGraph {unDirectedGraph :: Map a (Node a)}
45− deriving (Show, Binary, Typeable)
46−
47−-- | Allow users to concatenate different graphs
48−--
49−instance Ord a => Monoid (DirectedGraph a) where
50− mempty = DirectedGraph M.empty
51− mappend (DirectedGraph m1) (DirectedGraph m2) = DirectedGraph $
52− M.unionWith appendNodes m1 m2
src/Hakyll/Core/Identifier.hsdeleted−82
@@ -1,82 +0,0 @@
1−-- | An identifier is a type used to uniquely identify a resource, target...
2−--
3−-- One can think of an identifier as something similar to a file path. An
4−-- identifier is a path as well, with the different elements in the path
5−-- separated by @/@ characters. Examples of identifiers are:
6−--
7−-- * @posts/foo.markdown@
8−--
9−-- * @index@
10−--
11−-- * @error/404@
12−--
13−-- The most important difference between an 'Identifier' and a file path is that
14−-- the identifier for an item is not necesserily the file path.
15−--
16−-- For example, we could have an @index@ identifier, generated by Hakyll. The
17−-- actual file path would be @index.html@, but we identify it using @index@.
18−--
19−-- @posts/foo.markdown@ could be an identifier of an item that is rendered to
20−-- @posts/foo.html@. In this case, the identifier is the name of the source
21−-- file of the page.
22−--
23−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
24−module Hakyll.Core.Identifier
25− ( Identifier (..)
26− , parseIdentifier
27− , toFilePath
28− , setGroup
29− ) where
30−
31−import Control.Arrow (second)
32−import Control.Applicative ((<$>), (<*>))
33−import Control.Monad (mplus)
34−import Data.Monoid (Monoid, mempty, mappend)
35−import Data.List (intercalate)
36−
37−import Data.Binary (Binary, get, put)
38−import GHC.Exts (IsString, fromString)
39−import Data.Typeable (Typeable)
40−
41−-- | An identifier used to uniquely identify a value
42−--
43−data Identifier = Identifier
44− { identifierGroup :: Maybe String
45− , identifierPath :: String
46− } deriving (Eq, Ord, Typeable)
47−
48−instance Monoid Identifier where
49− mempty = Identifier Nothing ""
50− Identifier g1 p1 `mappend` Identifier g2 p2 =
51− Identifier (g1 `mplus` g2) (p1 `mappend` p2)
52−
53−instance Binary Identifier where
54− put (Identifier g p) = put g >> put p
55− get = Identifier <$> get <*> get
56−
57−instance Show Identifier where
58− show i@(Identifier Nothing _) = toFilePath i
59− show i@(Identifier (Just g) _) = toFilePath i ++ " (" ++ g ++ ")"
60−
61−instance IsString Identifier where
62− fromString = parseIdentifier
63−
64−-- | Parse an identifier from a string
65−--
66−parseIdentifier :: String -> Identifier
67−parseIdentifier = Identifier Nothing
68− . intercalate "/" . filter (not . null) . split'
69− where
70− split' [] = [[]]
71− split' str = let (pre, post) = second (drop 1) $ break (== '/') str
72− in pre : split' post
73−
74−-- | Convert an identifier to a relative 'FilePath'
75−--
76−toFilePath :: Identifier -> FilePath
77−toFilePath = identifierPath
78−
79−-- | Set the identifier group for some identifier
80−--
81−setGroup :: Maybe String -> Identifier -> Identifier
82−setGroup g (Identifier _ p) = Identifier g p
src/Hakyll/Core/Identifier/Pattern.hsdeleted−189
@@ -1,189 +0,0 @@
1−-- | Module providing pattern matching and capturing on 'Identifier's.
2−-- 'Pattern's come in two kinds:
3−--
4−-- * Simple glob patterns, like @foo\/*@;
5−--
6−-- * Custom, arbitrary predicates of the type @Identifier -> Bool@.
7−--
8−-- They both have advantages and disadvantages. By default, globs are used,
9−-- unless you construct your 'Pattern' using the 'predicate' function.
10−--
11−-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
12−-- only match the exact @foo\/bar@ identifier.
13−--
14−-- To match more than one identifier, there are different captures that one can
15−-- use:
16−--
17−-- * @*@: matches at most one element of an identifier;
18−--
19−-- * @**@: matches one or more elements of an identifier.
20−--
21−-- Some examples:
22−--
23−-- * @foo\/*@ will match @foo\/bar@ and @foo\/foo@, but not @foo\/bar\/qux@;
24−--
25−-- * @**@ will match any identifier;
26−--
27−-- * @foo\/**@ will match @foo\/bar@ and @foo\/bar\/qux@, but not @bar\/foo@;
28−--
29−-- * @foo\/*.html@ will match all HTML files in the @foo\/@ directory.
30−--
31−-- The 'capture' function allows the user to get access to the elements captured
32−-- by the capture elements in the pattern.
33−--
34−module Hakyll.Core.Identifier.Pattern
35− ( Pattern
36− , parseGlob
37− , predicate
38− , regex
39− , inGroup
40− , matches
41− , filterMatches
42− , capture
43− , fromCapture
44− , fromCaptures
45− ) where
46−
47−import Data.List (isPrefixOf, inits, tails)
48−import Control.Arrow ((&&&), (>>>))
49−import Control.Monad (msum)
50−import Data.Maybe (isJust, fromMaybe)
51−import Data.Monoid (Monoid, mempty, mappend)
52−
53−import GHC.Exts (IsString, fromString)
54−import Text.Regex.PCRE ((=~~))
55−
56−import Hakyll.Core.Identifier
57−
58−-- | One base element of a pattern
59−--
60−data GlobComponent = Capture
61− | CaptureMany
62− | Literal String
63− deriving (Eq, Show)
64−
65−-- | Type that allows matching on identifiers
66−--
67−data Pattern = Glob [GlobComponent]
68− | Predicate (Identifier -> Bool)
69−
70−instance IsString Pattern where
71− fromString = parseGlob
72−
73−instance Monoid Pattern where
74− mempty = Predicate (const True)
75− g@(Glob _) `mappend` x = Predicate (matches g) `mappend` x
76− x `mappend` g@(Glob _) = x `mappend` Predicate (matches g)
77− Predicate f `mappend` Predicate g = Predicate $ \i -> f i && g i
78−
79−-- | Parse a pattern from a string
80−--
81−parseGlob :: String -> Pattern
82−parseGlob = Glob . parse'
83− where
84− parse' str =
85− let (chunk, rest) = break (`elem` "\\*") str
86− in case rest of
87− ('\\' : x : xs) -> Literal (chunk ++ [x]) : parse' xs
88− ('*' : '*' : xs) -> Literal chunk : CaptureMany : parse' xs
89− ('*' : xs) -> Literal chunk : Capture : parse' xs
90− xs -> Literal chunk : Literal xs : []
91−
92−-- | Create a 'Pattern' from an arbitrary predicate
93−--
94−-- Example:
95−--
96−-- > predicate (\i -> matches "foo/*" i && not (matches "foo/bar" i))
97−--
98−predicate :: (Identifier -> Bool) -> Pattern
99−predicate = Predicate
100−
101−-- | Create a 'Pattern' from a regex
102−--
103−-- Example:
104−--
105−-- > regex "^foo/[^x]*$
106−--
107−regex :: String -> Pattern
108−regex str = predicate $ fromMaybe False . (=~~ str) . toFilePath
109−
110−-- | Create a 'Pattern' which matches if the identifier is in a certain group
111−-- (or in no group)
112−--
113−inGroup :: Maybe String -> Pattern
114−inGroup group = predicate $ (== group) . identifierGroup
115−
116−-- | Check if an identifier matches a pattern
117−--
118−matches :: Pattern -> Identifier -> Bool
119−matches (Glob p) = isJust . capture (Glob p)
120−matches (Predicate p) = (p $)
121−
122−-- | Given a list of identifiers, retain only those who match the given pattern
123−--
124−filterMatches :: Pattern -> [Identifier] -> [Identifier]
125−filterMatches = filter . matches
126−
127−-- | Split a list at every possible point, generate a list of (init, tail)
128−-- cases. The result is sorted with inits decreasing in length.
129−--
130−splits :: [a] -> [([a], [a])]
131−splits = inits &&& tails >>> uncurry zip >>> reverse
132−
133−-- | Match a glob against a pattern, generating a list of captures
134−--
135−capture :: Pattern -> Identifier -> Maybe [String]
136−capture (Glob p) (Identifier _ i) = capture' p i
137−capture (Predicate _) _ = Nothing
138−
139−-- | Internal verion of 'capture'
140−--
141−capture' :: [GlobComponent] -> String -> Maybe [String]
142−capture' [] [] = Just [] -- An empty match
143−capture' [] _ = Nothing -- No match
144−capture' (Literal l : ms) str
145− -- Match the literal against the string
146− | l `isPrefixOf` str = capture' ms $ drop (length l) str
147− | otherwise = Nothing
148−capture' (Capture : ms) str =
149− -- Match until the next /
150− let (chunk, rest) = break (== '/') str
151− in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
152−capture' (CaptureMany : ms) str =
153− -- Match everything
154− msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
155−
156−-- | Create an identifier from a pattern by filling in the captures with a given
157−-- string
158−--
159−-- Example:
160−--
161−-- > fromCapture (parseGlob "tags/*") "foo"
162−--
163−-- Result:
164−--
165−-- > "tags/foo"
166−--
167−fromCapture :: Pattern -> String -> Identifier
168−fromCapture pattern = fromCaptures pattern . repeat
169−
170−-- | Create an identifier from a pattern by filling in the captures with the
171−-- given list of strings
172−--
173−fromCaptures :: Pattern -> [String] -> Identifier
174−fromCaptures (Glob p) = Identifier Nothing . fromCaptures' p
175−fromCaptures (Predicate _) = error $
176− "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures called on a " ++
177− "predicate instead of a glob"
178−
179−-- | Internally used version of 'fromCaptures'
180−--
181−fromCaptures' :: [GlobComponent] -> [String] -> String
182−fromCaptures' [] _ = mempty
183−fromCaptures' (m : ms) [] = case m of
184− Literal l -> l `mappend` fromCaptures' ms []
185− _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
186− ++ "identifier list exhausted"
187−fromCaptures' (m : ms) ids@(i : is) = case m of
188− Literal l -> l `mappend` fromCaptures' ms ids
189− _ -> i `mappend` fromCaptures' ms is
src/Hakyll/Core/Logger.hsdeleted−99
@@ -1,99 +0,0 @@
1−-- | Produce pretty, thread-safe logs
2−--
3−{-# LANGUAGE BangPatterns #-}
4−module Hakyll.Core.Logger
5− ( Logger
6− , makeLogger
7− , flushLogger
8− , section
9− , timed
10− , report
11− , thrown
12− ) where
13−
14−import Control.Monad (forever)
15−import Control.Monad.Trans (MonadIO, liftIO)
16−import Control.Applicative ((<$>), (<*>))
17−import Control.Concurrent (forkIO)
18−import Control.Concurrent.Chan.Strict (Chan, newChan, readChan, writeChan)
19−import Control.Concurrent.MVar.Strict (MVar, newEmptyMVar, takeMVar, putMVar)
20−import Text.Printf (printf)
21−
22−import Data.Time (getCurrentTime, diffUTCTime)
23−
24−-- | Logger structure. Very complicated.
25−--
26−data Logger = Logger
27− { loggerChan :: Chan (Maybe String) -- Nothing marks the end
28− , loggerSync :: MVar () -- Used for sync on quit
29− }
30−
31−-- | Create a new logger
32−--
33−makeLogger :: IO Logger
34−makeLogger = do
35− logger <- Logger <$> newChan <*> newEmptyMVar
36− _ <- forkIO $ loggerThread logger
37− return logger
38− where
39− loggerThread logger = forever $ do
40− msg <- readChan $ loggerChan logger
41− case msg of
42− -- Stop: sync
43− Nothing -> putMVar (loggerSync logger) ()
44− -- Print and continue
45− Just m -> putStrLn m
46−
47−-- | Flush the logger (blocks until flushed)
48−--
49−flushLogger :: Logger -> IO ()
50−flushLogger logger = do
51− writeChan (loggerChan logger) Nothing
52− () <- takeMVar $ loggerSync logger
53− return ()
54−
55−-- | Send a raw message to the logger
56−--
57−message :: Logger -> String -> IO ()
58−message logger = writeChan (loggerChan logger) . Just
59−
60−-- | Start a section in the log
61−--
62−section :: MonadIO m
63− => Logger -- ^ Logger
64− -> String -- ^ Section name
65− -> m () -- ^ No result
66−section logger = liftIO . message logger
67−
68−-- | Execute a monadic action and log the duration
69−--
70−timed :: MonadIO m
71− => Logger -- ^ Logger
72− -> String -- ^ Message
73− -> m a -- ^ Action
74− -> m a -- ^ Timed and logged action
75−timed logger msg action = do
76− start <- liftIO getCurrentTime
77− !result <- action
78− stop <- liftIO getCurrentTime
79− let diff = fromEnum $ diffUTCTime stop start
80− ms = diff `div` 10 ^ (9 :: Int)
81− formatted = printf " [%4dms] %s" ms msg
82− liftIO $ message logger formatted
83− return result
84−
85−-- | Log something at the same level as 'timed', but without the timing
86−--
87−report :: MonadIO m
88− => Logger -- ^ Logger
89− -> String -- ^ Message
90− -> m () -- ^ No result
91−report logger msg = liftIO $ message logger $ " [ ] " ++ msg
92−
93−-- | Log an error that was thrown in the compilation phase
94−--
95−thrown :: MonadIO m
96− => Logger -- ^ Logger
97− -> String -- ^ Message
98− -> m () -- ^ No result
99−thrown logger msg = liftIO $ message logger $ " [ ERROR] " ++ msg
src/Hakyll/Core/Resource.hsdeleted−24
@@ -1,24 +0,0 @@
1−-- | Module exporting the simple 'Resource' type
2−--
3−module Hakyll.Core.Resource
4− ( Resource (..)
5− , fromIdentifier
6− , toIdentifier
7− ) where
8−
9−import Hakyll.Core.Identifier
10−
11−-- | A resource
12−--
13−newtype Resource = Resource {unResource :: String}
14− deriving (Eq, Show, Ord)
15−
16−-- | Create a resource from an identifier
17−--
18−fromIdentifier :: Identifier -> Resource
19−fromIdentifier = Resource . toFilePath
20−
21−-- | Map the resource to an identifier. Note that the group will not be set!
22−--
23−toIdentifier :: Resource -> Identifier
24−toIdentifier = parseIdentifier . unResource
src/Hakyll/Core/Resource/Provider.hsdeleted−101
@@ -1,101 +0,0 @@
1−-- | This module provides an API for resource providers. Resource providers
2−-- allow Hakyll to get content from resources; the type of resource depends on
3−-- the concrete instance.
4−--
5−-- A resource is represented by the 'Resource' type. This is basically just a
6−-- newtype wrapper around 'Identifier' -- but it has an important effect: it
7−-- guarantees that a resource with this identifier can be provided by one or
8−-- more resource providers.
9−--
10−-- Therefore, it is not recommended to read files directly -- you should use the
11−-- provided 'Resource' methods.
12−--
13−module Hakyll.Core.Resource.Provider
14− ( ResourceProvider (..)
15− , makeResourceProvider
16− , resourceExists
17− , resourceDigest
18− , resourceModified
19− ) where
20−
21−import Control.Applicative ((<$>))
22−import Control.Concurrent (MVar, readMVar, modifyMVar_, newMVar)
23−import Control.Monad ((<=<))
24−import Data.Word (Word8)
25−import Data.Map (Map)
26−import qualified Data.Map as M
27−
28−import qualified Data.ByteString.Lazy as LB
29−import OpenSSL.Digest.ByteString.Lazy (digest)
30−import OpenSSL.Digest (MessageDigest (MD5))
31−
32−import Hakyll.Core.Store
33−import Hakyll.Core.Resource
34−
35−-- | A value responsible for retrieving and listing resources
36−--
37−data ResourceProvider = ResourceProvider
38− { -- | A list of all resources this provider is able to provide
39− resourceList :: [Resource]
40− , -- | Retrieve a certain resource as string
41− resourceString :: Resource -> IO String
42− , -- | Retrieve a certain resource as lazy bytestring
43− resourceLazyByteString :: Resource -> IO LB.ByteString
44− , -- | Cache keeping track of modified items
45− resourceModifiedCache :: MVar (Map Resource Bool)
46− }
47−
48−-- | Create a resource provider
49−--
50−makeResourceProvider :: [Resource] -- ^ Resource list
51− -> (Resource -> IO String) -- ^ String reader
52− -> (Resource -> IO LB.ByteString) -- ^ ByteString reader
53− -> IO ResourceProvider -- ^ Resulting provider
54−makeResourceProvider l s b = ResourceProvider l s b <$> newMVar M.empty
55−
56−-- | Check if a given identifier has a resource
57−--
58−resourceExists :: ResourceProvider -> Resource -> Bool
59−resourceExists provider = flip elem $ resourceList provider
60−
61−-- | Retrieve a digest for a given resource
62−--
63−resourceDigest :: ResourceProvider -> Resource -> IO [Word8]
64−resourceDigest provider = digest MD5 <=< resourceLazyByteString provider
65−
66−-- | Check if a resource was modified
67−--
68−resourceModified :: ResourceProvider -> Store -> Resource -> IO Bool
69−resourceModified provider store resource = do
70− cache <- readMVar mvar
71− case M.lookup resource cache of
72− -- Already in the cache
73− Just m -> return m
74− -- Not yet in the cache, check digests (if it exists)
75− Nothing -> do
76− m <- if resourceExists provider resource
77− then digestModified provider store resource
78− else return False
79− modifyMVar_ mvar (return . M.insert resource m)
80− return m
81− where
82− mvar = resourceModifiedCache provider
83−
84−-- | Check if a resource digest was modified
85−--
86−digestModified :: ResourceProvider -> Store -> Resource -> IO Bool
87−digestModified provider store resource = do
88− -- Get the latest seen digest from the store
89− lastDigest <- storeGet store itemName identifier
90− -- Calculate the digest for the resource
91− newDigest <- resourceDigest provider resource
92− -- Check digests
93− if Just newDigest == lastDigest
94− -- All is fine, not modified
95− then return False
96− -- Resource modified; store new digest
97− else do storeSet store itemName identifier newDigest
98− return True
99− where
100− identifier = toIdentifier resource
101− itemName = "Hakyll.Core.ResourceProvider.digestModified"
src/Hakyll/Core/Resource/Provider/Dummy.hsdeleted−21
@@ -1,21 +0,0 @@
1−-- | Dummy resource provider for testing purposes
2−--
3−module Hakyll.Core.Resource.Provider.Dummy
4− ( dummyResourceProvider
5− ) where
6−
7−import Data.Map (Map)
8−import qualified Data.Map as M
9−
10−import qualified Data.ByteString.Lazy.Char8 as LBC
11−
12−import Hakyll.Core.Resource
13−import Hakyll.Core.Resource.Provider
14−
15−-- | Create a dummy 'ResourceProvider'
16−--
17−dummyResourceProvider :: Map String String -> IO ResourceProvider
18−dummyResourceProvider vfs = makeResourceProvider
19− (map Resource (M.keys vfs))
20− (return . (vfs M.!) . unResource)
21− (return . LBC.pack . (vfs M.!) . unResource)
src/Hakyll/Core/Resource/Provider/File.hsdeleted−24
@@ -1,24 +0,0 @@
1−-- | A concrete 'ResourceProvider' that gets it's resources from the filesystem
2−--
3−module Hakyll.Core.Resource.Provider.File
4− ( fileResourceProvider
5− ) where
6−
7−import Control.Applicative ((<$>))
8−
9−import qualified Data.ByteString.Lazy as LB
10−
11−import Hakyll.Core.Resource
12−import Hakyll.Core.Resource.Provider
13−import Hakyll.Core.Util.File
14−import Hakyll.Core.Configuration
15−
16−-- | Create a filesystem-based 'ResourceProvider'
17−--
18−fileResourceProvider :: HakyllConfiguration -> IO ResourceProvider
19−fileResourceProvider configuration = do
20− -- Retrieve a list of paths
21− list <- map Resource . filter (not . ignoreFile configuration) <$>
22− getRecursiveContents False "."
23− makeResourceProvider list (readFile . unResource)
24− (LB.readFile . unResource)
src/Hakyll/Core/Routes.hsdeleted−136
@@ -1,136 +0,0 @@
1−-- | Once a target is compiled, the user usually wants to save it to the disk.
2−-- This is where the 'Routes' type comes in; it determines where a certain
3−-- target should be written.
4−--
5−-- Suppose we have an item @foo\/bar.markdown@. We can render this to
6−-- @foo\/bar.html@ using:
7−--
8−-- > route "foo/bar.markdown" (setExtension ".html")
9−--
10−-- If we do not want to change the extension, we can use 'idRoute', the simplest
11−-- route available:
12−--
13−-- > route "foo/bar.markdown" idRoute
14−--
15−-- That will route @foo\/bar.markdown@ to @foo\/bar.markdown@.
16−--
17−-- Note that the extension says nothing about the content! If you set the
18−-- extension to @.html@, it is your own responsibility to ensure that the
19−-- content is indeed HTML.
20−--
21−-- Finally, some special cases:
22−--
23−-- * If there is no route for an item, this item will not be routed, so it will
24−-- not appear in your site directory.
25−--
26−-- * If an item matches multiple routes, the first rule will be chosen.
27−--
28−module Hakyll.Core.Routes
29− ( Routes
30− , runRoutes
31− , idRoute
32− , setExtension
33− , matchRoute
34− , customRoute
35− , gsubRoute
36− , composeRoutes
37− ) where
38−
39−import Data.Monoid (Monoid, mempty, mappend)
40−import Control.Monad (mplus)
41−import System.FilePath (replaceExtension)
42−
43−import Hakyll.Core.Identifier
44−import Hakyll.Core.Identifier.Pattern
45−import Hakyll.Core.Util.String
46−
47−-- | Type used for a route
48−--
49−newtype Routes = Routes {unRoutes :: Identifier -> Maybe FilePath}
50−
51−instance Monoid Routes where
52− mempty = Routes $ const Nothing
53− mappend (Routes f) (Routes g) = Routes $ \id' -> f id' `mplus` g id'
54−
55−-- | Apply a route to an identifier
56−--
57−runRoutes :: Routes -> Identifier -> Maybe FilePath
58−runRoutes = unRoutes
59−
60−-- | A route that uses the identifier as filepath. For example, the target with
61−-- ID @foo\/bar@ will be written to the file @foo\/bar@.
62−--
63−idRoute :: Routes
64−idRoute = Routes $ Just . toFilePath
65−
66−-- | Set (or replace) the extension of a route.
67−--
68−-- Example:
69−--
70−-- > runRoute (setExtension "html") "foo/bar"
71−--
72−-- Result:
73−--
74−-- > Just "foo/bar.html"
75−--
76−-- Example:
77−--
78−-- > runRoute (setExtension "html") "posts/the-art-of-trolling.markdown"
79−--
80−-- Result:
81−--
82−-- > Just "posts/the-art-of-trolling.html"
83−--
84−setExtension :: String -> Routes
85−setExtension extension = Routes $ fmap (`replaceExtension` extension)
86− . unRoutes idRoute
87−
88−-- | Apply the route if the identifier matches the given pattern, fail
89−-- otherwise
90−--
91−matchRoute :: Pattern -> Routes -> Routes
92−matchRoute pattern (Routes route) = Routes $ \id' ->
93− if matches pattern id' then route id' else Nothing
94−
95−-- | Create a custom route. This should almost always be used with
96−-- 'matchRoute'
97−--
98−customRoute :: (Identifier -> FilePath) -> Routes
99−customRoute f = Routes $ Just . f
100−
101−-- | Create a gsub route
102−--
103−-- Example:
104−--
105−-- > runRoutes (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
106−--
107−-- Result:
108−--
109−-- > Just "tags/bar.xml"
110−--
111−gsubRoute :: String -- ^ Pattern
112− -> (String -> String) -- ^ Replacement
113− -> Routes -- ^ Resulting route
114−gsubRoute pattern replacement = customRoute $
115− replaceAll pattern replacement . toFilePath
116−
117−-- | Compose routes so that @f `composeRoutes` g@ is more or less equivalent
118−-- with @f >>> g@.
119−--
120−-- Example:
121−--
122−-- > let routes = gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml"
123−-- > in runRoutes routes "tags/rss/bar"
124−--
125−-- Result:
126−--
127−-- > Just "tags/bar.xml"
128−--
129−-- If the first route given fails, Hakyll will not apply the second route.
130−--
131−composeRoutes :: Routes -- ^ First route to apply
132− -> Routes -- ^ Second route to apply
133− -> Routes -- ^ Resulting route
134−composeRoutes (Routes f) (Routes g) = Routes $ \i -> do
135− p <- f i
136− g $ parseIdentifier p
src/Hakyll/Core/Rules.hsdeleted−232
@@ -1,232 +0,0 @@
1−-- | This module provides a declarative DSL in which the user can specify the
2−-- different rules used to run the compilers.
3−--
4−-- The convention is to just list all items in the 'RulesM' monad, routes and
5−-- compilation rules.
6−--
7−-- A typical usage example would be:
8−--
9−-- > main = hakyll $ do
10−-- > match "posts/*" $ do
11−-- > route (setExtension "html")
12−-- > compile someCompiler
13−-- > match "css/*" $ do
14−-- > route idRoute
15−-- > compile compressCssCompiler
16−--
17−{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
18−module Hakyll.Core.Rules
19− ( RulesM
20− , Rules
21− , match
22− , group
23− , compile
24− , create
25− , route
26− , metaCompile
27− , metaCompileWith
28− ) where
29−
30−import Control.Applicative ((<$>))
31−import Control.Monad.Writer (tell)
32−import Control.Monad.Reader (ask, local)
33−import Control.Arrow (second, (>>>), arr, (>>^), (***))
34−import Control.Monad.State (get, put)
35−import Data.Monoid (mempty, mappend)
36−import qualified Data.Set as S
37−
38−import Data.Typeable (Typeable)
39−import Data.Binary (Binary)
40−
41−import Hakyll.Core.Resource
42−import Hakyll.Core.Resource.Provider
43−import Hakyll.Core.Identifier
44−import Hakyll.Core.Identifier.Pattern
45−import Hakyll.Core.Compiler.Internal
46−import Hakyll.Core.Routes
47−import Hakyll.Core.CompiledItem
48−import Hakyll.Core.Writable
49−import Hakyll.Core.Rules.Internal
50−import Hakyll.Core.Util.Arrow
51−
52−-- | Add a route
53−--
54−tellRoute :: Routes -> Rules
55−tellRoute route' = RulesM $ tell $ RuleSet route' mempty mempty
56−
57−-- | Add a number of compilers
58−--
59−tellCompilers :: (Binary a, Typeable a, Writable a)
60− => [(Identifier, Compiler () a)]
61− -> Rules
62−tellCompilers compilers = RulesM $ do
63− -- We box the compilers so they have a more simple type, and we apply the
64− -- current group to the corresponding identifiers
65− group' <- rulesGroup <$> ask
66− let compilers' = map (setGroup group' *** boxCompiler) compilers
67− tell $ RuleSet mempty compilers' mempty
68− where
69− boxCompiler = (>>> arr compiledItem >>> arr CompileRule)
70−
71−-- | Add resources
72−--
73−tellResources :: [Resource]
74− -> Rules
75−tellResources resources = RulesM $ tell $
76− RuleSet mempty mempty $ S.fromList resources
77−
78−-- | Only compile/route items satisfying the given predicate
79−--
80−match :: Pattern -> Rules -> Rules
81−match pattern = RulesM . local addPredicate . unRulesM
82− where
83− addPredicate env = env
84− { rulesPattern = rulesPattern env `mappend` pattern
85− }
86−
87−-- | Greate a group of compilers
88−--
89−-- Imagine you have a page that you want to render, but you also want the raw
90−-- content available on your site.
91−--
92−-- > match "test.markdown" $ do
93−-- > route $ setExtension "html"
94−-- > compile pageCompiler
95−-- >
96−-- > match "test.markdown" $ do
97−-- > route idRoute
98−-- > compile copyPageCompiler
99−--
100−-- Will of course conflict! In this case, Hakyll will pick the first matching
101−-- compiler (@pageCompiler@ in this case).
102−--
103−-- In case you want to have them both, you can use the 'group' function to
104−-- create a new group. For example,
105−--
106−-- > match "test.markdown" $ do
107−-- > route $ setExtension "html"
108−-- > compile pageCompiler
109−-- >
110−-- > group "raw" $ do
111−-- > match "test.markdown" $ do
112−-- > route idRoute
113−-- > compile copyPageCompiler
114−--
115−-- This will put the compiler for the raw content in a separate group
116−-- (@\"raw\"@), which causes it to be compiled as well.
117−--
118−group :: String -> Rules -> Rules
119−group g = RulesM . local setGroup' . unRulesM
120− where
121− setGroup' env = env { rulesGroup = Just g }
122−
123−-- | Add a compilation rule to the rules.
124−--
125−-- This instructs all resources to be compiled using the given compiler. When
126−-- no resources match the current selection, nothing will happen. In this case,
127−-- you might want to have a look at 'create'.
128−--
129−compile :: (Binary a, Typeable a, Writable a)
130− => Compiler Resource a -> Rules
131−compile compiler = RulesM $ do
132− pattern <- rulesPattern <$> ask
133− provider <- rulesResourceProvider <$> ask
134− let ids = filterMatches pattern $ map toIdentifier $ resourceList provider
135− unRulesM $ do
136− tellCompilers $ flip map ids $ \identifier ->
137− (identifier, constA (fromIdentifier identifier) >>> compiler)
138− tellResources $ map fromIdentifier ids
139−
140−-- | Add a compilation rule
141−--
142−-- This sets a compiler for the given identifier. No resource is needed, since
143−-- we are creating the item from scratch. This is useful if you want to create a
144−-- page on your site that just takes content from other items -- but has no
145−-- actual content itself.
146−--
147−create :: (Binary a, Typeable a, Writable a)
148− => Identifier -> Compiler () a -> Rules
149−create identifier compiler = tellCompilers [(identifier, compiler)]
150−
151−-- | Add a route.
152−--
153−-- This adds a route for all items matching the current pattern.
154−--
155−route :: Routes -> Rules
156−route route' = RulesM $ do
157− -- We want the route only to be applied if we match the current pattern and
158− -- group
159− pattern <- rulesPattern <$> ask
160− group' <- rulesGroup <$> ask
161− unRulesM $ tellRoute $ matchRoute (pattern `mappend` inGroup group') route'
162−
163−-- | Apart from regular compilers, one is also able to specify metacompilers.
164−-- Metacompilers are a special class of compilers: they are compilers which
165−-- produce other compilers.
166−--
167−-- This is needed when the list of compilers depends on something we cannot know
168−-- before actually running other compilers. The most typical example is if we
169−-- have a blogpost using tags.
170−--
171−-- Every post has a collection of tags. For example,
172−--
173−-- > post1: code, haskell
174−-- > post2: code, random
175−--
176−-- Now, we want to create a list of posts for every tag. We cannot write this
177−-- down in our 'Rules' DSL directly, since we don't know what tags the different
178−-- posts will have -- we depend on information that will only be available when
179−-- we are actually compiling the pages.
180−--
181−-- The solution is simple, using 'metaCompile', we can add a compiler that will
182−-- parse the pages and produce the compilers needed for the different tag pages.
183−--
184−-- And indeed, we can see that the first argument to 'metaCompile' is a
185−-- 'Compiler' which produces a list of ('Identifier', 'Compiler') pairs. The
186−-- idea is simple: 'metaCompile' produces a list of compilers, and the
187−-- corresponding identifiers.
188−--
189−-- For simple hakyll systems, it is no need for this construction. More
190−-- formally, it is only needed when the content of one or more items determines
191−-- which items must be rendered.
192−--
193−metaCompile :: (Binary a, Typeable a, Writable a)
194− => Compiler () [(Identifier, Compiler () a)]
195− -- ^ Compiler generating the other compilers
196− -> Rules
197− -- ^ Resulting rules
198−metaCompile compiler = RulesM $ do
199− -- Create an identifier from the state
200− state <- get
201− let index = rulesMetaCompilerIndex state
202− id' = fromCapture "Hakyll.Core.Rules.metaCompile/*" (show index)
203−
204− -- Update the state with a new identifier
205− put $ state {rulesMetaCompilerIndex = index + 1}
206−
207− -- Fallback to 'metaCompileWith' with now known identifier
208− unRulesM $ metaCompileWith id' compiler
209−
210−-- | Version of 'metaCompile' that allows you to specify a custom identifier for
211−-- the metacompiler.
212−--
213−metaCompileWith :: (Binary a, Typeable a, Writable a)
214− => Identifier
215− -- ^ Identifier for this compiler
216− -> Compiler () [(Identifier, Compiler () a)]
217− -- ^ Compiler generating the other compilers
218− -> Rules
219− -- ^ Resulting rules
220−metaCompileWith identifier compiler = RulesM $ do
221− group' <- rulesGroup <$> ask
222−
223− let -- Set the correct group on the identifier
224− id' = setGroup group' identifier
225− -- Function to box an item into a rule
226− makeRule = MetaCompileRule . map (second box)
227− -- Entire boxing function
228− box = (>>> fromDependency id' >>^ CompileRule . compiledItem)
229− -- Resulting compiler list
230− compilers = [(id', compiler >>> arr makeRule )]
231−
232− tell $ RuleSet mempty compilers mempty
src/Hakyll/Core/Rules/Internal.hsdeleted−99
@@ -1,99 +0,0 @@
1−-- | Internal rules module for types which are not exposed to the user
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−module Hakyll.Core.Rules.Internal
5− ( CompileRule (..)
6− , RuleSet (..)
7− , RuleState (..)
8− , RuleEnvironment (..)
9− , RulesM (..)
10− , Rules
11− , runRules
12− ) where
13−
14−import Control.Applicative (Applicative)
15−import Control.Monad.Writer (WriterT, execWriterT)
16−import Control.Monad.Reader (ReaderT, runReaderT)
17−import Control.Monad.State (State, evalState)
18−import Data.Monoid (Monoid, mempty, mappend)
19−import Data.Set (Set)
20−import qualified Data.Map as M
21−
22−import Hakyll.Core.Resource
23−import Hakyll.Core.Resource.Provider
24−import Hakyll.Core.Identifier
25−import Hakyll.Core.Identifier.Pattern
26−import Hakyll.Core.Compiler.Internal
27−import Hakyll.Core.Routes
28−import Hakyll.Core.CompiledItem
29−
30−-- | Output of a compiler rule
31−--
32−-- * The compiler will produce a simple item. This is the most common case.
33−--
34−-- * The compiler will produce more compilers. These new compilers need to be
35−-- added to the runtime if possible, since other items might depend upon them.
36−--
37−data CompileRule = CompileRule CompiledItem
38− | MetaCompileRule [(Identifier, Compiler () CompileRule)]
39−
40−-- | A collection of rules for the compilation process
41−--
42−data RuleSet = RuleSet
43− { -- | Routes used in the compilation structure
44− rulesRoutes :: Routes
45− , -- | Compilation rules
46− rulesCompilers :: [(Identifier, Compiler () CompileRule)]
47− , -- | A list of the used resources
48− rulesResources :: Set Resource
49− }
50−
51−instance Monoid RuleSet where
52− mempty = RuleSet mempty mempty mempty
53− mappend (RuleSet r1 c1 s1) (RuleSet r2 c2 s2) =
54− RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2)
55−
56−-- | Rule state
57−--
58−data RuleState = RuleState
59− { rulesMetaCompilerIndex :: Int
60− } deriving (Show)
61−
62−-- | Rule environment
63−--
64−data RuleEnvironment = RuleEnvironment
65− { rulesResourceProvider :: ResourceProvider
66− , rulesPattern :: Pattern
67− , rulesGroup :: Maybe String
68− }
69−
70−-- | The monad used to compose rules
71−--
72−newtype RulesM a = RulesM
73− { unRulesM :: ReaderT RuleEnvironment (WriterT RuleSet (State RuleState)) a
74− } deriving (Monad, Functor, Applicative)
75−
76−-- | Simplification of the RulesM type; usually, it will not return any
77−-- result.
78−--
79−type Rules = RulesM ()
80−
81−-- | Run a Rules monad, resulting in a 'RuleSet'
82−--
83−runRules :: Rules -> ResourceProvider -> RuleSet
84−runRules rules provider = nubCompilers $
85− evalState (execWriterT $ runReaderT (unRulesM rules) env) state
86− where
87− state = RuleState {rulesMetaCompilerIndex = 0}
88− env = RuleEnvironment { rulesResourceProvider = provider
89− , rulesPattern = mempty
90− , rulesGroup = Nothing
91− }
92−
93−-- | Remove duplicate compilers from the 'RuleSet'. When two compilers match an
94−-- item, we prefer the first one
95−--
96−nubCompilers :: RuleSet -> RuleSet
97−nubCompilers set = set { rulesCompilers = nubCompilers' (rulesCompilers set) }
98− where
99− nubCompilers' = M.toList . M.fromListWith (flip const)
src/Hakyll/Core/Run.hsdeleted−197
@@ -1,197 +0,0 @@
1−-- | This is the module which binds it all together
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
4−module Hakyll.Core.Run
5− ( run
6− ) where
7−
8−import Prelude hiding (reverse)
9−import Control.Monad (filterM)
10−import Control.Monad.Trans (liftIO)
11−import Control.Applicative (Applicative, (<$>))
12−import Control.Monad.Reader (ReaderT, runReaderT, ask)
13−import Control.Monad.State.Strict (StateT, runStateT, get, put)
14−import Data.Map (Map)
15−import qualified Data.Map as M
16−import Data.Monoid (mempty, mappend)
17−import Data.Maybe (fromMaybe)
18−import System.FilePath ((</>))
19−import qualified Data.Set as S
20−
21−import Hakyll.Core.Routes
22−import Hakyll.Core.Identifier
23−import Hakyll.Core.Util.File
24−import Hakyll.Core.Compiler
25−import Hakyll.Core.Compiler.Internal
26−import Hakyll.Core.Resource
27−import Hakyll.Core.Resource.Provider
28−import Hakyll.Core.Resource.Provider.File
29−import Hakyll.Core.Rules.Internal
30−import Hakyll.Core.DirectedGraph
31−import Hakyll.Core.DependencyAnalyzer
32−import Hakyll.Core.Writable
33−import Hakyll.Core.Store
34−import Hakyll.Core.Configuration
35−import Hakyll.Core.Logger
36−
37−-- | Run all rules needed, return the rule set used
38−--
39−run :: HakyllConfiguration -> Rules -> IO RuleSet
40−run configuration rules = do
41− logger <- makeLogger
42−
43− section logger "Initialising"
44− store <- timed logger "Creating store" $
45− makeStore $ storeDirectory configuration
46− provider <- timed logger "Creating provider" $
47− fileResourceProvider configuration
48−
49− -- Fetch the old graph from the store
50− oldGraph <- fromMaybe mempty <$>
51− storeGet store "Hakyll.Core.Run.run" "dependencies"
52−
53− let ruleSet = runRules rules provider
54− compilers = rulesCompilers ruleSet
55−
56− -- Extract the reader/state
57− reader = unRuntime $ addNewCompilers compilers
58− stateT = runReaderT reader $ RuntimeEnvironment
59− { hakyllLogger = logger
60− , hakyllConfiguration = configuration
61− , hakyllRoutes = rulesRoutes ruleSet
62− , hakyllResourceProvider = provider
63− , hakyllStore = store
64− }
65−
66− -- Run the program and fetch the resulting state
67− ((), state') <- runStateT stateT $ RuntimeState
68− { hakyllAnalyzer = makeDependencyAnalyzer mempty (const False) oldGraph
69− , hakyllCompilers = M.empty
70− }
71−
72− -- We want to save the final dependency graph for the next run
73− storeSet store "Hakyll.Core.Run.run" "dependencies" $
74− analyzerGraph $ hakyllAnalyzer state'
75−
76− -- Flush and return
77− flushLogger logger
78− return ruleSet
79−
80−data RuntimeEnvironment = RuntimeEnvironment
81− { hakyllLogger :: Logger
82− , hakyllConfiguration :: HakyllConfiguration
83− , hakyllRoutes :: Routes
84− , hakyllResourceProvider :: ResourceProvider
85− , hakyllStore :: Store
86− }
87−
88−data RuntimeState = RuntimeState
89− { hakyllAnalyzer :: DependencyAnalyzer Identifier
90− , hakyllCompilers :: Map Identifier (Compiler () CompileRule)
91− }
92−
93−newtype Runtime a = Runtime
94− { unRuntime :: ReaderT RuntimeEnvironment (StateT RuntimeState IO) a
95− } deriving (Functor, Applicative, Monad)
96−
97−-- | Add a number of compilers and continue using these compilers
98−--
99−addNewCompilers :: [(Identifier, Compiler () CompileRule)]
100− -- ^ Compilers to add
101− -> Runtime ()
102−addNewCompilers newCompilers = Runtime $ do
103− -- Get some information
104− logger <- hakyllLogger <$> ask
105− section logger "Adding new compilers"
106− provider <- hakyllResourceProvider <$> ask
107− store <- hakyllStore <$> ask
108−
109− -- Old state information
110− oldCompilers <- hakyllCompilers <$> get
111− oldAnalyzer <- hakyllAnalyzer <$> get
112−
113− let -- All known compilers
114− universe = M.keys oldCompilers ++ map fst newCompilers
115−
116− -- Create a new partial dependency graph
117− dependencies = flip map newCompilers $ \(id', compiler) ->
118− let deps = runCompilerDependencies compiler id' universe
119− in (id', deps)
120−
121− -- Create the dependency graph
122− newGraph = fromList dependencies
123−
124− -- Check which items have been modified
125− modified <- fmap S.fromList $ flip filterM (map fst newCompilers) $
126− liftIO . resourceModified provider store . fromIdentifier
127−
128− -- Create a new analyzer and append it to the currect one
129− let newAnalyzer = makeDependencyAnalyzer newGraph (`S.member` modified) $
130− analyzerPreviousGraph oldAnalyzer
131− analyzer = mappend oldAnalyzer newAnalyzer
132−
133− -- Update the state
134− put $ RuntimeState
135− { hakyllAnalyzer = analyzer
136− , hakyllCompilers = M.union oldCompilers (M.fromList newCompilers)
137− }
138−
139− -- Continue
140− unRuntime stepAnalyzer
141−
142−stepAnalyzer :: Runtime ()
143−stepAnalyzer = Runtime $ do
144− -- Step the analyzer
145− state <- get
146− let (signal, analyzer') = step $ hakyllAnalyzer state
147− put $ state { hakyllAnalyzer = analyzer' }
148−
149− case signal of Done -> return ()
150− Cycle _ -> return ()
151− Build id' -> unRuntime $ build id'
152−
153−build :: Identifier -> Runtime ()
154−build id' = Runtime $ do
155− logger <- hakyllLogger <$> ask
156− routes <- hakyllRoutes <$> ask
157− provider <- hakyllResourceProvider <$> ask
158− store <- hakyllStore <$> ask
159− compilers <- hakyllCompilers <$> get
160−
161− section logger $ "Compiling " ++ show id'
162−
163− -- Fetch the right compiler from the map
164− let compiler = compilers M.! id'
165−
166− -- Check if the resource was modified
167− isModified <- liftIO $ resourceModified provider store $ fromIdentifier id'
168−
169− -- Run the compiler
170− result <- timed logger "Total compile time" $ liftIO $
171− runCompiler compiler id' provider (M.keys compilers) routes
172− store isModified logger
173−
174− case result of
175− -- Compile rule for one item, easy stuff
176− Right (CompileRule compiled) -> do
177− case runRoutes routes id' of
178− Nothing -> return ()
179− Just url -> timed logger ("Routing to " ++ url) $ do
180− destination <-
181− destinationDirectory . hakyllConfiguration <$> ask
182− let path = destination </> url
183− liftIO $ makeDirectories path
184− liftIO $ write path compiled
185−
186− -- Continue for the remaining compilers
187− unRuntime stepAnalyzer
188−
189− -- Metacompiler, slightly more complicated
190− Right (MetaCompileRule newCompilers) ->
191− -- Actually I was just kidding, it's not hard at all
192− unRuntime $ addNewCompilers newCompilers
193−
194− -- Some error happened, log and continue
195− Left err -> do
196− thrown logger err
197− unRuntime stepAnalyzer
src/Hakyll/Core/Store.hsdeleted−93
@@ -1,93 +0,0 @@
1−-- | A store for stroing and retreiving items
2−--
3−{-# LANGUAGE ExistentialQuantification #-}
4−module Hakyll.Core.Store
5− ( Store
6− , makeStore
7− , storeSet
8− , storeGet
9− ) where
10−
11−import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
12−import System.FilePath ((</>))
13−import System.Directory (doesFileExist)
14−import Data.Maybe (fromMaybe)
15−import Data.Map (Map)
16−import qualified Data.Map as M
17−
18−import Data.Binary (Binary, encodeFile, decodeFile)
19−import Data.Typeable (Typeable, cast)
20−
21−import Hakyll.Core.Identifier
22−import Hakyll.Core.Util.File
23−
24−-- | Items we can store
25−--
26−data Storable = forall a. (Binary a, Typeable a) => Storable a
27−
28−-- | Data structure used for the store
29−--
30−data Store = Store
31− { -- | All items are stored on the filesystem
32− storeDirectory :: FilePath
33− , -- | And some items are also kept in-memory
34− storeMap :: MVar (Map FilePath Storable)
35− }
36−
37−-- | Initialize the store
38−--
39−makeStore :: FilePath -> IO Store
40−makeStore directory = do
41− mvar <- newMVar M.empty
42− return Store
43− { storeDirectory = directory
44− , storeMap = mvar
45− }
46−
47−-- | Auxiliary: add an item to the map
48−--
49−addToMap :: (Binary a, Typeable a) => Store -> FilePath -> a -> IO ()
50−addToMap store path value =
51− modifyMVar_ (storeMap store) $ return . M.insert path (Storable value)
52−
53−-- | Create a path
54−--
55−makePath :: Store -> String -> Identifier -> FilePath
56−makePath store name identifier = storeDirectory store </> name
57− </> group </> toFilePath identifier </> "hakyllstore"
58− where
59− group = fromMaybe "" $ identifierGroup identifier
60−
61−-- | Store an item
62−--
63−storeSet :: (Binary a, Typeable a)
64− => Store -> String -> Identifier -> a -> IO ()
65−storeSet store name identifier value = do
66− makeDirectories path
67− encodeFile path value
68− addToMap store path value
69− where
70− path = makePath store name identifier
71−
72−-- | Load an item
73−--
74−storeGet :: (Binary a, Typeable a)
75− => Store -> String -> Identifier -> IO (Maybe a)
76−storeGet store name identifier = do
77− -- First check the in-memory map
78− map' <- readMVar $ storeMap store
79− case M.lookup path map' of
80− -- Found in the in-memory map
81− Just (Storable s) -> return $ cast s
82− -- Not found in the map, try the filesystem
83− Nothing -> do
84− exists <- doesFileExist path
85− if not exists
86− -- Not found in the filesystem either
87− then return Nothing
88− -- Found in the filesystem
89− else do v <- decodeFile path
90− addToMap store path v
91− return $ Just v
92− where
93− path = makePath store name identifier
src/Hakyll/Core/UnixFilter.hsdeleted−76
@@ -1,76 +0,0 @@
1−-- | A Compiler that supports unix filters.
2−--
3−module Hakyll.Core.UnixFilter
4− ( unixFilter
5− ) where
6−
7−import Control.Concurrent (forkIO)
8−import System.IO (hPutStr, hClose, hGetContents)
9−import System.Posix.Process (executeFile, forkProcess)
10−import System.Posix.IO ( dupTo, createPipe, stdInput
11− , stdOutput, closeFd, fdToHandle
12− )
13−
14−import Hakyll.Core.Compiler
15−
16−-- | Use a unix filter as compiler. For example, we could use the 'rev' program
17−-- as a compiler.
18−--
19−-- > rev :: Compiler Resource String
20−-- > rev = getResourceString >>> unixFilter "rev" []
21−--
22−-- A more realistic example: one can use this to call, for example, the sass
23−-- compiler on CSS files. More information about sass can be found here:
24−--
25−-- <http://sass-lang.com/>
26−--
27−-- The code is fairly straightforward, given that we use @.scss@ for sass:
28−--
29−-- > route "style.scss" $ setExtension "css"
30−-- > compile "style.scss" $
31−-- > getResourceString >>> unixFilter "sass" ["-s", "--scss"]
32−-- > >>> arr compressCss
33−--
34−unixFilter :: String -- ^ Program name
35− -> [String] -- ^ Program args
36− -> Compiler String String -- ^ Resulting compiler
37−unixFilter programName args =
38− timedCompiler ("Executing external program " ++ programName) $
39− unsafeCompiler $ \input -> unixFilterIO programName args input
40−
41−-- | Internally used function
42−--
43−unixFilterIO :: String
44− -> [String]
45− -> String
46− -> IO String
47−unixFilterIO programName args input = do
48− -- Create pipes
49− (stdinRead, stdinWrite) <- createPipe
50− (stdoutRead, stdoutWrite) <- createPipe
51−
52− -- Fork the child
53− _ <- forkProcess $ do
54− -- Copy our pipes over the regular stdin/stdout
55− _ <- dupTo stdinRead stdInput
56− _ <- dupTo stdoutWrite stdOutput
57−
58− -- Close the now unneeded file descriptors in the child
59− mapM_ closeFd [stdinWrite, stdoutRead, stdinRead, stdoutWrite]
60−
61− -- Execute the program
62− _ <- executeFile programName True args Nothing
63− return ()
64−
65− -- On the parent side, close the client-side FDs.
66− mapM_ closeFd [stdinRead, stdoutWrite]
67−
68− -- Write the input to the child pipe
69− _ <- forkIO $ do
70− stdinWriteHandle <- fdToHandle stdinWrite
71− hPutStr stdinWriteHandle input
72− hClose stdinWriteHandle
73−
74− -- Receive the output from the child
75− stdoutReadHandle <- fdToHandle stdoutRead
76− hGetContents stdoutReadHandle
src/Hakyll/Core/Util/Arrow.hsdeleted−25
@@ -1,25 +0,0 @@
1−-- | Various arrow utility functions
2−--
3−module Hakyll.Core.Util.Arrow
4− ( constA
5− , sequenceA
6− , unitA
7− ) where
8−
9−import Control.Arrow (Arrow, (&&&), arr, (>>^))
10−
11−constA :: Arrow a
12− => c
13− -> a b c
14−constA = arr . const
15−
16−sequenceA :: Arrow a
17− => [a b c]
18− -> a b [c]
19−sequenceA = foldl reduce $ constA []
20− where
21− reduce la xa = xa &&& la >>^ arr (uncurry (:))
22−
23−unitA :: Arrow a
24− => a b ()
25−unitA = constA ()
src/Hakyll/Core/Util/File.hsdeleted−90
@@ -1,90 +0,0 @@
1−-- | A module containing various file utility functions
2−--
3−module Hakyll.Core.Util.File
4− ( makeDirectories
5− , getRecursiveContents
6− , isFileObsolete
7− , isFileInternal
8− ) where
9−
10−import Control.Applicative ((<$>))
11−import System.Time (ClockTime)
12−import Control.Monad (forM, filterM)
13−import Data.List (isPrefixOf)
14−import System.Directory ( createDirectoryIfMissing, doesDirectoryExist
15− , doesFileExist, getModificationTime
16− , getDirectoryContents
17− )
18−import System.FilePath ( normalise, takeDirectory, splitPath
19− , dropTrailingPathSeparator, (</>)
20− )
21−
22−import Hakyll.Core.Configuration
23−
24−-- | Given a path to a file, try to make the path writable by making
25−-- all directories on the path.
26−--
27−makeDirectories :: FilePath -> IO ()
28−makeDirectories = createDirectoryIfMissing True . takeDirectory
29−
30−-- | Get all contents of a directory. Note that files starting with a dot (.)
31−-- will be ignored.
32−--
33−getRecursiveContents :: Bool -- ^ Include directories?
34− -> FilePath -- ^ Directory to search
35− -> IO [FilePath] -- ^ List of files found
36−getRecursiveContents includeDirs topdir = do
37− topdirExists <- doesDirectoryExist topdir
38− if not topdirExists
39− then return []
40− else do
41− names <- filter isProper <$> getDirectoryContents topdir
42− paths <- forM names $ \name -> do
43− let path = normalise $ topdir </> name
44− isDirectory <- doesDirectoryExist path
45− if isDirectory then getRecursiveContents includeDirs path
46− else return [path]
47− return $ if includeDirs then topdir : concat paths
48− else concat paths
49− where
50− isProper = (`notElem` [".", ".."])
51−
52−-- | Check if a timestamp is obsolete compared to the timestamps of a number of
53−-- files. When they are no files, it is never obsolete.
54−--
55−isObsolete :: ClockTime -- ^ The time to check.
56− -> [FilePath] -- ^ Dependencies of the cached file.
57− -> IO Bool
58−isObsolete _ [] = return False
59−isObsolete timeStamp depends = do
60− depends' <- filterM doesFileExist depends
61− dependsModified <- mapM getModificationTime depends'
62− return (timeStamp < maximum dependsModified)
63−
64−-- | Check if a file is obsolete, given it's dependencies. When the file does
65−-- not exist, it is always obsolete. Other wise, it is obsolete if any of it's
66−-- dependencies has a more recent modification time than the file.
67−--
68−isFileObsolete :: FilePath -- ^ The cached file
69− -> [FilePath] -- ^ Dependencies of the cached file
70− -> IO Bool
71−isFileObsolete file depends = do
72− exists <- doesFileExist file
73− if not exists
74− then return True
75− else do timeStamp <- getModificationTime file
76− isObsolete timeStamp depends
77−
78−-- | Check if a file is meant for Hakyll internal use, i.e. if it is located in
79−-- the destination or store directory
80−--
81−isFileInternal :: HakyllConfiguration -- ^ Configuration
82− -> FilePath -- ^ File to check
83− -> Bool -- ^ If the given file is internal
84−isFileInternal configuration file =
85− any (`isPrefixOf` split file) dirs
86− where
87− split = map dropTrailingPathSeparator . splitPath
88− dirs = map (split . ($ configuration)) [ destinationDirectory
89− , storeDirectory
90− ]
src/Hakyll/Core/Writable.hsdeleted−30
@@ -1,30 +0,0 @@
1−-- | Describes writable items; items that can be saved to the disk
2−--
3−{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
4−module Hakyll.Core.Writable
5− ( Writable (..)
6− ) where
7−
8−import Data.Word (Word8)
9−
10−import qualified Data.ByteString as SB
11−import Text.Blaze (Html)
12−import Text.Blaze.Renderer.String (renderHtml)
13−
14−-- | Describes an item that can be saved to the disk
15−--
16−class Writable a where
17− -- | Save an item to the given filepath
18− write :: FilePath -> a -> IO ()
19−
20−instance Writable [Char] where
21− write = writeFile
22−
23−instance Writable SB.ByteString where
24− write p = SB.writeFile p
25−
26−instance Writable [Word8] where
27− write p = write p . SB.pack
28−
29−instance Writable Html where
30− write p html = write p $ renderHtml html
src/Hakyll/Core/Writable/CopyFile.hsdeleted−29
@@ -1,29 +0,0 @@
1−-- | Exports simple compilers to just copy files
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
4−module Hakyll.Core.Writable.CopyFile
5− ( CopyFile (..)
6− , copyFileCompiler
7− ) where
8−
9−import Control.Arrow ((>>^))
10−import System.Directory (copyFile)
11−
12−import Data.Typeable (Typeable)
13−import Data.Binary (Binary)
14−
15−import Hakyll.Core.Resource
16−import Hakyll.Core.Writable
17−import Hakyll.Core.Compiler
18−import Hakyll.Core.Identifier
19−
20−-- | Newtype construct around 'FilePath' which will copy the file directly
21−--
22−newtype CopyFile = CopyFile {unCopyFile :: FilePath}
23− deriving (Show, Eq, Ord, Binary, Typeable)
24−
25−instance Writable CopyFile where
26− write dst (CopyFile src) = copyFile src dst
27−
28−copyFileCompiler :: Compiler Resource CopyFile
29−copyFileCompiler = getIdentifier >>^ CopyFile . toFilePath
src/Hakyll/Core/Writable/WritableTuple.hsdeleted−37
@@ -1,37 +0,0 @@
1−-- | This module exposes a writable type 'WritableTuple' which is a simple
2−-- newtype wrapper around a tuple.
3−--
4−-- The idea is that, given a tuple @(a, b)@, @a@ is the value you actually want
5−-- to save to the disk, and @b@ is some additional info that you /don't/ want to
6−-- save, but that you need later, for example in a 'require' clause.
7−--
8−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
9−module Hakyll.Core.Writable.WritableTuple
10− ( WritableTuple (..)
11− , writableTupleFst
12− , writableTupleSnd
13− , writableTupleCompiler
14− ) where
15−
16−import Control.Arrow (arr)
17−
18−import Data.Typeable (Typeable)
19−import Data.Binary (Binary)
20−
21−import Hakyll.Core.Writable
22−import Hakyll.Core.Compiler
23−
24−newtype WritableTuple a b = WritableTuple {unWritableTuple :: (a, b)}
25− deriving (Show, Eq, Ord, Binary, Typeable)
26−
27−instance Writable a => Writable (WritableTuple a b) where
28− write dst (WritableTuple (x, _)) = write dst x
29−
30−writableTupleFst :: WritableTuple a b -> a
31−writableTupleFst = fst . unWritableTuple
32−
33−writableTupleSnd :: WritableTuple a b -> b
34−writableTupleSnd = snd . unWritableTuple
35−
36−writableTupleCompiler :: Compiler (a, b) (WritableTuple a b)
37−writableTupleCompiler = arr WritableTuple
src/Hakyll/Main.hsdeleted−113
@@ -1,113 +0,0 @@
1−-- | Module providing the main hakyll function and command-line argument parsing
2−--
3−module Hakyll.Main
4− ( hakyll
5− , hakyllWith
6− ) where
7−
8−import Control.Concurrent (forkIO)
9−import Control.Monad (when)
10−import System.Environment (getProgName, getArgs)
11−import System.Directory (doesDirectoryExist, removeDirectoryRecursive)
12−
13−import Hakyll.Core.Configuration
14−import Hakyll.Core.Run
15−import Hakyll.Core.Rules
16−import Hakyll.Core.Rules.Internal
17−import Hakyll.Web.Preview.Poll
18−import Hakyll.Web.Preview.Server
19−
20−-- | This usualy is the function with which the user runs the hakyll compiler
21−--
22−hakyll :: Rules -> IO ()
23−hakyll = hakyllWith defaultHakyllConfiguration
24−
25−-- | A variant of 'hakyll' which allows the user to specify a custom
26−-- configuration
27−--
28−hakyllWith :: HakyllConfiguration -> Rules -> IO ()
29−hakyllWith configuration rules = do
30− args <- getArgs
31− case args of
32− ["build"] -> build configuration rules
33− ["clean"] -> clean configuration
34− ["help"] -> help
35− ["preview"] -> preview configuration rules 8000
36− ["preview", p] -> preview configuration rules (read p)
37− ["rebuild"] -> rebuild configuration rules
38− ["server"] -> server configuration 8000
39− ["server", p] -> server configuration (read p)
40− _ -> help
41−
42−-- | Build the site
43−--
44−build :: HakyllConfiguration -> Rules -> IO ()
45−build configuration rules = do
46− _ <- run configuration rules
47− return ()
48−
49−-- | Remove the output directories
50−--
51−clean :: HakyllConfiguration -> IO ()
52−clean configuration = do
53− remove $ destinationDirectory configuration
54− remove $ storeDirectory configuration
55− where
56− remove dir = do
57− putStrLn $ "Removing " ++ dir ++ "..."
58− exists <- doesDirectoryExist dir
59− when exists $ removeDirectoryRecursive dir
60−
61−-- | Show usage information.
62−--
63−help :: IO ()
64−help = do
65− name <- getProgName
66− mapM_ putStrLn
67− [ "ABOUT"
68− , ""
69− , "This is a Hakyll site generator program. You should always"
70− , "run it from the project root directory."
71− , ""
72− , "USAGE"
73− , ""
74− , name ++ " build Generate the site"
75− , name ++ " clean Clean up and remove cache"
76− , name ++ " help Show this message"
77− , name ++ " preview [port] Run a server and autocompile"
78− , name ++ " rebuild Clean up and build again"
79− , name ++ " server [port] Run a local test server"
80− ]
81−
82−-- | Preview the site
83−--
84−preview :: HakyllConfiguration -> Rules -> Int -> IO ()
85−preview configuration rules port = do
86− -- Build once, keep the rule set
87− ruleSet <- run configuration rules
88−
89− -- Get the resource list and a callback for the preview poll
90− let resources = rulesResources ruleSet
91− callback = build configuration rules
92−
93− -- Fork a thread polling for changes
94− _ <- forkIO $ previewPoll configuration resources callback
95−
96− -- Run the server in the main thread
97− server configuration port
98−
99−-- | Rebuild the site
100−--
101−rebuild :: HakyllConfiguration -> Rules -> IO ()
102−rebuild configuration rules = do
103− clean configuration
104− build configuration rules
105−
106−-- | Start a server
107−--
108−server :: HakyllConfiguration -> Int -> IO ()
109−server configuration port = do
110− let destination = destinationDirectory configuration
111− staticServer destination preServeHook port
112− where
113− preServeHook _ = return ()
src/Hakyll/Web/Blaze.hsdeleted−34
@@ -1,34 +0,0 @@
1−-- | Module providing BlazeHtml support for hakyll
2−--
3−module Hakyll.Web.Blaze
4− ( getFieldHtml
5− , getFieldHtml'
6− , getBodyHtml
7− , getBodyHtml'
8− ) where
9−
10−import Text.Blaze (Html, toHtml, preEscapedString)
11−
12−import Hakyll.Web.Page
13−import Hakyll.Web.Page.Metadata
14−
15−-- | Get a field from a page and convert it to HTML. This version does escape
16−-- the given HTML
17−--
18−getFieldHtml :: String -> Page a -> Html
19−getFieldHtml key = preEscapedString . getField key
20−
21−-- | Version of 'getFieldHtml' that escapes the HTML content
22−--
23−getFieldHtml' :: String -> Page a -> Html
24−getFieldHtml' key = toHtml . getField key
25−
26−-- | Get the body as HTML
27−--
28−getBodyHtml :: Page String -> Html
29−getBodyHtml = preEscapedString . pageBody
30−
31−-- | Version of 'getBodyHtml' that escapes the HTML content
32−--
33−getBodyHtml' :: Page String -> Html
34−getBodyHtml' = toHtml . pageBody
src/Hakyll/Web/CompressCss.hsdeleted−51
@@ -1,51 +0,0 @@
1−-- | Module used for CSS compression. The compression is currently in a simple
2−-- state, but would typically reduce the number of bytes by about 25%.
3−--
4−module Hakyll.Web.CompressCss
5− ( compressCssCompiler
6− , compressCss
7− ) where
8−
9−import Data.Char (isSpace)
10−import Data.List (isPrefixOf)
11−import Control.Arrow ((>>^))
12−
13−import Hakyll.Core.Compiler
14−import Hakyll.Core.Resource
15−import Hakyll.Core.Util.String
16−
17−-- | Compiler form of 'compressCss'
18−--
19−compressCssCompiler :: Compiler Resource String
20−compressCssCompiler = getResourceString >>^ compressCss
21−
22−-- | Compress CSS to speed up your site.
23−--
24−compressCss :: String -> String
25−compressCss = compressSeparators
26− . stripComments
27− . compressWhitespace
28−
29−-- | Compresses certain forms of separators.
30−--
31−compressSeparators :: String -> String
32−compressSeparators = replaceAll "; *}" (const "}")
33− . replaceAll " *([{};:]) *" (take 1 . dropWhile isSpace)
34− . replaceAll ";;*" (const ";")
35−
36−-- | Compresses all whitespace.
37−--
38−compressWhitespace :: String -> String
39−compressWhitespace = replaceAll "[ \t\n][ \t\n]*" (const " ")
40−
41−-- | Function that strips CSS comments away.
42−--
43−stripComments :: String -> String
44−stripComments [] = []
45−stripComments str
46− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
47− | otherwise = head str : stripComments (drop 1 str)
48− where
49− eatComments str' | null str' = []
50− | isPrefixOf "*/" str' = drop 2 str'
51− | otherwise = eatComments $ drop 1 str'
src/Hakyll/Web/Feed.hsdeleted−124
@@ -1,124 +0,0 @@
1−-- | A Module that allows easy rendering of RSS feeds.
2−--
3−-- The main rendering functions (@renderRss@, @renderAtom@) all assume that
4−-- you pass the list of items so that the most recent entry in the feed is the
5−-- first item in the list.
6−--
7−-- Also note that the pages should have (at least) the following fields to
8−-- produce a correct feed:
9−--
10−-- - @$title@: Title of the item
11−--
12−-- - @$description@: Description to appear in the feed
13−--
14−-- - @$url@: URL to the item - this is usually set automatically.
15−--
16−-- In addition, the posts should be named according to the rules for
17−-- 'Hakyll.Page.Metadata.renderDateField'.
18−--
19−module Hakyll.Web.Feed
20− ( FeedConfiguration (..)
21− , renderRss
22− , renderAtom
23− ) where
24−
25−import Prelude hiding (id)
26−import Control.Category (id)
27−import Control.Arrow ((>>>), arr, (&&&))
28−import Control.Monad ((<=<))
29−import Data.Maybe (fromMaybe, listToMaybe)
30−
31−import Hakyll.Core.Compiler
32−import Hakyll.Web.Page
33−import Hakyll.Web.Page.Metadata
34−import Hakyll.Web.Template
35−import Hakyll.Web.Template.Read.Hakyll (readTemplate)
36−import Hakyll.Web.Util.Url
37−
38−import Paths_hakyll
39−
40−-- | This is a data structure to keep the configuration of a feed.
41−data FeedConfiguration = FeedConfiguration
42− { -- | Title of the feed.
43− feedTitle :: String
44− , -- | Description of the feed.
45− feedDescription :: String
46− , -- | Name of the feed author.
47− feedAuthorName :: String
48− , -- | Absolute root URL of the feed site (e.g. @http://jaspervdj.be@)
49− feedRoot :: String
50− }
51−
52−-- | This is an auxiliary function to create a listing that is, in fact, a feed.
53−-- The items should be sorted on date. The @$timestamp@ field should be set.
54−--
55−createFeed :: Template -- ^ Feed template
56− -> Template -- ^ Item template
57− -> String -- ^ URL of the feed
58− -> FeedConfiguration -- ^ Feed configuration
59− -> [Page String] -- ^ Items to include
60− -> String -- ^ Resulting feed
61−createFeed feedTemplate itemTemplate url configuration items =
62− pageBody $ applyTemplate feedTemplate
63− $ trySetField "timestamp" timestamp
64− $ trySetField "title" (feedTitle configuration)
65− $ trySetField "description" (feedDescription configuration)
66− $ trySetField "authorName" (feedDescription configuration)
67− $ trySetField "root" (feedRoot configuration)
68− $ trySetField "url" url
69− $ fromBody body
70− where
71− -- Preprocess items
72− items' = flip map items $ applyTemplate itemTemplate
73− . trySetField "root" (feedRoot configuration)
74−
75− -- Body: concatenated items
76− body = concat $ map pageBody items'
77−
78− -- Take the first timestamp, which should be the most recent
79− timestamp = fromMaybe "Unknown" $ do
80− p <- listToMaybe items
81− return $ getField "timestamp" p
82−
83−
84−-- | Abstract function to render any feed.
85−--
86−renderFeed :: FilePath -- ^ Feed template
87− -> FilePath -- ^ Item template
88− -> FeedConfiguration -- ^ Feed configuration
89− -> Compiler [Page String] String -- ^ Feed compiler
90−renderFeed feedTemplate itemTemplate configuration =
91− id &&& getRoute >>> renderFeed'
92− where
93− -- Arrow rendering the feed from the items and the URL
94− renderFeed' = unsafeCompiler $ \(items, url) -> do
95− feedTemplate' <- loadTemplate feedTemplate
96− itemTemplate' <- loadTemplate itemTemplate
97− let url' = toUrl $ fromMaybe noUrl url
98− return $ createFeed feedTemplate' itemTemplate' url' configuration items
99−
100− -- Auxiliary: load a template from a datafile
101− loadTemplate = fmap readTemplate . readFile <=< getDataFileName
102−
103− -- URL is required to have a valid field
104− noUrl = error "Hakyll.Web.Feed.renderFeed: no route specified"
105−
106−-- | Render an RSS feed with a number of items.
107−--
108−renderRss :: FeedConfiguration -- ^ Feed configuration
109− -> Compiler [Page String] String -- ^ Feed compiler
110−renderRss configuration = arr (map renderDate)
111− >>> renderFeed "templates/rss.xml" "templates/rss-item.xml" configuration
112− where
113− renderDate = renderDateField "timestamp" "%a, %d %b %Y %H:%M:%S UT"
114− "No date found."
115−
116−-- | Render an Atom feed with a number of items.
117−--
118−renderAtom :: FeedConfiguration -- ^ Feed configuration
119− -> Compiler [Page String] String -- ^ Feed compiler
120−renderAtom configuration = arr (map renderDate)
121− >>> renderFeed "templates/atom.xml" "templates/atom-item.xml" configuration
122− where
123− renderDate = renderDateField "timestamp" "%Y-%m-%dT%H:%M:%SZ"
124− "No date found."
src/Hakyll/Web/Page.hsdeleted−124
@@ -1,124 +0,0 @@
1−-- | A page is a key-value mapping, representing a page on your site
2−--
3−-- A page is an important concept in Hakyll. It is a key-value mapping, and has
4−-- one field with an arbitrary type. A 'Page' thus consists of
5−--
6−-- * a key-value mapping (of the type @Map String String@);
7−--
8−-- * a value (of the type @a@).
9−--
10−-- Usually, the value will be a 'String' as well, and the value will be the body
11−-- of the page.
12−--
13−-- Pages can be constructed using Haskell, but they are usually parsed from a
14−-- file. The file format for pages is pretty straightforward.
15−--
16−-- > This is a simple page
17−-- > consisting of two lines.
18−--
19−-- This is a valid page with two lines. If we load this in Hakyll, there would
20−-- be no metadata, and the body would be the given text. Let's look at a page
21−-- with some metadata.
22−--
23−-- > ---
24−-- > title: Alice's Adventures in Wonderland
25−-- > author: Lewis Caroll
26−-- > year: 1865
27−-- > ---
28−-- >
29−-- > Chapter I
30−-- > =========
31−-- >
32−-- > Down the Rabbit-Hole
33−-- > --------------------
34−-- >
35−-- > Alice was beginning to get very tired of sitting by her sister on the bank,
36−-- > and of having nothing to do: once or twice she had peeped into the book her
37−-- > sister was reading, but it had no pictures or conversations in it, "and
38−-- > what is the use of a book," thought Alice "without pictures or
39−-- > conversation?"
40−-- >
41−-- > ...
42−--
43−-- As you can see, we construct a metadata header in Hakyll using @---@. Then,
44−-- we simply list all @key: value@ pairs, and end with @---@ again. This page
45−-- contains three metadata fields and a body. The body is given in markdown
46−-- format, which can be easily rendered to HTML by Hakyll, using pandoc.
47−--
48−{-# LANGUAGE DeriveDataTypeable #-}
49−module Hakyll.Web.Page
50− ( Page (..)
51− , fromBody
52− , fromMap
53− , toMap
54− , readPageCompiler
55− , pageCompiler
56− , addDefaultFields
57− , sortByBaseName
58− ) where
59−
60−import Prelude hiding (id)
61−import Control.Category (id)
62−import Control.Arrow (arr, (>>^), (&&&), (>>>))
63−import System.FilePath (takeBaseName, takeDirectory)
64−import qualified Data.Map as M
65−import Data.List (sortBy)
66−import Data.Ord (comparing)
67−
68−import Hakyll.Core.Identifier
69−import Hakyll.Core.Compiler
70−import Hakyll.Core.Resource
71−import Hakyll.Web.Page.Internal
72−import Hakyll.Web.Page.Read
73−import Hakyll.Web.Page.Metadata
74−import Hakyll.Web.Pandoc
75−import Hakyll.Web.Template
76−import Hakyll.Web.Util.Url
77−
78−-- | Create a page from a body, without metadata
79−--
80−fromBody :: a -> Page a
81−fromBody = Page M.empty
82−
83−-- | Read a page (do not render it)
84−--
85−readPageCompiler :: Compiler Resource (Page String)
86−readPageCompiler = getResourceString >>^ readPage
87−
88−-- | Read a page, add default fields, substitute fields and render using pandoc
89−--
90−pageCompiler :: Compiler Resource (Page String)
91−pageCompiler = cached "Hakyll.Web.Page.pageCompiler" $
92− readPageCompiler >>> addDefaultFields >>> arr applySelf >>> pageRenderPandoc
93−
94−-- | Add a number of default metadata fields to a page. These fields include:
95−--
96−-- * @$url@
97−--
98−-- * @$category@
99−--
100−-- * @$title@
101−--
102−-- * @$path@
103−--
104−addDefaultFields :: Compiler (Page a) (Page a)
105−addDefaultFields = (getRoute &&& id >>^ uncurry addRoute)
106− >>> (getIdentifier &&& id >>^ uncurry addIdentifier)
107− where
108− -- Add root and url, based on route
109− addRoute Nothing = id
110− addRoute (Just r) = trySetField "url" (toUrl r)
111−
112− -- Add title and category, based on identifier
113− addIdentifier i = trySetField "title" (takeBaseName p)
114− . trySetField "category" (takeBaseName $ takeDirectory p)
115− . trySetField "path" p
116− where
117− p = toFilePath i
118−
119−-- | Sort posts based on the basename of the post. This is equivalent to a
120−-- chronologival sort, because of the @year-month-day-title.extension@ naming
121−-- convention in Hakyll.
122−--
123−sortByBaseName :: [Page a] -> [Page a]
124−sortByBaseName = sortBy $ comparing $ takeBaseName . getField "path"
src/Hakyll/Web/Page/Internal.hsdeleted−50
@@ -1,50 +0,0 @@
1−-- | Internal representation of the page datatype
2−--
3−{-# LANGUAGE DeriveDataTypeable #-}
4−module Hakyll.Web.Page.Internal
5− ( Page (..)
6− , fromMap
7− , toMap
8− ) where
9−
10−import Control.Applicative ((<$>), (<*>))
11−import Data.Monoid (Monoid, mempty, mappend)
12−
13−import Data.Map (Map)
14−import Data.Binary (Binary, get, put)
15−import Data.Typeable (Typeable)
16−import qualified Data.Map as M
17−
18−import Hakyll.Core.Writable
19−
20−-- | Type used to represent pages
21−--
22−data Page a = Page
23− { pageMetadata :: Map String String
24− , pageBody :: a
25− } deriving (Eq, Show, Typeable)
26−
27−instance Monoid a => Monoid (Page a) where
28− mempty = Page M.empty mempty
29− mappend (Page m1 b1) (Page m2 b2) =
30− Page (M.union m1 m2) (mappend b1 b2)
31−
32−instance Functor Page where
33− fmap f (Page m b) = Page m (f b)
34−
35−instance Binary a => Binary (Page a) where
36− put (Page m b) = put m >> put b
37− get = Page <$> get <*> get
38−
39−instance Writable a => Writable (Page a) where
40− write p (Page _ b) = write p b
41−
42−-- | Create a metadata page, without a body
43−--
44−fromMap :: Monoid a => Map String String -> Page a
45−fromMap m = Page m mempty
46−
47−-- | Convert a page to a map. The body will be placed in the @body@ key.
48−--
49−toMap :: Page String -> Map String String
50−toMap (Page m b) = M.insert "body" b m
src/Hakyll/Web/Page/Metadata.hsdeleted−156
@@ -1,156 +0,0 @@
1−-- | Provides various functions to manipulate the metadata fields of a page
2−--
3−module Hakyll.Web.Page.Metadata
4− ( getField
5− , getFieldMaybe
6− , setField
7− , trySetField
8− , setFieldA
9− , renderField
10− , changeField
11− , copyField
12− , renderDateField
13− , renderDateFieldWith
14− , copyBodyToField
15− , copyBodyFromField
16− ) where
17−
18−import Prelude hiding (id)
19−import Control.Category (id)
20−import Control.Arrow (Arrow, (>>>), (***), arr)
21−import Data.List (intercalate)
22−import Data.Maybe (fromMaybe)
23−import Data.Time.Clock (UTCTime)
24−import Data.Time.Format (parseTime, formatTime)
25−import qualified Data.Map as M
26−import System.FilePath (takeFileName)
27−import System.Locale (TimeLocale, defaultTimeLocale)
28−
29−import Hakyll.Web.Page.Internal
30−import Hakyll.Core.Util.String
31−
32−-- | Get a metadata field. If the field does not exist, the empty string is
33−-- returned.
34−--
35−getField :: String -- ^ Key
36− -> Page a -- ^ Page
37− -> String -- ^ Value
38−getField key = fromMaybe "" . getFieldMaybe key
39−
40−-- | Get a field in a 'Maybe' wrapper
41−--
42−getFieldMaybe :: String -- ^ Key
43− -> Page a -- ^ Page
44− -> Maybe String -- ^ Value, if found
45−getFieldMaybe key = M.lookup key . pageMetadata
46−
47−-- | Version of 'trySetField' which overrides any previous value
48−--
49−setField :: String -- ^ Key
50− -> String -- ^ Value
51− -> Page a -- ^ Page to add it to
52− -> Page a -- ^ Resulting page
53−setField k v (Page m b) = Page (M.insert k v m) b
54−
55−-- | Add a metadata field. If the field already exists, it is not overwritten.
56−--
57−trySetField :: String -- ^ Key
58− -> String -- ^ Value
59− -> Page a -- ^ Page to add it to
60− -> Page a -- ^ Resulting page
61−trySetField k v (Page m b) = Page (M.insertWith (flip const) k v m) b
62−
63−-- | Arrow-based variant of 'setField'. Because of it's type, this function is
64−-- very usable together with the different 'require' functions.
65−--
66−setFieldA :: Arrow a
67− => String -- ^ Key
68− -> a x String -- ^ Value arrow
69− -> a (Page b, x) (Page b) -- ^ Resulting arrow
70−setFieldA k v = id *** v >>> arr (uncurry $ flip $ setField k)
71−
72−-- | Do something with a metadata value, but keep the old value as well. If the
73−-- key given is not present in the metadata, nothing will happen. If the source
74−-- and destination keys are the same, the value will be changed (but you should
75−-- use 'changeField' for this purpose).
76−--
77−renderField :: String -- ^ Key of which the value should be copied
78− -> String -- ^ Key the value should be copied to
79− -> (String -> String) -- ^ Function to apply on the value
80− -> Page a -- ^ Page on which this should be applied
81− -> Page a -- ^ Resulting page
82−renderField src dst f page = case M.lookup src (pageMetadata page) of
83− Nothing -> page
84− Just value -> setField dst (f value) page
85−
86−-- | Change a metadata value.
87−--
88−-- > import Data.Char (toUpper)
89−-- > changeField "title" (map toUpper)
90−--
91−-- Will put the title in UPPERCASE.
92−--
93−changeField :: String -- ^ Key to change.
94− -> (String -> String) -- ^ Function to apply on the value.
95− -> Page a -- ^ Page to change
96− -> Page a -- ^ Resulting page
97−changeField key = renderField key key
98−
99−-- | Make a copy of a metadata field (put the value belonging to a certain key
100−-- under some other key as well)
101−--
102−copyField :: String -- ^ Key to copy
103− -> String -- ^ Destination to copy to
104− -> Page a -- ^ Page on which this should be applied
105− -> Page a -- ^ Resulting page
106−copyField src dst = renderField src dst id
107−
108−-- | When the metadata has a field called @path@ in a
109−-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),
110−-- this function can render the date.
111−--
112−-- > renderDate "date" "%B %e, %Y" "Date unknown"
113−--
114−-- Will render something like @January 32, 2010@.
115−--
116−renderDateField :: String -- ^ Key in which the rendered date should be placed
117− -> String -- ^ Format to use on the date
118− -> String -- ^ Default value, in case the date cannot be parsed
119− -> Page a -- ^ Page on which this should be applied
120− -> Page a -- ^ Resulting page
121−renderDateField = renderDateFieldWith defaultTimeLocale
122−
123−-- | This is an extended version of 'renderDateField' that allows you to
124−-- specify a time locale that is used for outputting the date. For more
125−-- details, see 'renderDateField'.
126−--
127−renderDateFieldWith :: TimeLocale -- ^ Output time locale
128− -> String -- ^ Destination key
129− -> String -- ^ Format to use on the date
130− -> String -- ^ Default value
131− -> Page a -- ^ Target page
132− -> Page a -- ^ Resulting page
133−renderDateFieldWith locale key format defaultValue =
134− renderField "path" key renderDate'
135− where
136− renderDate' filePath = fromMaybe defaultValue $ do
137− let dateString = intercalate "-" $ take 3
138− $ splitAll "-" $ takeFileName filePath
139− time <- parseTime defaultTimeLocale
140− "%Y-%m-%d"
141− dateString :: Maybe UTCTime
142− return $ formatTime locale format time
143−
144−-- | Copy the body of a page to a metadata field
145−--
146−copyBodyToField :: String -- ^ Destination key
147− -> Page String -- ^ Target page
148− -> Page String -- ^ Resulting page
149−copyBodyToField key page = setField key (pageBody page) page
150−
151−-- | Copy a metadata field to the page body
152−--
153−copyBodyFromField :: String -- ^ Source key
154− -> Page String -- ^ Target page
155− -> Page String -- ^ Resulting page
156−copyBodyFromField key page = fmap (const $ getField key page) page
src/Hakyll/Web/Page/Read.hsdeleted−60
@@ -1,60 +0,0 @@
1−-- | Module providing a function to parse a page from a file
2−--
3−module Hakyll.Web.Page.Read
4− ( readPage
5− ) where
6−
7−import Control.Applicative ((<$>), (<*>))
8−import Control.Arrow (second, (***))
9−import Control.Monad.State (State, get, put, evalState)
10−import Data.List (isPrefixOf)
11−import Data.Map (Map)
12−import qualified Data.Map as M
13−
14−import Hakyll.Web.Page.Internal
15−import Hakyll.Core.Util.String
16−
17−-- | We're using a simple state monad as parser
18−--
19−type LineParser = State [String]
20−
21−-- | Read the metadata section from a page
22−--
23−parseMetadata :: LineParser (Map String String)
24−parseMetadata = get >>= \content -> case content of
25− -- No lines means no metadata
26− [] -> return M.empty
27− -- Check if the file begins with a delimiter
28− (l : ls) -> if not (isPossibleDelimiter l)
29− then -- No delimiter means no metadata
30− return M.empty
31− else do -- Break the metadata section
32− let (metadata, rest) = second (drop 1) $ break (== l) ls
33− -- Put the rest back
34− put rest
35− -- Parse the metadata
36− return $ M.fromList $ map parseMetadata' metadata
37− where
38− -- Check if a line can be a delimiter
39− isPossibleDelimiter = isPrefixOf "---"
40−
41− -- Parse a "key: value" string to a (key, value) tupple
42− parseMetadata' = (trim *** trim . drop 1) . break (== ':')
43−
44−-- | Read the body section of a page
45−--
46−parseBody :: LineParser String
47−parseBody = do
48− body <- get
49− put []
50− return $ unlines body
51−
52−-- | Read an entire page
53−--
54−parsePage :: LineParser (Page String)
55−parsePage = Page <$> parseMetadata <*> parseBody
56−
57−-- | Read a page from a string
58−--
59−readPage :: String -> Page String
60−readPage = evalState parsePage . lines
src/Hakyll/Web/Pandoc.hsdeleted−110
@@ -1,110 +0,0 @@
1−-- | Module exporting pandoc bindings
2−--
3−module Hakyll.Web.Pandoc
4− ( -- * The basic building blocks
5− readPandoc
6− , readPandocWith
7− , writePandoc
8− , writePandocWith
9−
10− -- * Functions working on pages/compilers
11− , pageReadPandoc
12− , pageReadPandocWith
13− , pageRenderPandoc
14− , pageRenderPandocWith
15−
16− -- * Default options
17− , defaultHakyllParserState
18− , defaultHakyllWriterOptions
19− ) where
20−
21−import Prelude hiding (id)
22−import Control.Applicative ((<$>))
23−import Control.Arrow ((>>^), (&&&))
24−import Control.Category (id)
25−
26−import Text.Pandoc
27−
28−import Hakyll.Core.Compiler
29−import Hakyll.Web.Pandoc.FileType
30−import Hakyll.Web.Page.Internal
31−
32−-- | Read a string using pandoc, with the default options
33−--
34−readPandoc :: FileType -- ^ File type, determines how parsing happens
35− -> String -- ^ String to read
36− -> Pandoc -- ^ Resulting document
37−readPandoc = readPandocWith defaultHakyllParserState
38−
39−-- | Read a string using pandoc, with the supplied options
40−--
41−readPandocWith :: ParserState -- ^ Parser options
42− -> FileType -- ^ File type, determines how parsing happens
43− -> String -- ^ String to read
44− -> Pandoc -- ^ Resulting document
45−readPandocWith state fileType' = case fileType' of
46− Html -> readHtml state
47− LaTeX -> readLaTeX state
48− LiterateHaskell t -> readPandocWith state {stateLiterateHaskell = True} t
49− Markdown -> readMarkdown state
50− Rst -> readRST state
51− t -> error $
52− "Hakyll.Web.readPandocWith: I don't know how to read " ++ show t
53−
54−-- | Write a document (as HTML) using pandoc, with the default options
55−--
56−writePandoc :: Pandoc -- ^ Document to write
57− -> String -- ^ Resulting HTML
58−writePandoc = writePandocWith defaultHakyllWriterOptions
59−
60−-- | Write a document (as HTML) using pandoc, with the supplied options
61−--
62−writePandocWith :: WriterOptions -- ^ Writer options for pandoc
63− -> Pandoc -- ^ Document to write
64− -> String -- ^ Resulting HTML
65−writePandocWith = writeHtmlString
66−
67−-- | Read the resource using pandoc
68−--
69−pageReadPandoc :: Compiler (Page String) (Page Pandoc)
70−pageReadPandoc = pageReadPandocWith defaultHakyllParserState
71−
72−-- | Read the resource using pandoc
73−--
74−pageReadPandocWith :: ParserState -> Compiler (Page String) (Page Pandoc)
75−pageReadPandocWith state =
76− id &&& getFileType >>^ pageReadPandocWith'
77− where
78− pageReadPandocWith' (p, t) = readPandocWith state t <$> p
79−
80−-- | Render the resource using pandoc
81−--
82−pageRenderPandoc :: Compiler (Page String) (Page String)
83−pageRenderPandoc =
84− pageRenderPandocWith defaultHakyllParserState defaultHakyllWriterOptions
85−
86−-- | Render the resource using pandoc
87−--
88−pageRenderPandocWith :: ParserState
89− -> WriterOptions
90− -> Compiler (Page String) (Page String)
91−pageRenderPandocWith state options =
92− pageReadPandocWith state >>^ fmap (writePandocWith options)
93−
94−-- | The default reader options for pandoc parsing in hakyll
95−--
96−defaultHakyllParserState :: ParserState
97−defaultHakyllParserState = defaultParserState
98− { -- The following option causes pandoc to read smart typography, a nice
99− -- and free bonus.
100− stateSmart = True
101− }
102−
103−-- | The default writer options for pandoc rendering in hakyll
104−--
105−defaultHakyllWriterOptions :: WriterOptions
106−defaultHakyllWriterOptions = defaultWriterOptions
107− { -- This option causes literate haskell to be written using '>' marks in
108− -- html, which I think is a good default.
109− writerLiterateHaskell = True
110− }
src/Hakyll/Web/Pandoc/FileType.hsdeleted−55
@@ -1,55 +0,0 @@
1−-- | A module dealing with pandoc file extensions and associated file types
2−--
3−module Hakyll.Web.Pandoc.FileType
4− ( FileType (..)
5− , fileType
6− , getFileType
7− ) where
8−
9−import System.FilePath (takeExtension)
10−import Control.Arrow ((>>^))
11−
12−import Hakyll.Core.Identifier
13−import Hakyll.Core.Compiler
14−
15−-- | Datatype to represent the different file types Hakyll can deal with by
16−-- default
17−--
18−data FileType
19− = Html
20− | LaTeX
21− | LiterateHaskell FileType
22− | Markdown
23− | Rst
24− | PlainText
25− | Css
26− | Binary
27− deriving (Eq, Ord, Show, Read)
28−
29−-- | Get the file type for a certain file. The type is determined by extension.
30−--
31−fileType :: FilePath -> FileType
32−fileType = fileType' . takeExtension
33− where
34− fileType' ".htm" = Html
35− fileType' ".html" = Html
36− fileType' ".lhs" = LiterateHaskell Markdown
37− fileType' ".markdown" = Markdown
38− fileType' ".md" = Markdown
39− fileType' ".mdn" = Markdown
40− fileType' ".mdown" = Markdown
41− fileType' ".mdwn" = Markdown
42− fileType' ".mkd" = Markdown
43− fileType' ".mkdwn" = Markdown
44− fileType' ".page" = Markdown
45− fileType' ".rst" = Rst
46− fileType' ".tex" = LaTeX
47− fileType' ".text" = PlainText
48− fileType' ".txt" = PlainText
49− fileType' ".css" = Css
50− fileType' _ = Binary -- Treat unknown files as binary
51−
52−-- | Get the file type for the current file
53−--
54−getFileType :: Compiler a FileType
55−getFileType = getIdentifier >>^ fileType . toFilePath
src/Hakyll/Web/Preview/Server.hsdeleted−73
@@ -1,73 +0,0 @@
1−-- | Implements a basic static file server for previewing options
2−--
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Web.Preview.Server
5− ( staticServer
6− ) where
7−
8−import Control.Monad.Trans (liftIO)
9−import Control.Applicative ((<$>))
10−import Codec.Binary.UTF8.String
11−import Network.HTTP.Base (urlDecode)
12−import System.FilePath ((</>))
13−import System.Directory (doesFileExist)
14−
15−import qualified Data.ByteString as SB
16−import Snap.Util.FileServe (serveFile)
17−import Snap.Types (Snap, rqURI, getRequest, writeBS)
18−import Snap.Http.Server ( httpServe, setAccessLog, setErrorLog, addListen
19− , ConfigListen (..), emptyConfig
20− )
21−
22−import Hakyll.Core.Util.String (replaceAll)
23−
24−-- | The first file in the list that actually exists is returned
25−--
26−findFile :: [FilePath] -> IO (Maybe FilePath)
27−findFile [] = return Nothing
28−findFile (x : xs) = do
29− exists <- doesFileExist x
30− if exists then return (Just x) else findFile xs
31−
32−-- | Serve a given directory
33−--
34−static :: FilePath -- ^ Directory to serve
35− -> (FilePath -> IO ()) -- ^ Pre-serve hook
36− -> Snap ()
37−static directory preServe = do
38− -- Obtain the path
39− uri <- rqURI <$> getRequest
40− let filePath = replaceAll "\\?$" (const "") -- Remove trailing ?
41− $ replaceAll "#[^#]*$" (const "") -- Remove #section
42− $ replaceAll "^/" (const "") -- Remove leading /
43− $ urlDecode $ decode $ SB.unpack uri
44−
45− -- Try to find the requested file
46− r <- liftIO $ findFile $ map (directory </>) $
47− [ filePath
48− , filePath </> "index.htm"
49− , filePath </> "index.html"
50− ]
51−
52− case r of
53− -- Not found, error
54− Nothing -> writeBS "Not found"
55− -- Found, serve
56− Just f -> do
57− liftIO $ preServe f
58− serveFile f
59−
60−-- | Main method, runs a static server in the given directory
61−--
62−staticServer :: FilePath -- ^ Directory to serve
63− -> (FilePath -> IO ()) -- ^ Pre-serve hook
64− -> Int -- ^ Port to listen on
65− -> IO () -- ^ Blocks forever
66−staticServer directory preServe port =
67− httpServe config $ static directory preServe
68− where
69− -- Snap server config
70− config = addListen (ListenHttp "0.0.0.0" port)
71− $ setAccessLog Nothing
72− $ setErrorLog Nothing
73− $ emptyConfig
src/Hakyll/Web/RelativizeUrls.hsdeleted−62
@@ -1,62 +0,0 @@
1−-- | This module exposes a function which can relativize URL's on a webpage.
2−--
3−-- This means that one can deploy the resulting site on
4−-- @http:\/\/example.com\/@, but also on @http:\/\/example.com\/some-folder\/@
5−-- without having to change anything (simply copy over the files).
6−--
7−-- To use it, you should use absolute URL's from the site root everywhere. For
8−-- example, use
9−--
10−-- > <img src="/images/lolcat.png" alt="Funny zomgroflcopter" />
11−--
12−-- in a blogpost. When running this through the relativize URL's module, this
13−-- will result in (suppose your blogpost is located at @\/posts\/foo.html@:
14−--
15−-- > <img src="../images/lolcat.png" alt="Funny zomgroflcopter" />
16−--
17−module Hakyll.Web.RelativizeUrls
18− ( relativizeUrlsCompiler
19− , relativizeUrls
20− ) where
21−
22−import Prelude hiding (id)
23−import Control.Category (id)
24−import Control.Arrow ((&&&), (>>^))
25−import Data.List (isPrefixOf)
26−import qualified Data.Set as S
27−
28−import Text.HTML.TagSoup
29−
30−import Hakyll.Core.Compiler
31−import Hakyll.Web.Page
32−import Hakyll.Web.Util.Url
33−
34−-- | Compiler form of 'relativizeUrls' which automatically picks the right root
35−-- path
36−--
37−relativizeUrlsCompiler :: Compiler (Page String) (Page String)
38−relativizeUrlsCompiler = getRoute &&& id >>^ uncurry relativize
39− where
40− relativize Nothing = id
41− relativize (Just r) = fmap (relativizeUrls $ toSiteRoot r)
42−
43−-- | Relativize URL's in HTML
44−--
45−relativizeUrls :: String -- ^ Path to the site root
46− -> String -- ^ HTML to relativize
47− -> String -- ^ Resulting HTML
48−relativizeUrls root = renderTags . map relativizeUrls' . parseTags
49− where
50− relativizeUrls' (TagOpen s a) = TagOpen s $ map (relativizeUrlsAttrs root) a
51− relativizeUrls' x = x
52−
53−-- | Relativize URL's in attributes
54−--
55−relativizeUrlsAttrs :: String -- ^ Path to the site root
56− -> Attribute String -- ^ Attribute to relativize
57− -> Attribute String -- ^ Resulting attribute
58−relativizeUrlsAttrs root (key, value)
59− | key `S.member` urls && "/" `isPrefixOf` value = (key, root ++ value)
60− | otherwise = (key, value)
61− where
62− urls = S.fromList ["src", "href"]
src/Hakyll/Web/Tags.hsdeleted−204
@@ -1,204 +0,0 @@
1−-- | Module containing some specialized functions to deal with tags.
2−-- This Module follows certain conventions. My advice is to stick with them if
3−-- possible.
4−--
5−-- More concrete: all functions in this module assume that the tags are
6−-- located in the @tags@ field, and separated by commas. An example file
7−-- @foo.markdown@ could look like:
8−--
9−-- > ---
10−-- > author: Philip K. Dick
11−-- > title: Do androids dream of electric sheep?
12−-- > tags: future, science fiction, humanoid
13−-- > ---
14−-- > The novel is set in a post-apocalyptic near future, where the Earth and
15−-- > its populations have been damaged greatly by Nuclear...
16−--
17−-- All the following functions would work with such a format. In addition to
18−-- tags, Hakyll also supports categories. The convention when using categories
19−-- is to place pages in subdirectories.
20−--
21−-- An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@
22−-- Tags or categories are read using the @readTags@ and @readCategory@
23−-- functions. This module only provides functions to work with tags:
24−-- categories are represented as tags. This is perfectly possible: categories
25−-- only have an additional restriction that a page can only have one category
26−-- (instead of multiple tags).
27−--
28−{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, Arrows #-}
29−module Hakyll.Web.Tags
30− ( Tags (..)
31− , readTagsWith
32− , readTags
33− , readCategory
34− , renderTagCloud
35− , renderTagList
36− , renderTagsField
37− , renderCategoryField
38− ) where
39−
40−import Prelude hiding (id)
41−import Control.Category (id)
42−import Control.Applicative ((<$>))
43−import qualified Data.Map as M
44−import Data.List (intersperse, intercalate)
45−import Control.Arrow (arr, (&&&), (>>>), (***), (<<^), returnA)
46−import Data.Maybe (catMaybes, fromMaybe)
47−import Data.Monoid (mconcat)
48−
49−import Data.Typeable (Typeable)
50−import Data.Binary (Binary, get, put)
51−import Text.Blaze.Renderer.String (renderHtml)
52−import Text.Blaze ((!), toHtml, toValue)
53−import qualified Text.Blaze.Html5 as H
54−import qualified Text.Blaze.Html5.Attributes as A
55−
56−import Hakyll.Web.Page
57−import Hakyll.Web.Page.Metadata
58−import Hakyll.Web.Util.Url
59−import Hakyll.Core.Writable
60−import Hakyll.Core.Identifier
61−import Hakyll.Core.Compiler
62−import Hakyll.Core.Util.String
63−
64−-- | Data about tags
65−--
66−data Tags a = Tags
67− { tagsMap :: [(String, [Page a])]
68− } deriving (Show, Typeable)
69−
70−instance Binary a => Binary (Tags a) where
71− get = Tags <$> get
72− put (Tags m) = put m
73−
74−instance Writable (Tags a) where
75− write _ _ = return ()
76−
77−-- | Obtain tags from a page
78−--
79−getTags :: Page a -> [String]
80−getTags = map trim . splitAll "," . getField "tags"
81−
82−-- | Obtain categories from a page
83−--
84−getCategory :: Page a -> [String]
85−getCategory = return . getField "category"
86−
87−-- | Higher-level function to read tags
88−--
89−readTagsWith :: (Page a -> [String]) -- ^ Function extracting tags from a page
90− -> [Page a] -- ^ Pages
91− -> Tags a -- ^ Resulting tags
92−readTagsWith f pages = Tags
93− { tagsMap = M.toList $
94− foldl (M.unionWith (++)) M.empty (map readTagsWith' pages)
95− }
96− where
97− -- Create a tag map for one page
98− readTagsWith' page =
99− let tags = f page
100− in M.fromList $ zip tags $ repeat [page]
101−
102−-- | Read a tagmap using the @tags@ metadata field
103−--
104−readTags :: [Page a] -> Tags a
105−readTags = readTagsWith getTags
106−
107−-- | Read a tagmap using the @category@ metadata field
108−--
109−readCategory :: [Page a] -> Tags a
110−readCategory = readTagsWith getCategory
111−
112−-- | Render tags in HTML
113−--
114−renderTags :: (String -> Identifier)
115− -- ^ Produce a link
116− -> (String -> String -> Int -> Int -> Int -> String)
117− -- ^ Produce a tag item: tag, url, count, min count, max count
118− -> ([String] -> String)
119− -- ^ Join items
120− -> Compiler (Tags a) String
121− -- ^ Tag cloud renderer
122−renderTags makeUrl makeItem concatItems = proc (Tags tags) -> do
123− -- In tags' we create a list: [((tag, route), count)]
124− tags' <- mapCompiler ((id &&& (getRouteFor <<^ makeUrl)) *** arr length)
125− -< tags
126−
127− let -- Absolute frequencies of the pages
128− freqs = map snd tags'
129−
130− -- The minimum and maximum count found
131− (min', max')
132− | null freqs = (0, 1)
133− | otherwise = (minimum &&& maximum) freqs
134−
135− -- Create a link for one item
136− makeItem' ((tag, url), count) =
137− makeItem tag (toUrl $ fromMaybe "/" url) count min' max'
138−
139− -- Render and return the HTML
140− returnA -< concatItems $ map makeItem' tags'
141−
142−-- | Render a tag cloud in HTML
143−--
144−renderTagCloud :: (String -> Identifier) -- ^ Produce a link for a tag
145− -> Double -- ^ Smallest font size, in percent
146− -> Double -- ^ Biggest font size, in percent
147− -> Compiler (Tags a) String -- ^ Tag cloud renderer
148−renderTagCloud makeUrl minSize maxSize =
149− renderTags makeUrl makeLink (intercalate " ")
150− where
151− makeLink tag url count min' max' = renderHtml $
152− H.a ! A.style (toValue $ "font-size: " ++ size count min' max')
153− ! A.href (toValue url)
154− $ toHtml tag
155−
156− -- Show the relative size of one 'count' in percent
157− size count min' max' =
158− let diff = 1 + fromIntegral max' - fromIntegral min'
159− relative = (fromIntegral count - fromIntegral min') / diff
160− size' = floor $ minSize + relative * (maxSize - minSize)
161− in show (size' :: Int) ++ "%"
162−
163−-- | Render a simple tag list in HTML, with the tag count next to the item
164−--
165−renderTagList :: (String -> Identifier) -> Compiler (Tags a) (String)
166−renderTagList makeUrl = renderTags makeUrl makeLink (intercalate ", ")
167− where
168− makeLink tag url count _ _ = renderHtml $
169− H.a ! A.href (toValue url) $ toHtml (tag ++ "(" ++ show count ++ ")")
170−
171−-- | Render tags with links
172−--
173−renderTagsFieldWith :: (Page a -> [String]) -- ^ Function to get the tags
174− -> String -- ^ Destination key
175− -> (String -> Identifier) -- ^ Create a link for a tag
176− -> Compiler (Page a) (Page a) -- ^ Resulting compiler
177−renderTagsFieldWith tags destination makeUrl =
178− id &&& arr tags >>> setFieldA destination renderTags'
179− where
180− -- Compiler creating a comma-separated HTML string for a list of tags
181− renderTags' :: Compiler [String] String
182− renderTags' = arr (map $ id &&& makeUrl)
183− >>> mapCompiler (id *** getRouteFor)
184− >>> arr (map $ uncurry renderLink)
185− >>> arr (renderHtml . mconcat . intersperse ", " . catMaybes)
186−
187− -- Render one tag link
188− renderLink _ Nothing = Nothing
189− renderLink tag (Just filePath) = Just $
190− H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag
191−
192−-- | Render tags with links
193−--
194−renderTagsField :: String -- ^ Destination key
195− -> (String -> Identifier) -- ^ Create a link for a tag
196− -> Compiler (Page a) (Page a) -- ^ Resulting compiler
197−renderTagsField = renderTagsFieldWith getTags
198−
199−-- | Render the category in a link
200−--
201−renderCategoryField :: String -- ^ Destination key
202− -> (String -> Identifier) -- ^ Create a category link
203− -> Compiler (Page a) (Page a) -- ^ Resulting compiler
204−renderCategoryField = renderTagsFieldWith getCategory
src/Hakyll/Web/Template.hsdeleted−109
@@ -1,109 +0,0 @@
1−-- | This module provides means for reading and applying 'Template's.
2−--
3−-- Templates are tools to convert data (pages) into a string. They are
4−-- perfectly suited 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−-- We can use this template to render a 'Page' which has a body and a @$title$@
26−-- metadata field.
27−--
28−-- As you can see, the format is very simple -- @$key$@ is used to render the
29−-- @$key$@ field from the page, everything else is literally copied. If you want
30−-- to literally insert @\"$key$\"@ into your page (for example, when you're
31−-- writing a Hakyll tutorial) you can use
32−--
33−-- > <p>
34−-- > A literal $$key$$.
35−-- > </p>
36−--
37−-- Because of it's simplicity, these templates can be used for more than HTML:
38−-- you could make, for example, CSS or JS templates as well.
39−--
40−-- In addition to the native format, Hakyll also supports hamlet templates. For
41−-- more information on hamlet templates, please refer to:
42−-- <http://hackage.haskell.org/package/hamlet>.
43−--
44−module Hakyll.Web.Template
45− ( Template
46− , applyTemplate
47− , applySelf
48− , templateCompiler
49− , templateCompilerWith
50− , applyTemplateCompiler
51− ) where
52−
53−import Control.Arrow
54−import Data.Maybe (fromMaybe)
55−import System.FilePath (takeExtension)
56−import qualified Data.Map as M
57−
58−import Text.Hamlet (HamletSettings, defaultHamletSettings)
59−
60−import Hakyll.Core.Compiler
61−import Hakyll.Core.Identifier
62−import Hakyll.Core.Resource
63−import Hakyll.Web.Template.Internal
64−import Hakyll.Web.Template.Read
65−import Hakyll.Web.Page.Internal
66−
67−-- | Substitutes @$identifiers@ in the given @Template@ by values from the given
68−-- "Page". When a key is not found, it is left as it is. You can specify
69−-- the characters used to replace escaped dollars (@$$@) here.
70−--
71−applyTemplate :: Template -> Page String -> Page String
72−applyTemplate template page =
73− fmap (const $ substitute =<< unTemplate template) page
74− where
75− map' = toMap page
76− substitute (Chunk chunk) = chunk
77− substitute (Key key) = fromMaybe ("$" ++ key ++ "$") $ M.lookup key map'
78− substitute (Escaped) = "$"
79−
80−-- | Apply a page as it's own template. This is often very useful to fill in
81−-- certain keys like @$root@ and @$url@.
82−--
83−applySelf :: Page String -> Page String
84−applySelf page = applyTemplate (readTemplate $ pageBody page) page
85−
86−-- | Read a template. If the extension of the file we're compiling is
87−-- @.hml@ or @.hamlet@, it will be considered as a Hamlet template, and parsed
88−-- as such.
89−--
90−templateCompiler :: Compiler Resource Template
91−templateCompiler = templateCompilerWith defaultHamletSettings
92−
93−-- | Version of 'templateCompiler' that enables custom settings.
94−--
95−templateCompilerWith :: HamletSettings -> Compiler Resource Template
96−templateCompilerWith settings =
97− cached "Hakyll.Web.Template.templateCompilerWith" $
98− getIdentifier &&& getResourceString >>^ uncurry read'
99− where
100− read' identifier string =
101− if takeExtension (toFilePath identifier) `elem` [".hml", ".hamlet"]
102− -- Hamlet template
103− then readHamletTemplateWith settings string
104− -- Hakyll template
105− else readTemplate string
106−
107−applyTemplateCompiler :: Identifier -- ^ Template
108− -> Compiler (Page String) (Page String) -- ^ Compiler
109−applyTemplateCompiler identifier = require identifier (flip applyTemplate)
src/Hakyll/Web/Template/Internal.hsdeleted−45
@@ -1,45 +0,0 @@
1−-- | Module containing the template data structure
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
4−module Hakyll.Web.Template.Internal
5− ( Template (..)
6− , TemplateElement (..)
7− ) where
8−
9−import Control.Applicative ((<$>))
10−
11−import Data.Binary (Binary, get, getWord8, put, putWord8)
12−import Data.Typeable (Typeable)
13−
14−import Hakyll.Core.Writable
15−
16−-- | Datatype used for template substitutions.
17−--
18−newtype Template = Template
19− { unTemplate :: [TemplateElement]
20− }
21− deriving (Show, Eq, Binary, Typeable)
22−
23−instance Writable Template where
24− -- Writing a template is impossible
25− write _ _ = return ()
26−
27−-- | Elements of a template.
28−--
29−data TemplateElement
30− = Chunk String
31− | Key String
32− | Escaped
33− deriving (Show, Eq, Typeable)
34−
35−instance Binary TemplateElement where
36− put (Chunk string) = putWord8 0 >> put string
37− put (Key key) = putWord8 1 >> put key
38− put (Escaped) = putWord8 2
39−
40− get = getWord8 >>= \tag -> case tag of
41− 0 -> Chunk <$> get
42− 1 -> Key <$> get
43− 2 -> return Escaped
44− _ -> error $ "Hakyll.Web.Template.Internal: "
45− ++ "Error reading cached template"
src/Hakyll/Web/Template/Read.hsdeleted−10
@@ -1,10 +0,0 @@
1−-- | Re-exports all different template reading modules
2−--
3−module Hakyll.Web.Template.Read
4− ( readTemplate
5− , readHamletTemplate
6− , readHamletTemplateWith
7− ) where
8−
9−import Hakyll.Web.Template.Read.Hakyll
10−import Hakyll.Web.Template.Read.Hamlet
src/Hakyll/Web/Template/Read/Hakyll.hsdeleted−35
@@ -1,35 +0,0 @@
1−-- | Read templates in Hakyll's native format
2−--
3−module Hakyll.Web.Template.Read.Hakyll
4− ( readTemplate
5− ) where
6−
7−import Data.List (isPrefixOf)
8−import Data.Char (isAlphaNum)
9−
10−import Hakyll.Web.Template.Internal
11−
12−-- | Construct a @Template@ from a string.
13−--
14−readTemplate :: String -> Template
15−readTemplate = Template . readTemplate'
16− where
17− readTemplate' [] = []
18− readTemplate' string
19− | "$$" `isPrefixOf` string =
20− Escaped : readTemplate' (drop 2 string)
21− | "$" `isPrefixOf` string =
22− case readKey (drop 1 string) of
23− Just (key, rest) -> Key key : readTemplate' rest
24− Nothing -> Chunk "$" : readTemplate' (drop 1 string)
25− | otherwise =
26− let (chunk, rest) = break (== '$') string
27− in Chunk chunk : readTemplate' rest
28−
29− -- Parse an key into (key, rest) if it's valid, and return
30− -- Nothing otherwise
31− readKey string =
32− let (key, rest) = span isAlphaNum string
33− in if not (null key) && "$" `isPrefixOf` rest
34− then Just (key, drop 1 rest)
35− else Nothing
src/Hakyll/Web/Template/Read/Hamlet.hsdeleted−46
@@ -1,46 +0,0 @@
1−-- | Read templates in the hamlet format
2−--
3−{-# LANGUAGE MultiParamTypeClasses #-}
4−module Hakyll.Web.Template.Read.Hamlet
5− ( readHamletTemplate
6− , readHamletTemplateWith
7− ) where
8−
9−import Text.Hamlet (HamletSettings (..), defaultHamletSettings)
10−import Text.Hamlet.RT
11−
12−import Hakyll.Web.Template.Internal
13−
14−-- | Read a hamlet template using the default settings
15−--
16−readHamletTemplate :: String -> Template
17−readHamletTemplate = readHamletTemplateWith defaultHamletSettings
18−
19−-- | Read a hamlet template using the specified settings
20−--
21−readHamletTemplateWith :: HamletSettings -> String -> Template
22−readHamletTemplateWith settings string =
23− let result = parseHamletRT settings string
24− in case result of
25− Just hamlet -> fromHamletRT hamlet
26− Nothing -> error
27− "Hakyll.Web.Template.Read.Hamlet.readHamletTemplateWith: \
28− \Could not parse Hamlet file"
29−
30−-- | Convert a 'HamletRT' to a 'Template'
31−--
32−fromHamletRT :: HamletRT -- ^ Hamlet runtime template
33− -> Template -- ^ Hakyll template
34−fromHamletRT (HamletRT sd) = Template $ map fromSimpleDoc sd
35− where
36− fromSimpleDoc :: SimpleDoc -> TemplateElement
37− fromSimpleDoc (SDRaw chunk) = Chunk chunk
38− fromSimpleDoc (SDVar [var]) = Key var
39− fromSimpleDoc (SDVar _) = error
40− "Hakyll.Web.Template.Read.Hamlet.fromHamletRT: \
41− \Hakyll does not support '.' in identifier names when using \
42− \hamlet templates."
43− fromSimpleDoc _ = error
44− "Hakyll.Web.Template.Read.Hamlet.fromHamletRT: \
45− \Only simple $key$ identifiers are allowed when using hamlet \
46− \templates."
src/Hakyll/Web/Util/Url.hsdeleted−35
@@ -1,35 +0,0 @@
1−-- | Miscellaneous URL manipulation functions.
2−--
3−module Hakyll.Web.Util.Url
4− ( toUrl
5− , toSiteRoot
6− ) where
7−
8−import System.FilePath (splitPath, takeDirectory, joinPath)
9−
10−-- | Convert a filepath to an URL starting from the site root
11−--
12−-- Example:
13−--
14−-- > toUrl "foo/bar.html"
15−--
16−-- Result:
17−--
18−-- > "/foo/bar.html"
19−--
20−toUrl :: FilePath -> String
21−toUrl ('/' : xs) = '/' : xs
22−toUrl url = '/' : url
23−
24−-- | Get the relative url to the site root, for a given (absolute) url
25−--
26−toSiteRoot :: String -> String
27−toSiteRoot = emptyException . joinPath . map parent
28− . filter relevant . splitPath . takeDirectory
29− where
30− parent = const ".."
31− emptyException [] = "."
32− emptyException x = x
33− relevant "." = False
34− relevant "/" = False
35− relevant _ = True
src/Init.hsnew file+123
@@ -0,0 +1,123 @@
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.yamlnew file+33
@@ -0,0 +1,33 @@
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.hsnew file+30
@@ -0,0 +1,30 @@
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.hsnew file+71
@@ -0,0 +1,71 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.Dependencies.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Data.List (delete)
10+import qualified Data.Map as M
11+import qualified Data.Set as S
12+import Test.Tasty (TestTree, testGroup)
13+import Test.Tasty.HUnit (Assertion, (@=?))
14+
15+
16+--------------------------------------------------------------------------------
17+import Hakyll.Core.Dependencies
18+import Hakyll.Core.Identifier
19+import TestSuite.Util
20+
21+
22+--------------------------------------------------------------------------------
23+tests :: TestTree
24+tests = testGroup "Hakyll.Core.Dependencies.Tests" $
25+ fromAssertions "analyze" [case01, case02, case03]
26+
27+
28+--------------------------------------------------------------------------------
29+oldUniverse :: [Identifier]
30+oldUniverse = M.keys oldFacts
31+
32+
33+--------------------------------------------------------------------------------
34+oldFacts :: DependencyFacts
35+oldFacts = M.fromList
36+ [ ("posts/01.md",
37+ [])
38+ , ("posts/02.md",
39+ [])
40+ , ("index.md",
41+ [ PatternDependency "posts/*"
42+ (S.fromList ["posts/01.md", "posts/02.md"])
43+ , IdentifierDependency "posts/01.md"
44+ , IdentifierDependency "posts/02.md"
45+ ])
46+ ]
47+
48+
49+--------------------------------------------------------------------------------
50+-- | posts/02.md has changed
51+case01 :: Assertion
52+case01 = S.fromList ["posts/02.md", "index.md"] @=? ood
53+ where
54+ (ood, _, _) = outOfDate oldUniverse (S.singleton "posts/02.md") oldFacts
55+
56+
57+--------------------------------------------------------------------------------
58+-- | about.md was added
59+case02 :: Assertion
60+case02 = S.singleton "about.md" @=? ood
61+ where
62+ (ood, _, _) = outOfDate ("about.md" : oldUniverse) S.empty oldFacts
63+
64+
65+--------------------------------------------------------------------------------
66+-- | posts/01.md was removed
67+case03 :: Assertion
68+case03 = S.singleton "index.md" @=? ood
69+ where
70+ (ood, _, _) =
71+ outOfDate ("posts/01.md" `delete` oldUniverse) S.empty oldFacts
tests/Hakyll/Core/DependencyAnalyzer/Tests.hsdeleted−70
@@ -1,70 +0,0 @@
1−module Hakyll.Core.DependencyAnalyzer.Tests where
2−
3−import Control.Arrow (second)
4−import qualified Data.Set as S
5−import Data.Monoid (mempty)
6−
7−import Test.Framework
8−import Test.Framework.Providers.HUnit
9−import Test.HUnit hiding (Test)
10−
11−import Hakyll.Core.DirectedGraph
12−import Hakyll.Core.DependencyAnalyzer
13−
14−tests :: [Test]
15−tests =
16− [ testCase "step [1]" step1
17− , testCase "step [2]" step2
18− ]
19−
20−step1 :: Assertion
21−step1 = Just (S.fromList [1, 2, 5, 6, 7, 8, 9]) @?=
22− stepAll (makeDependencyAnalyzer graph isOutOfDate prev)
23− where
24− node = curry $ second S.fromList
25−
26− graph = fromList
27− [ node (8 :: Int) [2, 4, 6]
28− , node 2 [4, 3]
29− , node 4 [3]
30− , node 6 [4]
31− , node 3 []
32− , node 9 [5]
33− , node 5 [7]
34− , node 1 [7]
35− , node 7 []
36− ]
37−
38− prev = fromList
39− [ node 8 [2, 4, 6]
40− , node 2 [4, 3]
41− , node 4 [3]
42− , node 6 [4]
43− , node 3 []
44− , node 9 [5]
45− , node 5 [7]
46− , node 1 [7]
47− , node 7 [8]
48− ]
49−
50− isOutOfDate = (`elem` [5, 2, 6])
51−
52−step2 :: Assertion
53−step2 = Nothing @?= stepAll (makeDependencyAnalyzer graph isOutOfDate mempty)
54− where
55− node = curry $ second S.fromList
56−
57− -- Cycle: 4 -> 7 -> 5 -> 9 -> 4
58− graph = fromList
59− [ node (1 :: Int) [6]
60− , node 2 [3]
61− , node 3 []
62− , node 4 [1, 7, 8]
63− , node 5 [9]
64− , node 6 [3]
65− , node 7 [5]
66− , node 8 [2]
67− , node 9 [4]
68− ]
69−
70− isOutOfDate = const True
tests/Hakyll/Core/Identifier/Tests.hs+32−12
@@ -1,21 +1,31 @@
1+--------------------------------------------------------------------------------
12{-# LANGUAGE OverloadedStrings #-}
23module Hakyll.Core.Identifier.Tests
34 ( tests
45 ) where
56
6−import Test.Framework
7−import Test.HUnit hiding (Test)
87
9−import Hakyll.Core.Identifier.Pattern
10−import TestSuite.Util
8+--------------------------------------------------------------------------------
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit ((@=?))
1111
12−tests :: [Test]
13−tests = concat
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Core.Identifier
15+import Hakyll.Core.Identifier.Pattern
16+import TestSuite.Util
17+
18+
19+--------------------------------------------------------------------------------
20+tests :: TestTree
21+tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
1422 [ captureTests
15− , regexTests
23+ , matchesTests
1624 ]
1725
18−captureTests :: [Test]
26+
27+--------------------------------------------------------------------------------
28+captureTests :: [TestTree]
1929captureTests = fromAssertions "capture"
2030 [ Just ["bar"] @=? capture "foo/**" "foo/bar"
2131 , Just ["foo/bar"] @=? capture "**" "foo/bar"
@@ -32,10 +42,20 @@ captureTests = fromAssertions "capture"
3242 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
3343 , Just [] @=? capture "\\*.jpg" "*.jpg"
3444 , Nothing @=? capture "\\*.jpg" "foo.jpg"
45+ , Just ["xyz","42"] @=? capture (fromRegex "cat-([a-z]+)/foo([0-9]+).jpg") "cat-xyz/foo42.jpg"
3546 ]
3647
37−regexTests :: [Test]
38−regexTests = fromAssertions "regex"
39− [ True @=? matches (regex "^foo/[^x]*$") "foo/bar"
40− , False @=? matches (regex "^foo/[^x]*$") "foo/barx"
48+
49+--------------------------------------------------------------------------------
50+matchesTests :: [TestTree]
51+matchesTests = fromAssertions "matches"
52+ [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
53+ , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
54+ , True @=? matches (fromVersion (Just "xz")) (setVersion (Just "xz") "bar")
55+ , True @=? matches (fromRegex "^foo/[^x]*$") "foo/bar"
56+ , False @=? matches (fromRegex "^foo/[^x]*$") "foo/barx"
57+ , True @=? matches (complement "foo.markdown") "bar.markdown"
58+ , False @=? matches (complement "foo.markdown") "foo.markdown"
59+ , True @=? matches ("foo" .||. "bar") "bar"
60+ , False @=? matches ("bar" .&&. hasNoVersion) (setVersion (Just "xz") "bar")
4161 ]
tests/Hakyll/Core/Provider/Metadata/Tests.hsnew file+62
@@ -0,0 +1,62 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Provider.Metadata.Tests
3+ ( tests
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
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
12+import Hakyll.Core.Provider.Metadata
13+import Test.Tasty (TestTree, testGroup)
14+import Test.Tasty.HUnit (Assertion, assertFailure, (@=?))
15+import TestSuite.Util
16+
17+
18+--------------------------------------------------------------------------------
19+tests :: TestTree
20+tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
21+ fromAssertions "page" [testPage01, testPage02]
22+
23+
24+
25+--------------------------------------------------------------------------------
26+testPage01 :: Assertion
27+testPage01 =
28+ (meta [("foo", "bar")], "qux\n") `expectRight` parsePage
29+ "---\n\
30+ \foo: bar\n\
31+ \---\n\
32+ \qux\n"
33+
34+
35+--------------------------------------------------------------------------------
36+testPage02 :: Assertion
37+testPage02 =
38+ (meta [("description", descr)], "Hello I am dog\n") `expectRight`
39+ parsePage
40+ "---\n\
41+ \description: A long description that would look better if it\n\
42+ \ spanned multiple lines and was indented\n\
43+ \---\n\
44+ \Hello I am dog\n"
45+ where
46+ descr :: String
47+ descr =
48+ "A long description that would look better if it \
49+ \spanned multiple lines and was indented"
50+
51+
52+--------------------------------------------------------------------------------
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
tests/Hakyll/Core/Provider/Tests.hsnew file+36
@@ -0,0 +1,36 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.Provider.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Hakyll.Core.Metadata
10+import Hakyll.Core.Provider
11+import Test.Tasty (TestTree, testGroup)
12+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
13+import TestSuite.Util
14+
15+
16+--------------------------------------------------------------------------------
17+tests :: TestTree
18+tests = testGroup "Hakyll.Core.Provider.Tests"
19+ [ testCase "case01" case01
20+ ]
21+
22+
23+--------------------------------------------------------------------------------
24+case01 :: Assertion
25+case01 = do
26+ store <- newTestStore
27+ provider <- newTestProvider store
28+ True @=? resourceExists provider "example.md"
29+
30+ metadata <- resourceMetadata provider "example.md"
31+ Just "An example" @=? lookupString "title" metadata
32+ Just "External data" @=? lookupString "external" metadata
33+
34+ doesntExist <- resourceMetadata provider "doesntexist.md"
35+ mempty @=? doesntExist
36+ cleanTestEnv
tests/Hakyll/Core/Routes/Tests.hs+41−15
@@ -1,24 +1,50 @@
1+--------------------------------------------------------------------------------
12{-# LANGUAGE OverloadedStrings #-}
23module Hakyll.Core.Routes.Tests
34 ( tests
45 ) where
56
6−import Test.Framework
7−import Test.HUnit hiding (Test)
87
9−import Hakyll.Core.Routes
10−import TestSuite.Util
8+--------------------------------------------------------------------------------
9+import Data.Maybe (fromMaybe)
10+import Hakyll.Core.Identifier
11+import Hakyll.Core.Metadata
12+import Hakyll.Core.Routes
13+import System.FilePath ((</>))
14+import Test.Tasty (TestTree, testGroup)
15+import Test.Tasty.HUnit (Assertion, (@=?))
16+import TestSuite.Util
1117
12−tests :: [Test]
13−tests = fromAssertions "runRoutes"
14− [ Just "foo.html" @=? runRoutes (setExtension "html") "foo"
15− , Just "foo.html" @=? runRoutes (setExtension ".html") "foo"
16− , Just "foo.html" @=? runRoutes (setExtension "html") "foo.markdown"
17− , Just "foo.html" @=? runRoutes (setExtension ".html") "foo.markdown"
1818
19− , Just "tags/bar.xml" @=?
20− runRoutes (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
21− , Just "tags/bar.xml" @=?
22− runRoutes (gsubRoute "rss/" (const "") `composeRoutes`
23− setExtension "xml") "tags/rss/bar"
19+--------------------------------------------------------------------------------
20+tests :: TestTree
21+tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
22+ [ testRoutes "foo.html" (setExtension "html") "foo"
23+ , testRoutes "foo.html" (setExtension ".html") "foo"
24+ , testRoutes "foo.html" (setExtension "html") "foo.markdown"
25+ , testRoutes "foo.html" (setExtension ".html") "foo.markdown"
26+
27+ , testRoutes "neve ro ddo reven"
28+ (customRoute (reverse . toFilePath )) "never odd or even"
29+
30+ , testRoutes "foo" (constRoute "foo") "bar"
31+
32+ , testRoutes "tags/bar.xml" (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
33+ , testRoutes "tags/bar.xml"
34+ (gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml")
35+ "tags/rss/bar"
36+
37+ , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
38+ fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
39+ "example.md"
2440 ]
41+
42+
43+--------------------------------------------------------------------------------
44+testRoutes :: FilePath -> Routes -> Identifier -> Assertion
45+testRoutes expected r id' = do
46+ store <- newTestStore
47+ provider <- newTestProvider store
48+ (route, _) <- runRoutes r provider id'
49+ Just expected @=? route
50+ cleanTestEnv
tests/Hakyll/Core/Rules/Tests.hsnew file+104
@@ -0,0 +1,104 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.Rules.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Data.IORef (IORef, newIORef, readIORef,
10+ writeIORef)
11+import qualified Data.Set as S
12+import Hakyll.Core.Compiler
13+import Hakyll.Core.File
14+import Hakyll.Core.Identifier
15+import Hakyll.Core.Identifier.Pattern
16+import Hakyll.Core.Metadata
17+import Hakyll.Core.Routes
18+import Hakyll.Core.Rules
19+import Hakyll.Core.Rules.Internal
20+import System.FilePath ((</>))
21+import Test.Tasty (TestTree, testGroup)
22+import Test.Tasty.HUnit (Assertion, (@=?))
23+import TestSuite.Util
24+
25+
26+--------------------------------------------------------------------------------
27+tests :: TestTree
28+tests = testGroup "Hakyll.Core.Rules.Tests" $ fromAssertions "runRules"
29+ [case01]
30+
31+
32+--------------------------------------------------------------------------------
33+case01 :: Assertion
34+case01 = do
35+ ioref <- newIORef False
36+ store <- newTestStore
37+ provider <- newTestProvider store
38+ ruleSet <- runRules (rules01 ioref) provider
39+ let identifiers = S.fromList $ map fst $ rulesCompilers ruleSet
40+ routes = rulesRoutes ruleSet
41+ checkRoute ex i =
42+ runRoutes routes provider i >>= \(r, _) -> Just ex @=? r
43+
44+ -- 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 @=?)
53+ cleanTestEnv
54+ where
55+ sv g = setVersion (Just g)
56+ 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"
65+ , sv "raw" "russian.md"
66+ , sv "mv1" "russian.md"
67+ , sv "mv2" "russian.md"
68+ ]
69+
70+
71+--------------------------------------------------------------------------------
72+rules01 :: IORef Bool -> Rules ()
73+rules01 ioref = do
74+ -- Compile some posts
75+ match "*.md" $ do
76+ route $ setExtension "html"
77+ compile copyFileCompiler
78+
79+ -- Yeah. I don't know how else to test this stuff?
80+ preprocess $ writeIORef ioref True
81+
82+ -- Compile them, raw
83+ match "*.md" $ version "raw" $ do
84+ route idRoute
85+ compile getResourceString
86+
87+ version "metadataMatch" $
88+ matchMetadata "*.md" (\md -> lookupString "subblog" md == Just "food") $ do
89+ route $ customRoute $ \id' -> "food" </> toFilePath id'
90+ compile getResourceString
91+
92+ -- Regression test
93+ version "nav" $ match (fromList ["example.md"]) $ do
94+ route idRoute
95+ compile copyFileCompiler
96+
97+ -- Another edge case: different versions in one match
98+ match "*.md" $ do
99+ version "mv1" $ do
100+ route $ setExtension "mv1"
101+ compile getResourceString
102+ version "mv2" $ do
103+ route $ setExtension "mv2"
104+ compile getResourceString
tests/Hakyll/Core/Runtime/Tests.hsnew file+96
@@ -0,0 +1,96 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.Runtime.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import qualified Data.ByteString as B
10+import System.FilePath ((</>))
11+import Test.Tasty (TestTree, testGroup)
12+import Test.Tasty.HUnit (Assertion, (@?=))
13+
14+
15+--------------------------------------------------------------------------------
16+import Hakyll
17+import qualified Hakyll.Core.Logger as Logger
18+import Hakyll.Core.Runtime
19+import TestSuite.Util
20+
21+
22+--------------------------------------------------------------------------------
23+tests :: TestTree
24+tests = testGroup "Hakyll.Core.Runtime.Tests" $
25+ fromAssertions "run" [case01, case02]
26+
27+
28+--------------------------------------------------------------------------------
29+case01 :: Assertion
30+case01 = do
31+ logger <- Logger.new Logger.Error
32+ _ <- run testConfiguration logger $ do
33+ match "images/*" $ do
34+ route idRoute
35+ compile copyFileCompiler
36+
37+ match "*.md" $ do
38+ route $ setExtension "html"
39+ compile $ do
40+ getResourceBody
41+ >>= 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
52+
53+ create ["bodies.txt"] $ do
54+ route idRoute
55+ compile $ do
56+ items <- loadAllSnapshots "*.md" "raw"
57+ makeItem $ concat $ map itemBody (items :: [Item String])
58+
59+ favicon <- B.readFile $
60+ providerDirectory testConfiguration </> "images/favicon.ico"
61+ favicon' <- B.readFile $
62+ destinationDirectory testConfiguration </> "images/favicon.ico"
63+ favicon @?= favicon'
64+
65+ example <- readFile $
66+ destinationDirectory testConfiguration </> "example.html"
67+ lines example @?= ["<p>This is an example.</p>"]
68+
69+ bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
70+ head (lines bodies) @?= "This is an example."
71+
72+ partial <- readFile $ providerDirectory testConfiguration </> "partial.html.out"
73+ partial' <- readFile $ destinationDirectory testConfiguration </> "partial.html.out"
74+ partial @?= partial'
75+
76+ cleanTestEnv
77+
78+
79+--------------------------------------------------------------------------------
80+case02 :: Assertion
81+case02 = do
82+ logger <- Logger.new Logger.Error
83+ _ <- run testConfiguration logger $ do
84+ match "images/favicon.ico" $ do
85+ route $ gsubRoute "images/" (const "")
86+ compile $ makeItem ("Test" :: String)
87+
88+ match "images/**" $ do
89+ route idRoute
90+ compile copyFileCompiler
91+
92+ favicon <- readFile $
93+ destinationDirectory testConfiguration </> "favicon.ico"
94+ favicon @?= "Test"
95+
96+ cleanTestEnv
tests/Hakyll/Core/Store/Tests.hsnew file+83
@@ -0,0 +1,83 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.Store.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
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)
16+
17+
18+--------------------------------------------------------------------------------
19+import qualified Hakyll.Core.Store as Store
20+import TestSuite.Util
21+
22+
23+--------------------------------------------------------------------------------
24+tests :: TestTree
25+tests = testGroup "Hakyll.Core.Store.Tests"
26+ [ testProperty "simple get . set" simpleSetGet
27+ , testProperty "persistent get . set" persistentSetGet
28+ , testCase "WrongType get . set" wrongType
29+ , testCase "isMembertest . set" isMembertest
30+ ]
31+
32+
33+--------------------------------------------------------------------------------
34+simpleSetGet :: Q.Property
35+simpleSetGet = Q.monadicIO $ do
36+ key <- Q.pick Q.arbitrary
37+ value <- Q.pick Q.arbitrary
38+ store <- Q.run newTestStore
39+ Q.run $ Store.set store key (value :: String)
40+ value' <- Q.run $ Store.get store key
41+ Q.assert $ Store.Found value == value'
42+ Q.run cleanTestEnv
43+
44+
45+--------------------------------------------------------------------------------
46+persistentSetGet :: Q.Property
47+persistentSetGet = Q.monadicIO $ do
48+ key <- Q.pick Q.arbitrary
49+ value <- Q.pick Q.arbitrary
50+ store1 <- Q.run newTestStore
51+ Q.run $ Store.set store1 key (value :: String)
52+ -- Now Create another store from the same dir to test persistence
53+ store2 <- Q.run newTestStore
54+ value' <- Q.run $ Store.get store2 key
55+ Q.assert $ Store.Found value == value'
56+ Q.run cleanTestEnv
57+
58+
59+--------------------------------------------------------------------------------
60+wrongType :: H.Assertion
61+wrongType = do
62+ store <- newTestStore
63+ -- Store a string and try to fetch an int
64+ Store.set store ["foo", "bar"] ("qux" :: String)
65+ 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"
71+ cleanTestEnv
72+
73+
74+--------------------------------------------------------------------------------
75+isMembertest :: H.Assertion
76+isMembertest = do
77+ store <- newTestStore
78+ Store.set store ["foo", "bar"] ("qux" :: String)
79+ good <- Store.isMember store ["foo", "bar"]
80+ bad <- Store.isMember store ["foo", "baz"]
81+ True H.@=? good
82+ False H.@=? bad
83+ cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hsnew file+66
@@ -0,0 +1,66 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Core.UnixFilter.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit (testCase)
11+import qualified Test.Tasty.HUnit as H
12+
13+
14+--------------------------------------------------------------------------------
15+import Hakyll.Core.Compiler
16+import Hakyll.Core.Identifier
17+import Hakyll.Core.Item
18+import Hakyll.Core.UnixFilter
19+import TestSuite.Util
20+
21+
22+--------------------------------------------------------------------------------
23+tests :: TestTree
24+tests = testGroup "Hakyll.Core.UnixFilter.Tests"
25+ [ testCase "unixFilter rev" unixFilterRev
26+ , testCase "unixFilter false" unixFilterFalse
27+ , testCase "unixFilter error" unixFilterError
28+ ]
29+
30+testMarkdown :: Identifier
31+testMarkdown = "russian.md"
32+
33+--------------------------------------------------------------------------------
34+unixFilterRev :: H.Assertion
35+unixFilterRev = do
36+ store <- newTestStore
37+ 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)
41+ cleanTestEnv
42+ where
43+ compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
44+ rev = map reverse . lines
45+
46+
47+--------------------------------------------------------------------------------
48+unixFilterFalse :: H.Assertion
49+unixFilterFalse = do
50+ store <- newTestStore
51+ provider <- newTestProvider store
52+ testCompilerError store provider testMarkdown compiler "exit code"
53+ cleanTestEnv
54+ where
55+ 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.hsnew file+45
@@ -0,0 +1,45 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Util.String.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.Core.Util.String
14+import TestSuite.Util
15+
16+
17+--------------------------------------------------------------------------------
18+tests :: TestTree
19+tests = testGroup "Hakyll.Core.Util.String.Tests" $ concat
20+ [ fromAssertions "trim"
21+ [ "foo" @=? trim " foo\n\t "
22+ ]
23+
24+ , fromAssertions "replaceAll"
25+ [ "32 & 131" @=? replaceAll "0x[0-9]+" (show . readInt) "0x20 & 0x83"
26+ ]
27+
28+ , fromAssertions "splitAll"
29+ [ ["λ", "∀x.x", "hi"] @=? splitAll ", *" "λ, ∀x.x, hi"
30+ ]
31+
32+ , fromAssertions "needlePrefix"
33+ [ Just "ab" @=? needlePrefix "cd" "abcde"
34+ , Just "xx" @=? needlePrefix "ab" "xxab"
35+ , Nothing @=? needlePrefix "a" "xx"
36+ , Just "x" @=? needlePrefix "ab" "xabxab"
37+ , Just "" @=? needlePrefix "ab" "abc"
38+ , Just "" @=? needlePrefix "ab" "abab"
39+ , Nothing @=? needlePrefix "" ""
40+ ]
41+ ]
42+
43+ where
44+ readInt :: String -> Int
45+ readInt = read
tests/Hakyll/Web/CompressCss/Tests.hsnew file+64
@@ -0,0 +1,64 @@
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.hsnew file+38
@@ -0,0 +1,38 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Web.Html.RelativizeUrls.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit ((@=?))
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Web.Html.RelativizeUrls
15+import TestSuite.Util
16+
17+
18+--------------------------------------------------------------------------------
19+tests :: TestTree
20+tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
21+ fromAssertions "relativizeUrls"
22+ [ "<a href=\"../foo\">bar</a>" @=?
23+ relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
24+ , "<img src=\"../../images/lolcat.png\" />" @=?
25+ relativizeUrlsWith "../.." "<img src=\"/images/lolcat.png\" />"
26+ , "<video poster=\"../../images/lolcat.png\"></video>" @=?
27+ relativizeUrlsWith "../.."
28+ "<video poster=\"/images/lolcat.png\"></video>"
29+ , "<a href=\"http://haskell.org\">Haskell</a>" @=?
30+ relativizeUrlsWith "../.."
31+ "<a href=\"http://haskell.org\">Haskell</a>"
32+ , "<a href=\"http://haskell.org\">Haskell</a>" @=?
33+ relativizeUrlsWith "../.."
34+ "<a href=\"http://haskell.org\">Haskell</a>"
35+ , "<script src=\"//ajax.googleapis.com/jquery.min.js\"></script>" @=?
36+ relativizeUrlsWith "../.."
37+ "<script src=\"//ajax.googleapis.com/jquery.min.js\"></script>"
38+ ]
tests/Hakyll/Web/Html/Tests.hsnew file+84
@@ -0,0 +1,84 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Web.Html.Tests
3+ ( tests
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
8+import Data.Char (toUpper)
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit ((@=?))
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Web.Html
15+import TestSuite.Util
16+
17+
18+--------------------------------------------------------------------------------
19+tests :: TestTree
20+tests = testGroup "Hakyll.Web.Html.Tests" $ concat
21+ [ fromAssertions "demoteHeaders"
22+ [ "<h2>A h1 title</h2>" @=?
23+ demoteHeaders "<h1>A h1 title</h1>"
24+ ]
25+
26+ , fromAssertions "withUrls"
27+ [ "<a href=\"FOO\">bar</a>" @=?
28+ withUrls (map toUpper) "<a href=\"foo\">bar</a>"
29+ , "<img src=\"OH BAR\" />" @=?
30+ withUrls (map toUpper) "<img src=\"oh bar\" />"
31+
32+ -- Test escaping
33+ , "<script>\"sup\"</script>" @=?
34+ withUrls id "<script>\"sup\"</script>"
35+ , "<code>&lt;stdio&gt;</code>" @=?
36+ withUrls id "<code>&lt;stdio&gt;</code>"
37+ , "<style>body > p { line-height: 1.3 }</style>" @=?
38+ withUrls id "<style>body > p { line-height: 1.3 }</style>"
39+
40+ -- Test minimizing elements
41+ , "<meta bar=\"foo\" />" @=?
42+ withUrls id "<meta bar=\"foo\" />"
43+ ]
44+
45+ , fromAssertions "toUrl"
46+ [ "/foo/bar.html" @=? toUrl "foo/bar.html"
47+ , "/" @=? toUrl "/"
48+ , "/funny-pics.html" @=? toUrl "/funny-pics.html"
49+ , "/funny%20pics.html" @=? toUrl "funny pics.html"
50+ -- Test various reserved characters (RFC 3986, section 2.2)
51+ , "/%21%2A%27%28%29%3B%3A%40%26.html" @=? toUrl "/!*'();:@&.html"
52+ , "/%3D%2B%24%2C/%3F%23%5B%5D.html" @=? toUrl "=+$,/?#[].html"
53+ -- Test various characters that are nor reserved, nor unreserved.
54+ , "/%E3%81%82%F0%9D%90%87%E2%88%80" @=? toUrl "\12354\119815\8704"
55+ ]
56+
57+ , fromAssertions "toSiteRoot"
58+ [ ".." @=? toSiteRoot "/foo/bar.html"
59+ , "." @=? toSiteRoot "index.html"
60+ , "." @=? toSiteRoot "/index.html"
61+ , "../.." @=? toSiteRoot "foo/bar/qux"
62+ , ".." @=? toSiteRoot "./foo/bar.html"
63+ , ".." @=? toSiteRoot "/foo/./bar.html"
64+ ]
65+
66+ , 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"
72+ ]
73+
74+ , fromAssertions "stripTags"
75+ [ "foo" @=? stripTags "<p>foo</p>"
76+ , "foo bar" @=? stripTags "<p>foo</p> bar"
77+ , "foo" @=? stripTags "<p>foo</p"
78+ ]
79+
80+ , fromAssertions "escapeHtml"
81+ [ "Me &amp; Dean" @=? escapeHtml "Me & Dean"
82+ , "&lt;img&gt;" @=? escapeHtml "<img>"
83+ ]
84+ ]
tests/Hakyll/Web/Page/Metadata/Tests.hsdeleted−54
@@ -1,54 +0,0 @@
1−module Hakyll.Web.Page.Metadata.Tests
2− ( tests
3− ) where
4−
5−import Test.Framework
6−import Test.HUnit hiding (Test)
7−
8−import qualified Data.Map as M
9−import Data.Monoid (mempty)
10−import Data.Char (toLower)
11−
12−import Hakyll.Web.Page
13−import Hakyll.Web.Page.Metadata
14−import TestSuite.Util
15−
16−tests :: [Test]
17−tests = concat $
18− [ fromAssertions "getField"
19− [ "bar" @=? getField "foo" (Page (M.singleton "foo" "bar") "body\n")
20− , "" @=? getField "foo" (Page M.empty "body")
21− ]
22−
23− , fromAssertions "getFieldMaybe"
24− [ Just "bar" @=? getFieldMaybe "foo" (Page (M.singleton "foo" "bar") "")
25− , Nothing @=? getFieldMaybe "foo" (Page M.empty "body")
26− ]
27−
28− , fromAssertions "setField"
29− [ (Page (M.singleton "bar" "foo") "") @=? setField "bar" "foo" mempty
30− , (Page (M.singleton "bar" "foo") "") @=?
31− setField "bar" "foo" (Page (M.singleton "bar" "qux") "")
32− ]
33−
34− , fromAssertions "trySetField"
35− [ (Page (M.singleton "bar" "foo") "") @=? trySetField "bar" "foo" mempty
36− , (Page (M.singleton "bar" "qux") "") @=?
37− trySetField "bar" "foo" (Page (M.singleton "bar" "qux") "")
38− ]
39−
40− , fromAssertions "setFieldA"
41− [ (Page (M.singleton "bar" "foo") "") @=?
42− setFieldA "bar" (map toLower) (mempty, "FOO")
43− ]
44−
45− , fromAssertions "copyBodyToField"
46− [ (Page (M.singleton "bar" "foo") "foo") @=?
47− copyBodyToField "bar" (Page M.empty "foo")
48− ]
49−
50− , fromAssertions "copyBodyFromField"
51− [ (Page (M.singleton "bar" "foo") "foo") @=?
52− copyBodyFromField "bar" (Page (M.singleton "bar" "foo") "qux")
53− ]
54− ]
tests/Hakyll/Web/Page/Tests.hsdeleted−32
@@ -1,32 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Hakyll.Web.Page.Tests
3− ( tests
4− ) where
5−
6−import Test.Framework
7−import Test.HUnit hiding (Test)
8−
9−import qualified Data.Map as M
10−
11−import Hakyll.Web.Page
12−import Hakyll.Web.Page.Read
13−import TestSuite.Util
14−
15−tests :: [Test]
16−tests = fromAssertions "readPage"
17− [ Page (M.singleton "foo" "bar") "body\n" @=? readPage
18− "--- \n\
19− \foo: bar \n\
20− \--- \n\
21− \body"
22−
23− , Page M.empty "line one\nlijn twee\n" @=? readPage
24− "line one\n\
25− \lijn twee"
26−
27− , Page (M.fromList [("field1", "unos"), ("veld02", "deux")]) "" @=? readPage
28− "---\n\
29− \veld02: deux\n\
30− \field1: unos\n\
31− \---"
32− ]
tests/Hakyll/Web/Pandoc/FileType/Tests.hsnew file+26
@@ -0,0 +1,26 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Web.Pandoc.FileType.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit ((@=?))
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Web.Pandoc.FileType
15+import TestSuite.Util
16+
17+
18+--------------------------------------------------------------------------------
19+tests :: TestTree
20+tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
21+ fromAssertions "fileType"
22+ [ Markdown @=? fileType "index.md"
23+ , Rst @=? fileType "about/foo.rst"
24+ , LiterateHaskell Markdown @=? fileType "posts/bananas.lhs"
25+ , LiterateHaskell LaTeX @=? fileType "posts/bananas.tex.lhs"
26+ ]
tests/Hakyll/Web/RelativizeUrls/Tests.hsdeleted−20
@@ -1,20 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Hakyll.Web.RelativizeUrls.Tests
3− ( tests
4− ) where
5−
6−import Test.Framework
7−import Test.HUnit hiding (Test)
8−
9−import Hakyll.Web.RelativizeUrls
10−import TestSuite.Util
11−
12−tests :: [Test]
13−tests = fromAssertions "relativizeUrls"
14− [ "<a href=\"../foo\">bar</a>" @=?
15− relativizeUrls ".." "<a href=\"/foo\">bar</a>"
16− , "<img src=\"../../images/lolcat.png\"></img>" @=?
17− relativizeUrls "../.." "<img src=\"/images/lolcat.png\" />"
18− , "<a href=\"http://haskell.org\">Haskell</a>" @=?
19− relativizeUrls "../.." "<a href=\"http://haskell.org\">Haskell</a>"
20− ]
tests/Hakyll/Web/Template/Context/Tests.hsnew file+95
@@ -0,0 +1,95 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Web.Template.Context.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Core.Compiler
15+import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
17+import Hakyll.Core.Provider
18+import Hakyll.Core.Store (Store)
19+import Hakyll.Web.Template.Context
20+import Hakyll.Web.Template.Internal
21+import TestSuite.Util
22+
23+
24+--------------------------------------------------------------------------------
25+tests :: TestTree
26+tests = testGroup "Hakyll.Web.Template.Context.Tests"
27+ [ testCase "testDateField" testDateField
28+ , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
29+ ]
30+
31+
32+--------------------------------------------------------------------------------
33+testDateField :: Assertion
34+testDateField = do
35+ store <- newTestStore
36+ provider <- newTestProvider store
37+
38+ date1 <- testContextDone store provider "example.md" "date" $
39+ dateField "date" "%B %e, %Y"
40+ date1 @=? "October 22, 2012"
41+
42+ date2 <- testContextDone store provider
43+ "posts/2010-08-26-birthday.md" "date" $
44+ dateField "date" "%B %e, %Y"
45+ date2 @=? "August 26, 2010"
46+
47+ date3 <- testContextDone store provider
48+ "posts/2018-09-26.md" "date" $
49+ dateField "date" "%B %e, %Y"
50+ date3 @=? "September 26, 2018"
51+
52+ date4 <- testContextDone store provider
53+ "posts/2019/05/10/tomorrow.md" "date" $
54+ dateField "date" "%B %e, %Y"
55+ date4 @=? "May 10, 2019"
56+ cleanTestEnv
57+
58+
59+--------------------------------------------------------------------------------
60+testContextDone :: Store -> Provider -> Identifier -> String
61+ -> Context String -> IO String
62+testContextDone store provider identifier key context =
63+ testCompilerDone store provider identifier $ do
64+ item <- getResourceBody
65+ cf <- unContext context key [] item
66+ case cf of
67+ StringField str -> return str
68+ _ -> error $
69+ "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'
tests/Hakyll/Web/Template/Tests.hs+149−37
@@ -1,44 +1,156 @@
1+--------------------------------------------------------------------------------
12{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
24module Hakyll.Web.Template.Tests
35 ( tests
46 ) where
57
6−import Test.Framework
7−import Test.HUnit hiding (Test)
8−
9−import qualified Data.Map as M
10−
11−import Hakyll.Web.Page
12−import Hakyll.Web.Template
13−import Hakyll.Web.Template.Read
14−import TestSuite.Util
15−
16−tests :: [Test]
17−tests = fromAssertions "applyTemplate"
18− -- Hakyll templates
19− [ applyTemplateAssertion readTemplate
20− "bar" "$foo$" [("foo", "bar")]
21−
22− , applyTemplateAssertion readTemplate
23− "$ barqux" "$$ $foo$$bar$" [("foo", "bar"), ("bar", "qux")]
24−
25− -- Hamlet templates
26− , applyTemplateAssertion readHamletTemplate
27− "<head><title>notice</title></head><body>A paragraph</body>"
28− "<head\n\
29− \ <title>#{title}\n\
30− \<body\n\
31− \ A paragraph\n"
32− [("title", "notice")]
8+
9+--------------------------------------------------------------------------------
10+import Test.Tasty (TestTree, testGroup)
11+import Test.Tasty.HUnit (Assertion, assertBool, testCase,
12+ (@=?), (@?=))
13+
14+import Data.Either (isLeft)
15+
16+--------------------------------------------------------------------------------
17+import Hakyll.Core.Compiler
18+import Hakyll.Core.Identifier
19+import Hakyll.Core.Item
20+import Hakyll.Core.Provider
21+import Hakyll.Web.Template
22+import Hakyll.Web.Template.Context
23+import Hakyll.Web.Template.Internal
24+import Hakyll.Web.Template.List
25+import TestSuite.Util
26+
27+
28+--------------------------------------------------------------------------------
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]
3389 ]
90+ where
91+ parse = parseTemplateElemsFile ""
92+
93+
94+--------------------------------------------------------------------------------
95+test :: (Identifier, Identifier, Identifier) -> Assertion
96+test (outf, tplf, itemf) = do
97+ store <- newTestStore
98+ provider <- newTestProvider store
99+
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
106+
107+ out @=? itemBody item
108+ cleanTestEnv
109+
110+
111+--------------------------------------------------------------------------------
112+testContext :: Context String
113+testContext = mconcat
114+ [ defaultContext
115+ , listField "authors" (bodyField "name") $ do
116+ n1 <- makeItem "Jan"
117+ n2 <- makeItem "Piet"
118+ return [n1, n2]
119+ , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
120+ ]
121+
122+
123+--------------------------------------------------------------------------------
124+testApplyJoinTemplateList :: Assertion
125+testApplyJoinTemplateList = do
126+ store <- newTestStore
127+ provider <- newTestProvider store
128+ tpl <- testCompilerDone store provider "tpl" $
129+ compileTemplateItem (Item "tpl" "<b>$body$</b>")
130+ str <- testCompilerDone store provider "item3" $
131+ applyJoinTemplateList ", " tpl defaultContext [i1, i2]
132+
133+ str @?= "<b>Hello</b>, <b>World</b>"
134+ cleanTestEnv
135+ where
136+ i1 = Item "item1" "Hello"
137+ 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"
34156
35−-- | Utility function to create quick template tests
36−--
37−applyTemplateAssertion :: (String -> Template) -- ^ Template parser
38− -> String -- ^ Expected
39− -> String -- ^ Template
40− -> [(String, String)] -- ^ Page
41− -> Assertion -- ^ Resulting assertion
42−applyTemplateAssertion parser expected template page =
43− expected @=? pageBody (applyTemplate (parser template)
44− (fromMap $ M.fromList page))
tests/Hakyll/Web/Util/Url/Tests.hsdeleted−25
@@ -1,25 +0,0 @@
1−module Hakyll.Web.Util.Url.Tests
2− ( tests
3− ) where
4−
5−import Test.Framework
6−import Test.HUnit hiding (Test)
7−
8−import Hakyll.Web.Util.Url
9−import TestSuite.Util
10−
11−tests :: [Test]
12−tests = concat
13− [ fromAssertions "toUrl"
14− [ "/foo/bar.html" @=? toUrl "foo/bar.html"
15− , "/" @=? toUrl "/"
16− , "/funny-pics.html" @=? toUrl "/funny-pics.html"
17− ]
18−
19− , fromAssertions "toSiteRoot"
20− [ ".." @=? toSiteRoot "/foo/bar.html"
21− , "." @=? toSiteRoot "index.html"
22− , "." @=? toSiteRoot "/index.html"
23− , "../.." @=? toSiteRoot "foo/bar/qux"
24− ]
25− ]
tests/TestSuite.hs+48−24
@@ -1,32 +1,56 @@
1−module Main where
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+module Main
4+ ( main
5+ ) where
26
3−import Test.Framework (defaultMain, testGroup)
47
5−import qualified Hakyll.Core.DependencyAnalyzer.Tests
8+--------------------------------------------------------------------------------
9+import Test.Tasty (defaultMain, testGroup)
10+
11+
12+--------------------------------------------------------------------------------
13+import qualified Hakyll.Core.Dependencies.Tests
614import qualified Hakyll.Core.Identifier.Tests
15+import qualified Hakyll.Core.Provider.Metadata.Tests
16+import qualified Hakyll.Core.Provider.Tests
717import qualified Hakyll.Core.Routes.Tests
8−import qualified Hakyll.Web.Page.Tests
9−import qualified Hakyll.Web.Page.Metadata.Tests
10−import qualified Hakyll.Web.RelativizeUrls.Tests
18+import qualified Hakyll.Core.Rules.Tests
19+import qualified Hakyll.Core.Runtime.Tests
20+import qualified Hakyll.Core.Store.Tests
21+import qualified Hakyll.Core.UnixFilter.Tests
22+import qualified Hakyll.Core.Util.String.Tests
23+import qualified Hakyll.Web.CompressCss.Tests
24+import qualified Hakyll.Web.Html.RelativizeUrls.Tests
25+import qualified Hakyll.Web.Html.Tests
26+#ifdef USE_PANDOC
27+import qualified Hakyll.Web.Pandoc.FileType.Tests
28+#endif
29+import qualified Hakyll.Web.Template.Context.Tests
1130import qualified Hakyll.Web.Template.Tests
12−import qualified Hakyll.Web.Util.Url.Tests
31+import qualified Hakyll.Web.Tags.Tests
32+
1333
34+--------------------------------------------------------------------------------
1435main :: IO ()
15−main = defaultMain
16− [ testGroup "Hakyll.Core.DependencyAnalyzer.Tests"
17− Hakyll.Core.DependencyAnalyzer.Tests.tests
18− , testGroup "Hakyll.Core.Identifier.Tests"
19− Hakyll.Core.Identifier.Tests.tests
20− , testGroup "Hakyll.Core.Routes.Tests"
21− Hakyll.Core.Routes.Tests.tests
22− , testGroup "Hakyll.Web.Page.Tests"
23− Hakyll.Web.Page.Tests.tests
24− , testGroup "Hakyll.Web.Page.Metadata.Tests"
25− Hakyll.Web.Page.Metadata.Tests.tests
26− , testGroup "Hakyll.Web.RelativizeUrls.Tests"
27− Hakyll.Web.RelativizeUrls.Tests.tests
28− , testGroup "Hakyll.Web.Template.Tests"
29− Hakyll.Web.Template.Tests.tests
30− , testGroup "Hakyll.Web.Util.Url.Tests"
31− Hakyll.Web.Util.Url.Tests.tests
36+main = defaultMain $ testGroup "Hakyll"
37+ [ Hakyll.Core.Dependencies.Tests.tests
38+ , Hakyll.Core.Identifier.Tests.tests
39+ , Hakyll.Core.Provider.Metadata.Tests.tests
40+ , Hakyll.Core.Provider.Tests.tests
41+ , Hakyll.Core.Routes.Tests.tests
42+ , Hakyll.Core.Rules.Tests.tests
43+ , Hakyll.Core.Runtime.Tests.tests
44+ , Hakyll.Core.Store.Tests.tests
45+ , Hakyll.Core.UnixFilter.Tests.tests
46+ , Hakyll.Core.Util.String.Tests.tests
47+ , Hakyll.Web.CompressCss.Tests.tests
48+ , Hakyll.Web.Html.RelativizeUrls.Tests.tests
49+ , Hakyll.Web.Html.Tests.tests
50+#ifdef USE_PANDOC
51+ , Hakyll.Web.Pandoc.FileType.Tests.tests
52+#endif
53+ , Hakyll.Web.Tags.Tests.tests
54+ , Hakyll.Web.Template.Context.Tests.tests
55+ , Hakyll.Web.Template.Tests.tests
3256 ]
tests/TestSuite/Util.hs+117−8
@@ -1,16 +1,125 @@
1+--------------------------------------------------------------------------------
12-- | Test utilities
2−--
33module TestSuite.Util
44 ( fromAssertions
5+ , newTestStore
6+ , newTestProvider
7+ , testCompiler
8+ , testCompilerDone
9+ , testCompilerError
10+ , testConfiguration
11+ , cleanTestEnv
12+ , renderParagraphs
513 ) where
614
7−import Test.Framework
8−import Test.Framework.Providers.HUnit
9−import Test.HUnit hiding (Test)
1015
16+--------------------------------------------------------------------------------
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)
23+
24+
25+--------------------------------------------------------------------------------
26+import Hakyll.Core.Compiler.Internal
27+import Hakyll.Core.Configuration
28+import Hakyll.Core.Identifier
29+import qualified Hakyll.Core.Logger as Logger
30+import Hakyll.Core.Provider
31+import Hakyll.Core.Store (Store)
32+import qualified Hakyll.Core.Store as Store
33+import Hakyll.Core.Util.File
34+import Hakyll.Core.Item
35+
36+
37+--------------------------------------------------------------------------------
1138fromAssertions :: String -- ^ Name
1239 -> [Assertion] -- ^ Cases
13− -> [Test] -- ^ Result tests
14−fromAssertions name = zipWith testCase names
15− where
16− names = map (\n -> name ++ " [" ++ show n ++ "]") [1 :: Int ..]
40+ -> [TestTree] -- ^ Result tests
41+fromAssertions name =
42+ zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
43+
44+
45+--------------------------------------------------------------------------------
46+newTestStore :: IO Store
47+newTestStore = Store.new True $ storeDirectory testConfiguration
48+
49+
50+--------------------------------------------------------------------------------
51+newTestProvider :: Store -> IO Provider
52+newTestProvider store = newProvider store (const $ return False) $
53+ providerDirectory testConfiguration
54+
55+
56+--------------------------------------------------------------------------------
57+testCompiler :: Store -> Provider -> Identifier -> Compiler a
58+ -> IO (CompilerResult a)
59+testCompiler store provider underlying compiler = do
60+ logger <- Logger.new Logger.Error
61+ let read' = CompilerRead
62+ { compilerConfig = testConfiguration
63+ , compilerUnderlying = underlying
64+ , compilerProvider = provider
65+ , compilerUniverse = S.empty
66+ , compilerRoutes = mempty
67+ , compilerStore = store
68+ , compilerLogger = logger
69+ }
70+
71+ result <- runCompiler compiler read'
72+ Logger.flush logger
73+ return result
74+
75+
76+--------------------------------------------------------------------------------
77+testCompilerDone :: Store -> Provider -> Identifier -> Compiler a -> IO a
78+testCompilerDone store provider underlying compiler = do
79+ result <- testCompiler store provider underlying compiler
80+ case result of
81+ CompilerDone x _ -> return x
82+ CompilerError e -> fail $
83+ "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
84+ " threw: " ++ intercalate "; " (compilerErrorMessages e)
85+ CompilerRequire i _ -> fail $
86+ "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
87+ " requires: " ++ show i
88+ CompilerSnapshot _ _ -> fail
89+ "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
90+
91+testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()
92+testCompilerError store provider underlying compiler expectedMessage = do
93+ result <- testCompiler store provider underlying compiler
94+ case result of
95+ CompilerError e ->
96+ any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?
97+ "Expecting '" ++ expectedMessage ++ "' error"
98+ _ -> assertFailure "Expecting CompilerError"
99+
100+--------------------------------------------------------------------------------
101+testConfiguration :: Configuration
102+testConfiguration = defaultConfiguration
103+ { destinationDirectory = "_testsite"
104+ , storeDirectory = "_teststore"
105+ , tmpDirectory = "_testtmp"
106+ , providerDirectory = "tests/data"
107+ }
108+
109+
110+--------------------------------------------------------------------------------
111+cleanTestEnv :: IO ()
112+cleanTestEnv = do
113+ removeDirectory $ destinationDirectory testConfiguration
114+ removeDirectory $ storeDirectory testConfiguration
115+ removeDirectory $ tmpDirectory testConfiguration
116+
117+
118+--------------------------------------------------------------------------------
119+-- | like 'Hakyll.Web.Pandoc.renderPandoc'
120+-- | but allowing to test without the @usePandoc@ flag
121+renderParagraphs :: Item String -> Compiler (Item String)
122+renderParagraphs = withItemBody (return
123+ . intercalate "\n" -- no trailing line
124+ . map (("<p>"++) . (++"</p>"))
125+ . lines)
tests/data/embed.htmlnew file+1
@@ -0,0 +1 @@
1+<p>$body$</p>
tests/data/example.mdnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: An example
3+---
4+
5+This is an example.
tests/data/example.md.metadatanew file+5
@@ -0,0 +1,5 @@
1+external: External data
2+date: 2012-10-22 14:35:24
3+subblog: food
4+intfield: 42
5+numfield: 3.14
tests/data/images/favicon.iconew file

Binary file not shown.

tests/data/just-meta.htmlnew file+7
@@ -0,0 +1,7 @@
1+<pre>
2+external: {$external$}
3+date: {$date$}
4+subblog: {$subblog$}
5+intfield: {$intfield$}
6+numfield: {$numfield$}
7+</pre>
tests/data/just-meta.html.outnew file+7
@@ -0,0 +1,7 @@
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.htmlnew file+3
@@ -0,0 +1,3 @@
1+<p>This is an included partial.</p>
2+
3+$body$
tests/data/partial.htmlnew file+3
@@ -0,0 +1,3 @@
1+<p>This is a file that includes a partial.</p>
2+
3+$partial("partial-helper.html")$
tests/data/partial.html.outnew file+7
@@ -0,0 +1,7 @@
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/2010-08-26-birthday.mdnew file+1
@@ -0,0 +1 @@
1+It's my birthday today.
tests/data/posts/2018-09-26.mdnew file+1
@@ -0,0 +1 @@
1+Something happened today.
tests/data/posts/2019/05/10/tomorrow.mdnew file+1
@@ -0,0 +1 @@
1+This day hasn't happened yet (as of writing this).
tests/data/russian.mdnew file+14
@@ -0,0 +1,14 @@
1+Статья 18
2+
3+Каждый человек имеет право на свободу мысли, совести и религии; это
4+право включает свободу менять свою религию или убеждения и свободу
5+исповедовать свою религию или убеждения как единолично, так и сообща с
6+другими, публичным или частным порядком в учении, богослужении и
7+выполнении религиозных и ритуальных обрядов.
8+
9+Статья 19
10+
11+Каждый человек имеет право на свободу убеждений и на свободное выражение
12+их; это право включает свободу беспрепятственно придерживаться своих
13+убеждений и свободу искать, получать и распространять информацию и идеи
14+любыми средствами и независимо от государственных границ.
tests/data/strip.htmlnew file+34
@@ -0,0 +1,34 @@
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.outnew file+18
@@ -0,0 +1,18 @@
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.htmlnew file+30
@@ -0,0 +1,30 @@
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)$$name$$sep$, $endfor$
28+
29+ $body$
30+</div>
tests/data/template.html.outnew file+28
@@ -0,0 +1,28 @@
1+<div>
2+ I'm so rich I have $3.
3+
4+ oof
5+ foo
6+
7+
8+ I have body
9+
10+
11+
12+
13+
14+ should be printed
15+
16+
17+ <ul>
18+
19+ <li>Jan</li>
20+
21+ <li>Piet</li>
22+
23+ </ul>
24+
25+ Jan, Piet
26+
27+ <p>This is an example.</p>
28+</div>
examples/hakyll/about.markdown → web/about.markdownrenamed+14−8
@@ -2,13 +2,13 @@
22title: About
33---
44
5−## Code
5+# Code
66
77The code for Hakyll is freely available on
88[github](http://github.com/jaspervdj/Hakyll/). Patches and suggestions are
99always very welcome.
1010
11−## Inspiration
11+# Inspiration
1212
1313Hakyll is not the only static site generator out there. It was inspired by the
1414following awesome projects:
@@ -17,20 +17,26 @@ following awesome projects:
1717- [nanoc](http://nanoc.stoneship.org/)
1818- [Jekyll](http://jekyllrb.com/)
1919
20−## License
20+# License
2121
2222Hakyll is available under a BSD license. Note, however, that pandoc is
2323released under a GPL license. Since you'll probably use Hakyll with pandoc,
2424you will have to license your code under a GPL-compatible license.
2525
26−## Authors
26+# Authors
2727
2828Hakyll was originally written by [Jasper Van der Jeugt](http://jaspervdj.be),
2929who still maintains the package. Contributors:
3030
31−- [seschwar](http://github.com/seschwar)
32−- [JD Marble](http://github.com/jdmarble)
33−- [sargon](http://github.com/sargon)
34−- [Paolo Veronelli](http://github.com/paolino)
31+- [Andy Irving](https://github.com/irv)
3532- [Benedict Eastaugh](http://extralogical.net/)
33+- [Florian Hars](https://github.com/fhars)
34+- [JD Marble](https://github.com/jdmarble)
3635- [Nicolas Wu](http://zenzike.com/)
36+- [Paolo Veronelli](https://github.com/paolino)
37+- [sargon](https://github.com/sargon)
38+- [seschwar](https://github.com/seschwar)
39+- [favonia](https://github.com/favonia)
40+- [Robin Windels](https://github.com/rwindelz)
41+- [Miikka Koskinen](http://miikka.me/)
42+- [Simonas Kazlauskas](http://kazlauskas.me/)
examples/hakyll/css/default.css → web/css/default.cssrenamed+45−30
@@ -1,10 +1,10 @@
11html {
2− padding: 0px;
3− margin: 0px;
42 background-color: white;
53 color: black;
64 font-family: sans-serif;
7− line-height: 160%;
5+ line-height: 140%;
6+ margin: 0px;
7+ padding: 0px;
88}
99
1010body {
@@ -13,44 +13,54 @@ body {
1313}
1414
1515div#header {
16+ font-size: 28px;
17+ font-weight: bold;
1618 height: 32px;
17− padding: 20px 0px 20px 60px;
19+ line-height: 32px;
20+ margin: 24px 0px 12px 128px;
21+ text-transform: uppercase;
1822}
1923
2024div#header img {
21− display: inline;
22− vertical-align: middle;
25+ float: left;
26+ margin: 0px 30px 0px 0px;
2327}
2428
25−div#header h1 {
26− padding-left: 10px;
27− display: inline;
28− text-transform: uppercase;
29− vertical-align: middle;
29+div#header a {
30+ text-decoration: none;
3031}
3132
3233div#main {
3334 margin: 0px auto 0px auto;
34− width: 760px;
35+ width: 860px;
3536}
3637
3738div#sidebar {
3839 margin-right: 30px;
3940 width: 160px;
4041 float: left;
41− text-align: right;
42+}
43+
44+div#sidebar h1 {
45+ clear: both;
46+ float: right;
4247}
4348
4449div#sidebar a {
50+ clear: both;
4551 display: block;
46− font-size: 110%;
47− text-decoration: none;
52+ float: right;
4853 margin-bottom: 10px;
54+ text-decoration: none;
4955 text-transform: uppercase;
5056}
5157
58+div#sidebar img {
59+ margin: 0px;
60+}
61+
5262div#content {
53− width: 570px;
63+ width: 670px;
5464 float: right;
5565}
5666
@@ -61,37 +71,42 @@ div#footer {
6171 text-align: center;
6272}
6373
74+p {
75+ padding: 0px;
76+ margin: 10px 0px 10px 0px;
77+}
78+
6479a {
6580 color: black;
6681}
6782
83+h1 {
84+ font-size: 20px;
85+ margin: 12px 0px 12px 0px;
86+}
87+
6888h2 {
69− font-size: 120%;
70− text-transform: uppercase;
89+ font-size: 18px;
90+ margin: 12px 0px 12px 0px;
7191}
7292
7393h3 {
74− font-size: 100%;
94+ font-size: 16px;
95+}
96+
97+h1, h2, h3 {
7598 text-transform: uppercase;
99+ font-weight: bold;
76100}
77101
78102h1 a, h2 a, h3 a {
79103 text-decoration: none;
80104}
81105
82−div.column {
83− width: 50%;
84− float: left;
85−}
86−
87−div.column p {
88− padding-right: 15px;
89−}
90−
91106img {
92− display: block;
93− margin: 10px auto 10px auto;
94107 border: none;
108+ display: block;
109+ margin: 6px auto 30px auto;
95110}
96111
97112ul {
web/css/syntax.cssnew file+61
@@ -0,0 +1,61 @@
1+a.sourceLine { display: inline-block; line-height: 1.25; }
2+a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
3+a.sourceLine:empty { height: 1.2em; }
4+.sourceCode { overflow: visible; }
5+code.sourceCode { white-space: pre; position: relative; }
6+div.sourceCode { margin: 1em 0; }
7+pre.sourceCode { margin: 0; }
8+@media screen {
9+div.sourceCode { overflow: auto; }
10+}
11+@media print {
12+code.sourceCode { white-space: pre-wrap; }
13+a.sourceLine { text-indent: -1em; padding-left: 1em; }
14+}
15+pre.numberSource a.sourceLine
16+ { position: relative; left: -4em; }
17+pre.numberSource a.sourceLine::before
18+ { content: attr(title);
19+ position: relative; left: -1em; text-align: right; vertical-align: baseline;
20+ border: none; pointer-events: all; display: inline-block;
21+ -webkit-touch-callout: none; -webkit-user-select: none;
22+ -khtml-user-select: none; -moz-user-select: none;
23+ -ms-user-select: none; user-select: none;
24+ padding: 0 4px; width: 4em;
25+ color: #aaaaaa;
26+ }
27+pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
28+div.sourceCode
29+ { }
30+@media screen {
31+a.sourceLine::before { text-decoration: underline; }
32+}
33+code span.al { color: #ff0000; font-weight: bold; } /* Alert */
34+code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
35+code span.at { color: #7d9029; } /* Attribute */
36+code span.bn { color: #40a070; } /* BaseN */
37+code span.bu { } /* BuiltIn */
38+code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
39+code span.ch { color: #4070a0; } /* Char */
40+code span.cn { color: #880000; } /* Constant */
41+code span.co { color: #60a0b0; font-style: italic; } /* Comment */
42+code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
43+code span.do { color: #ba2121; font-style: italic; } /* Documentation */
44+code span.dt { color: #902000; } /* DataType */
45+code span.dv { color: #40a070; } /* DecVal */
46+code span.er { color: #ff0000; font-weight: bold; } /* Error */
47+code span.ex { } /* Extension */
48+code span.fl { color: #40a070; } /* Float */
49+code span.fu { color: #06287e; } /* Function */
50+code span.im { } /* Import */
51+code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
52+code span.kw { color: #007020; font-weight: bold; } /* Keyword */
53+code span.op { color: #666666; } /* Operator */
54+code span.ot { color: #007020; } /* Other */
55+code span.pp { color: #bc7a00; } /* Preprocessor */
56+code span.sc { color: #4070a0; } /* SpecialChar */
57+code span.ss { color: #bb6688; } /* SpecialString */
58+code span.st { color: #4070a0; } /* String */
59+code span.va { color: #19177c; } /* Variable */
60+code span.vs { color: #4070a0; } /* VerbatimString */
61+code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
web/examples.markdownnew file+215
@@ -0,0 +1,215 @@
1+---
2+title: Examples
3+---
4+
5+# People using Hakyll
6+
7+A lot of sites running Hakyll also publish the source code. This is a very
8+interesting resource to learn from as well. If you're using Hakyll for your
9+site, and the source code is available, please notify me so I can add you to
10+this list. This list has no particular ordering.
11+
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+
17+## Hakyll 4
18+
19+- <http://jaspervdj.be/>,
20+ [source](https://github.com/jaspervdj/jaspervdj)
21+- <http://www.imagination-land.org/>,
22+ [source](https://github.com/Keruspe/blog/)
23+- <https://bananasandlenses.net/>,
24+ [source](https://github.com/denisshevchenko/bananasandlenses)
25+- <http://blog.clement.delafargue.name/>,
26+ [source](https://github.com/divarvel/blog)
27+- <http://bneijt.nl>,
28+ [source](https://github.com/bneijt/bneijt.nl)
29+- <http://brianshourd.com/>,
30+ [source](https://github.com/brianshourd/brianshourd.com)
31+- <http://variadic.me>,
32+ [source](https://github.com/eakron/variadic.me)
33+- <http://yannesposito.com/>,
34+ [source](https://github.com/yogsototh/yblog)
35+- <http://dannysu.com/>,
36+ [source](https://github.com/dannysu/hakyll-blog)
37+- <http://meta.plasm.us/>,
38+ [source](https://github.com/travisbrown/metaplasm)
39+- <http://workforpizza.com/>,
40+ [source](https://github.com/irneh/workforpizza)
41+- <http://poleiro.info/>,
42+ [source](https://github.com/arthuraa/poleiro)
43+- <http://www.austinrochford.com/>,
44+ [source](https://github.com/AustinRochford/blog)
45+- <http://www.regisfoucault.com/>,
46+ [source](https://github.com/regisfoucault/blog)
47+- <http://www.chaoxuprime.com/>,
48+ [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
49+- <http://www.skybluetrades.net/>,
50+ [source](https://github.com/ian-ross/blog)
51+- <http://chromaticleaves.com/>,
52+ [source](https://github.com/ericrasmussen/chromaticleaves)
53+- <http://www.clever-cloud.com/en/>,
54+ [source](https://github.com/CleverCloud/clever-cloud.com)
55+- <http://blaenkdenum.com/>,
56+ [source](https://github.com/blaenk/blaenk.github.io)
57+- <https://xinitrc.de/>,
58+ [source](https://github.com/xinitrc/xinitrc.de)
59+- <http://nickcharlton.net/>,
60+ [source](https://github.com/nickcharlton/nickcharlton.net)
61+- <http://scr.stunts.hu/>,
62+ [literate source](http://scr.stunts.hu/hakyll.html)
63+- <http://www.eanalytica.com/>,
64+ [literate source](http://www.eanalytica.com/site/)
65+- <http://cse.iitk.ac.in/users/ppk>,
66+ [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)
187+
188+## Hakyll 3.X
189+
190+- <http://ethanschoonover.com/>,
191+ [source](https://github.com/altercation/ethanschoonover.com)
192+- <http://extralogical.net/>,
193+ [source](https://github.com/beastaugh/extralogical.net)
194+- <http://blog.demotera.com/>,
195+ [source](https://bitbucket.org/paul_r/blog-de-demotera)
196+- <http://projects.haskell.org/diagrams/>,
197+ [source](http://patch-tag.com/r/byorgey/diagrams-doc/snapshot/current/content/pretty/web/)
198+- <http://wunki.org>,
199+ [source](https://github.com/wunki/wunki.org)
200+- <http://rs.io>,
201+ [source](https://github.com/robertseaton/rs.io/)
202+- <http://blog.coldflake.com/>,
203+ [source](https://github.com/marcmo/blog.coldflake)
204+- <http://deepak.jois.name>,
205+ [source](https://github.com/deepakjois/website)
206+- <http://orftz.com>,
207+ [source](https://github.com/orftz/orftz.com)
208+- <http://citationneeded.me/>,
209+ [literate source](http://citationneeded.me/hakyll.html)
210+- <http://shakthimaan.com/news.html>,
211+ [source](http://gitorious.org/shakthimaan-blog)
212+- <http://www.alfredodinapoli.com/>,
213+ [source](https://github.com/CharlesStain/alfredodinapoli.com)
214+- <http://michaelxavier.net>,
215+ [source](https://github.com/michaelxavier/michaelxavier.net)
web/images/brochure-files.pngnew file

Binary file not shown.

web/images/hakyll-system-1.pngnew file

Binary file not shown.

web/images/hakyll-system-2.pngnew file

Binary file not shown.

web/images/hakyll-system-3.pngnew file

Binary file not shown.

examples/hakyll/images/lambda.png → web/images/lambda.pngrenamed

Binary file not shown.

web/index.markdownnew file+42
@@ -0,0 +1,42 @@
1+---
2+title: Home
3+---
4+
5+# Overview
6+
7+Static sites are fast, secure, easy to deploy, and manageable using version
8+control.
9+
10+Hakyll is a [Haskell](http://haskell.org) library for generating static sites,
11+mostly aimed at small-to-medium sites and personal blogs. It is written in a
12+very configurable way and uses an [xmonad](http://xmonad.org)-like DSL for
13+configuration.
14+
15+Integration with [pandoc](http://johnmacfarlane.net/pandoc/) gives us markdown
16+and TeX support, including syntax highlighting and other goodies.
17+
18+# The Hakyll System
19+
20+## Write your content in whatever format you prefer
21+
22+![Write your content](/images/hakyll-system-1.png)
23+
24+## Create compilation rules in a Haskell EDSL
25+
26+![Write your rules](/images/hakyll-system-2.png)
27+
28+## Compile it to HTML and upload it!
29+
30+![Compile it](/images/hakyll-system-3.png)
31+
32+# Getting Started
33+
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/
38+
39+- read the [tutorials](/tutorials.html);
40+- mail the [google discussion group](http://groups.google.com/group/hakyll);
41+- ask questions on the IRC channel: `#hakyll` on
42+ [freenode](http://freenode.net/).
web/libraries.markdownnew file+21
@@ -0,0 +1,21 @@
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.
examples/hakyll/reference.markdown → web/reference.markdownrenamed+1−1
@@ -2,7 +2,7 @@
22title: Reference
33---
44
5−## Reference
5+# Reference
66
77We keep a copy of the reference of the latest stable version here. This
88reference is automatically generated by
web/site.hsnew file+117
@@ -0,0 +1,117 @@
1+--------------------------------------------------------------------------------
2+{-# 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)
8+import Hakyll
9+import System.Directory (copyFile)
10+import System.FilePath (dropTrailingPathSeparator, splitPath)
11+import Text.Pandoc
12+
13+
14+--------------------------------------------------------------------------------
15+main :: IO ()
16+main = hakyllWith config $ do
17+ -- Copy CHANGELOG.md here.
18+ preprocess $ copyFile "../CHANGELOG.md" "releases.markdown"
19+
20+ -- CSS
21+ match "css/*" $ do
22+ route idRoute
23+ compile compressCssCompiler
24+
25+ -- Static directories
26+ forM_ ["images/*", "examples/*"] $ \f -> match f $ do
27+ route idRoute
28+ compile copyFileCompiler
29+
30+ -- Haddock stuff
31+ match "reference/**.html" $ do
32+ route idRoute
33+ compile $ fmap (withUrls hackage) <$> getResourceString
34+
35+ -- Haddock stuff
36+ match ("reference/**" `mappend` complement "**.html") $ do
37+ route idRoute
38+ compile copyFileCompiler
39+
40+ -- Pages
41+ match "*.markdown" $ do
42+ route $ setExtension "html"
43+ compile $ pandocCompiler
44+ >>= loadAndApplyTemplate "templates/default.html" defaultContext
45+ >>= relativizeUrls
46+
47+ -- Tutorials
48+ match "tutorials/*" $ do
49+ route $ setExtension "html"
50+ compile $ pandocCompilerWith defaultHakyllReaderOptions withToc
51+ >>= loadAndApplyTemplate "templates/tutorial.html" defaultContext
52+ >>= loadAndApplyTemplate "templates/default.html" defaultContext
53+ >>= relativizeUrls
54+
55+ -- Tutorial list
56+ create ["tutorials.html"] $ do
57+ route idRoute
58+ compile $ do
59+ ctx <- tutorialsCtx <$>
60+ sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
61+ makeItem ""
62+ >>= loadAndApplyTemplate "templates/tutorials.html" ctx
63+ >>= loadAndApplyTemplate "templates/default.html" ctx
64+ >>= relativizeUrls
65+
66+ -- Templates
67+ match "templates/*" $ compile templateCompiler
68+ where
69+ withToc = defaultHakyllWriterOptions
70+ { writerTableOfContents = True
71+ , writerTemplate = Just "$toc$\n$body$"
72+ }
73+
74+
75+--------------------------------------------------------------------------------
76+config :: Configuration
77+config = defaultConfiguration
78+ { deployCommand = "rsync --checksum -ave 'ssh -p 2222' \
79+ \_site/* \
80+ \jaspervdj@jaspervdj.be:jaspervdj.be/hakyll/"
81+ }
82+
83+
84+--------------------------------------------------------------------------------
85+-- | Turns
86+--
87+-- > /usr/share/doc/ghc/html/libraries/base-4.6.0.0/Data-String.html
88+--
89+-- into
90+--
91+-- > http://hackage.haskell.org/packages/archive/base/4.6.0.0/doc/html/Data-String.html
92+hackage :: String -> String
93+hackage url
94+ | "/usr" `isPrefixOf` url =
95+ "http://hackage.haskell.org/packages/archive/" ++
96+ packageName ++ "/" ++ version' ++ "/doc/html/" ++ baseName
97+ | otherwise = url
98+ where
99+ (packageName, version') = second (drop 1) $ break (== '-') package
100+ (baseName : package : _) = map dropTrailingPathSeparator $
101+ reverse $ splitPath url
102+
103+
104+--------------------------------------------------------------------------------
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
examples/hakyll/templates/default.html → web/templates/default.htmlrenamed+19−6
@@ -15,14 +15,27 @@
1515 <meta name="description" content="Hakyll - A Static Site Generator in Haskell."/>
1616 </head>
1717 <body>
18+ <!-- Fork me on Github -->
19+ <a href="https://github.com/jaspervdj/hakyll"><img style="position: absolute; top: 0; right: 0; border: 0; margin: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
1820 <div id="main">
1921 <div id="header">
20− <img src="/images/lambda.png" alt="lambda" />
21− <h1>Hakyll - $title$</h1>
22+ <a href="/">
23+ <img src="/images/lambda.png" alt="lambda" />
24+ Hakyll
25+ </a>
2226 </div>
2327 <!-- Sidebar. -->
2428 <div id="sidebar">
25− $sidebar$
29+ <h1>Navigation</h1>
30+ <a href="/index.html">home</a>
31+ <a href="/tutorials.html">tutorials</a>
32+ <a href="/libraries.html">libraries</a>
33+ <a href="/reference.html">reference</a>
34+ <a href="/about.html">about</a>
35+ <a href="/tutorials/faq.html">faq</a>
36+ <a href="/examples.html">examples</a>
37+ <a href="/releases.html">releases</a>
38+ <a href="https://jaspervdj.be/contact.html#donate" target="_blank">donate</a>
2639 </div>
2740
2841 <div id="content">
@@ -36,12 +49,12 @@
3649
3750 <!-- Google Analytics -->
3851 <script type="text/javascript">
39− var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
40− document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
52+ var gaJsHost = (('https:' == document.location.protocol) ? 'https://ssl.' : 'http://www.');
53+ document.write(unescape('%3Cscript src=\'' + gaJsHost + 'google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E'));
4154 </script>
4255 <script type="text/javascript">
4356 try {
44− var pageTracker = _gat._getTracker("UA-11993001-1");
57+ var pageTracker = _gat._getTracker('UA-11993001-1');
4558 pageTracker._trackPageview();
4659 } catch(err) {}
4760 </script>
web/templates/tutorial.htmlnew file+21
@@ -0,0 +1,21 @@
1+<h1>Tutorial: $title$</h1>
2+
3+$body$
4+
5+<h1>Other tutorials</h1>
6+
7+The other tutorials can be found <a href="/tutorials.html">here</a>.
8+
9+<h1>Documentation inaccurate or out-of-date? Found a typo?</h1>
10+
11+Hakyll is an open source project, and one of the hardest parts is writing
12+correct, up-to-date, and understandable documentation. Therefore, the
13+authors would really appreciate it if you would
14+<a href="http://groups.google.com/group/hakyll">give feedback</a> about
15+the tutorials, and especially report errors or difficulties you encountered. If
16+you have a github account, you can use the
17+<a href="https://github.com/jaspervdj/hakyll/issues">issue system</a>. Thanks!
18+
19+If you run into any problems, all questions are welcome in the above google
20+group, or you could try the IRC channel, <code>#hakyll</code> on
21+<a href="http://freenode.net/">freenode</a>.
web/templates/tutorials.htmlnew file+56
@@ -0,0 +1,56 @@
1+<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>
11+<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>
45+<p>
46+ All these tutorials assume you are using the latest stable version of
47+ Hakyll. If this is not the case, you might want to update using:
48+</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>
web/tutorials/01-installation.markdownnew file+54
@@ -0,0 +1,54 @@
1+---
2+title: Installation
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Installation
8+------------
9+
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:
13+
14+ $ stack install hakyll
15+
16+[stack]: http://www.haskellstack.org/
17+
18+Linux distro packages:
19+
20+- [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`
23+
24+Building the example site
25+-------------------------
26+
27+Apart from the main Hakyll library, the cabal package also provides you with an
28+executable `hakyll-init` to create an example site. This is an easy way to get
29+started:
30+
31+ $ stack exec hakyll-init my-site
32+
33+This creates a folder `my-site` in the current directory, with some example
34+content and a generic configuration.
35+
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`.
39+
40+The file `site.hs` holds the configuration of your site, as an executable
41+haskell program. We can compile and run it like this:
42+
43+ $ cd my-site
44+ $ stack init # creates stack.yaml file based on my-site.cabal
45+ $ stack build
46+ $ stack exec site build
47+
48+If you installed `hakyll` with a preview server (this is the default), you can
49+now use
50+
51+ $ stack exec site watch
52+
53+and have a look at your site at
54+[http://localhost:8000/](http://localhost:8000/).
web/tutorials/02-basics.markdownnew file+49
@@ -0,0 +1,49 @@
1+---
2+title: The basics
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Building and cleaning
8+---------------------
9+
10+If 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:
13+
14+- `_site`, with your site as HTML files, ready to be deployed;
15+- `_cache`, which Hakyll uses internally.
16+
17+`stack exec site clean` removes these directories, and `stack exec site
18+rebuild` performs a `clean` and then a `build`.
19+
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:
23+
24+ stack build
25+ stack exec site rebuild
26+
27+At this point, feel free to change some files, `stack exec site build` and see
28+what happens!
29+
30+Pages and metadata
31+------------------
32+
33+You might've noticed that the markdown pages all start with a block:
34+
35+ ---
36+ title: Contact
37+ ---
38+
39+ I live...
40+
41+This is entirely optional, but useful for providing extra information
42+("metadata") about items. All items can have metadata: since it's not really
43+convenient to add such a header to an image, you can also do this using a
44+separate file.
45+
46+For a file called `images/foo.png`, you can add an `images/foo.png.metadata`
47+file with contents:
48+
49+ title: An image of a cow
web/tutorials/03-rules-routes-compilers.markdownnew file+137
@@ -0,0 +1,137 @@
1+---
2+title: Rules, routes and compilers
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Basic rules
8+-----------
9+
10+While changing the content is nice, you'll have time for that once your site is
11+configured. Configuration is done using the `site.hs` file: let's take a look at
12+it!
13+
14+```haskell
15+main :: IO ()
16+main = hakyll $ do
17+ ...
18+```
19+
20+Hakyll configurations are in the `Rules` monad. In order to run them, the
21+`hakyll` function is used, so your main function usually starts this way.
22+`hakyllWith` is also available, this function allows you specify a custom
23+[Configuration].
24+
25+[Configuration]: /reference/Hakyll-Core-Configuration.html
26+
27+Some actual rules look like this:
28+
29+```haskell
30+match "images/*" $ do
31+ route idRoute
32+ compile copyFileCompiler
33+
34+match "css/*" $ do
35+ route idRoute
36+ compile compressCssCompiler
37+
38+```
39+
40+This is a declarative DSL: the order in which you write the rules makes little
41+difference, Hakyll will use dependency tracking to determine the correct order.
42+
43+We group the different rules using `match`. The first argument for `match` is a
44+[Pattern]. The `OverloadedStrings` extension allows us to just write `String`s
45+here, which are interpreted as globs --- all files in the `images/` directory,
46+and all files in the `css/` directory.
47+
48+[Pattern]: /reference/Hakyll-Core-Identifier-Pattern.html
49+
50+However, we can see that one item makes no use of `match`, but uses `create`
51+instead.
52+
53+```haskell
54+create ["archive.html"] $ do
55+ route idRoute
56+ compile $ do
57+ ...
58+```
59+
60+Don't pay attention to the somewhat complicated-looking stuff in `compile` --
61+this will become clear soon. The real question here is why we use `create`
62+instead of `match`.
63+
64+The answer is simple: there is no `archive.html` file in our project directory!
65+So if we were to use `match`, no file would be matched, and hence, nothing
66+would appear in the output directory. `create`, however, ensures the items
67+listed are always produced.
68+
69+Basic routes
70+------------
71+
72+The `route` function is used for determining the output file. For example, you
73+probably want to write the processed contents of `contact.markdown` to
74+`_site/contact.html` and not `_site/contact.markdown`.
75+
76+`idRoute` is a commonly used route and just keeps the filename. We use this for
77+e.g. the images and CSS files.
78+
79+`setExtension` is another common route which takes a single argument: the
80+desired extension of the resulting file. In order to route `contact.markdown` to
81+`_site/contact.html`, use:
82+
83+```haskell
84+route $ setExtension "html"
85+```
86+
87+`customRoute` is a more advanced higher-order function which allows for even
88+more customization. You want to route `contact.markdown` to
89+`_site/nwodkram.tcatnoc`? No problem, just use:
90+
91+```haskell
92+route $ customRoute $ reverse . toFilePath
93+```
94+
95+More information can be found in the [Routes] module.
96+
97+[Routes]: /reference/Hakyll-Core-Routes.html
98+
99+Basic compilers
100+---------------
101+
102+The `compile` function determines how the content is produced for a certain
103+item. `compile` takes a value of the type `Compiler (Item a)`. Let's look at
104+some common examples:
105+
106+- `copyFileCompiler` is self-explanatory, the output is exactly the same as the
107+ input;
108+- `compressCssCompiler` performs some simple build-in compression
109+ transformations for CSS;
110+- `pandocCompiler` reads markdown, reStructuredText, or another input format and
111+ renders it as HTML (if you want to pass specific options to pandoc, use
112+ `pandocCompilerWith`).
113+
114+Compilers are very flexible: `Compiler` is a [Monad] and `Item` is a [Functor].
115+
116+[Monad]: http://learnyouahaskell.com/a-fistful-of-monads
117+[Functor]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids
118+
119+A good example to illustrate the `Monad` instance for `Compiler` is
120+
121+```haskell
122+relativizeUrls :: Item String -> Compiler (Item String)
123+```
124+
125+This compiler traverses your HTML and changes absolute URLs (e.g.
126+`/posts/foo.markdown` into relative ones: `../posts/foo.markdown`). This is
127+extremely useful if you want to deploy your site in a subdirectory (e.g.
128+`jaspervdj.be/hakyll` instead of `jaspervdj.be`). Combining this with the
129+`pandocCompiler` gives us:
130+
131+```haskell
132+pandocCompiler >>= relativizeUrls :: Compiler (Item String)
133+```
134+
135+For a real website, you probably also want to use templates in order to give
136+your pages produced by pandoc a nice layout. We tackle this in the next
137+tutorial.
web/tutorials/04-compilers.markdownnew file+295
@@ -0,0 +1,295 @@
1+---
2+title: 'More on compilers: load, and templates'
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Loading items
8+-------------
9+
10+The compiler Monad is a complex beast, but this is nicely hidden for the user of
11+the Hakyll library.
12+
13+Suppose that you're generating `index.html` which shows your latest brilliant
14+blogpost. This requires `posts/foo.markdown` to be generated *before*
15+`index.html` (so we don't have to generate it twice). But you don't have to care
16+about any of that: Hakyll will sort this out for you automatically!
17+
18+Let's see some quick examples. We can load a specific item:
19+
20+```haskell
21+load "posts/foo.markdown" :: Compiler (Item String)
22+```
23+
24+Or a whole bunch of them:
25+
26+```haskell
27+loadAll "posts/*" :: Compiler [Item String]
28+```
29+
30+Sometimes you just want the *contents* and not the `Item`:
31+
32+```haskell
33+loadBody "posts/foo.markdown" :: Compiler String
34+```
35+
36+This is all useful if we want to use Hakyll's templating system.
37+
38+Templates
39+---------
40+
41+### Basic templates
42+
43+Let's have a look at a simple template:
44+
45+```html
46+<h1>$title$</h1>
47+<div class="info">Posted on $date$</div>
48+$body$
49+```
50+
51+As you can probably guess, template files just contain text and only the `$`
52+character has special meaning: text between dollar signs ("fields") is replaced
53+when the template is applied. If you want an actual dollar sign in the output,
54+use `$$`.
55+
56+You usually compile the templates from disk using the aptly named
57+`templateBodyCompiler`:
58+
59+```haskell
60+match "templates/*" $ compile templateBodyCompiler
61+```
62+
63+Notice the lack of `route` here: this is because we don't need to write the
64+templates to your `_site` folder, we just want to use them elsewhere.
65+
66+### Templates: Context
67+
68+We can easily guess the meaning of `$title$`, `$date$`, and `$body$`, but these
69+are not hard-coded fields: they belong to a certain [Context]. A `Context`
70+determines how the fields are interpreted. It's a [Monoid] and therefore very
71+composable.
72+
73+[Context]: /reference/Hakyll-Web-Template-Context.html
74+[Monoid]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids
75+
76+`field` allows us to create a `Context` for a single field:
77+
78+```haskell
79+field :: String -> (Item a -> Compiler String) -> Context a
80+```
81+
82+Let's try this out. Note that this is for illustration purposes only: you
83+shouldn't have to write complicated fields often. We can implement the `$body$`
84+field like this:
85+
86+```haskell
87+field "body" $ \item -> return (itemBody item) :: Context String
88+```
89+
90+And `$title$` like this:
91+
92+```haskell
93+titleContext :: Context a
94+titleContext = field "title" $ \item -> do
95+ metadata <- getMetadata (itemIdentifier item)
96+ return $ fromMaybe "No title" $ lookupString "title" metadata
97+```
98+
99+And compose them using the `Monoid` instance:
100+
101+```haskell
102+context :: Context String
103+context = mconcat
104+ [ titleContext
105+ , field "body" $ return . itemBody
106+ ]
107+```
108+
109+Obviously, it would be tedious to implement things like `titleContext` over and
110+over again for different websites and different fields. This is why hakyll
111+provides `defaultContext`. `defaultContext` is a composed `Context` and allows
112+you to use:
113+
114+- `$body$` for the body of the page;
115+- `$url$` for the destination URL of the page;
116+- `$path$` for the original filepath of the page;
117+- `$foo$` where foo is specified in the metadata.
118+
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`:
123+
124+```haskell
125+postCtx :: Context String
126+postCtx =
127+ dateField "date" "%B %e, %Y" `mappend`
128+ defaultContext
129+```
130+
131+### Loading and applying templates
132+
133+Now 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
135+template:
136+
137+```haskell
138+compile $ do
139+ tpl <- loadBody "templates/post.html"
140+ pandocCompiler >>= applyTemplate tpl postCtx
141+```
142+
143+Loading and then immediately applying a template is so common there's a
144+shorthand function:
145+
146+```haskell
147+compile $
148+ pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx
149+```
150+
151+Control flow in templates
152+-------------------------
153+
154+Sometimes 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
156+structures for this. The syntax for these structures was based on the syntax
157+used in pandoc templates, since Hakyll already has tight integration with
158+pandoc.
159+
160+### Conditionals
161+
162+In `templates/post.html` of the example site we generated using `hakyll-init`,
163+we see an example of a conditional:
164+
165+```html
166+<div class="info">
167+ Posted on $date$
168+ $if(author)$
169+ by $author$
170+ $endif$
171+</div>
172+```
173+
174+This example should be pretty straightforward. One important thing to notice is
175+that `$if(foo)$` **does not** check the truth value of `$foo$`: it merely checks
176+if such a key is present.
177+
178+Note that an if-else form is supported as well:
179+
180+```html
181+<div class="info">
182+ Posted on $date$
183+ $if(author)$
184+ by $author$
185+ $else$
186+ by some unknown author
187+ $endif$
188+</div>
189+```
190+
191+### Partials
192+
193+Partials allow you to [DRY] up your templates by putting repetitive actions into
194+separate template files. You can then include them using
195+`$partial("filename.html")$`.
196+
197+[DRY]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
198+
199+An example can be found in `templates/archive.html`:
200+
201+```html
202+Here you can find all my previous posts:
203+$partial("templates/post-list.html")$
204+```
205+
206+This partial is just another template and uses the same syntax. Note that in
207+order to use something like this, we also need to load the partial template in
208+our `site.hs`:
209+
210+```haskell
211+match "templates/post-list.html" $ compile templateCompiler
212+```
213+
214+Fortunately, we usually don't need to add this since we already have:
215+
216+```haskell
217+match "templates/*" $ compile templateCompiler
218+```
219+
220+### Producing a list of items: for
221+
222+At this point, everything in the example site we generated should be clear to
223+you, except for how we produce the list of posts in `archive.html` and
224+`index.html`. Let's look at the `templates/post-list.html` template:
225+
226+```html
227+<ul>
228+ $for(posts)$
229+ <li>
230+ <a href="$url$">$title$</a> - $date$
231+ </li>
232+ $endfor$
233+</ul>
234+```
235+
236+This uses the `$for(foo)$` construct. This construct allows you loop over a
237+list, in this case, `$posts$`. Inside the body of this for loop, all fields
238+refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
239+
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+
254+Of course, $posts$ does not magically appear. We have to specify this in
255+`site.hs`. Let's look at how `archive.html` is generated:
256+
257+```haskell
258+posts <- recentFirst =<< loadAll "posts/*"
259+let archiveCtx =
260+ listField "posts" postCtx (return posts) `mappend`
261+ constField "title" "Archives" `mappend`
262+ defaultContext
263+```
264+
265+We discussed `loadAll` earlier in this tutorial.
266+
267+`recentFirst` sorts items by date. This relies on the convention that posts are
268+always named `YYYY-MM-DD-title.extension` in Hakyll -- or that the date must be
269+present in the metadata.
270+
271+```haskell
272+recentFirst :: [Item a] -> Compiler [Item a]
273+```
274+
275+After loading and sorting the items, we use `listField` to create the `$posts$`
276+key.
277+
278+```haskell
279+listField :: String -> Context a -> Compiler [Item a] -> Context b
280+```
281+
282+The first parameter is simply the name of the key (`"posts"`). Secondly we have
283+a `Context` with which all items should be rendered -- for our example site, we
284+already wrote such a `Context` for posts: `postCtx`. Lastly, we have a
285+`Compiler` which loads the items. We already loaded the items so we can just use
286+`return posts`.
287+
288+The following snippet would produce the same result:
289+
290+```haskell
291+let archiveCtx =
292+ listField "posts" postCtx (recentFirst =<< loadAll "posts/*") `mappend`
293+ constField "title" "Archives" `mappend`
294+ defaultContext
295+```
web/tutorials/05-snapshots-feeds.markdownnew file+129
@@ -0,0 +1,129 @@
1+---
2+title: Snapshots, and how to produce an RSS/Atom feed
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Basic feed configuration
8+------------------------
9+
10+Hakyll has built-in support for two types of feeds: RSS and Atom. This tutorial
11+explains how you can add these to your blog or website. The first step is to
12+define a `FeedConfiguration` to set some basic options. For example, a cooking
13+blog may have the following declaration:
14+
15+```haskell
16+myFeedConfiguration :: FeedConfiguration
17+myFeedConfiguration = FeedConfiguration
18+ { feedTitle = "Healthy cooking: latest recipes"
19+ , feedDescription = "This feed provides fresh recipes for fresh food!"
20+ , feedAuthorName = "John Doe"
21+ , feedAuthorEmail = "test@example.com"
22+ , feedRoot = "http://healthycooking.example.com"
23+ }
24+```
25+
26+Simple feed rendering
27+---------------------
28+
29+Now, let's look at how we actually create a feed. Two functions are available:
30+
31+```haskell
32+renderAtom :: FeedConfiguration
33+ -> Context String
34+ -> [Item String]
35+ -> Compiler (Item String)
36+```
37+
38+```haskell
39+renderRss :: FeedConfiguration
40+ -> Context String
41+ -> [Item String]
42+ -> Compiler (Item String)
43+```
44+
45+As you can see, they have exactly the same signature: we're going to use
46+`renderAtom` in this tutorial, but it's trivial to change this to an RSS feed.
47+
48+```haskell
49+create ["atom.xml"] $ do
50+ route idRoute
51+ compile $ do
52+ let feedCtx = postCtx `mappend`
53+ constField "description" "This is the post description"
54+
55+ posts <- fmap (take 10) . recentFirst =<< loadAll "posts/*"
56+ renderAtom myFeedConfiguration feedCtx posts
57+```
58+
59+There we go! We simply take the 10 last posts and pass them to `renderAtom`,
60+with our configuration and a `Context`.
61+
62+It's a bit of a problem that we don't have a description for our posts, and the
63+Atom/RSS feed renderer requires this. One option is to add a `description: Foo`
64+header to our all posts. However, the description is the body text as it appears
65+in most RSS readers, so we would prefer to include the entire content of the
66+posts here.
67+
68+Snapshots
69+---------
70+
71+This poses a problem: if we just `load` all posts and take their content: we get
72+the *finished, processed* content. This means all templates have been applied at
73+that point (including `templates/default.html`). We don't want to include our
74+entire site with navigation in the RSS feed, but rather just the post HTML.
75+
76+Snapshots provide a solution to this problem. They allow you to save an `Item`
77+at any point during its compilation, so you can `load` it later. Let's apply
78+this to our concrete problem.
79+
80+```haskell
81+match "posts/*" $ do
82+ route $ setExtension "html"
83+ compile $ pandocCompiler
84+ >>= loadAndApplyTemplate "templates/post.html" postCtx
85+ >>= loadAndApplyTemplate "templates/default.html" postCtx
86+ >>= relativizeUrls
87+```
88+
89+now becomes:
90+
91+```haskell
92+match "posts/*" $ do
93+ route $ setExtension "html"
94+ compile $ pandocCompiler
95+ >>= loadAndApplyTemplate "templates/post.html" postCtx
96+ >>= saveSnapshot "content"
97+ >>= loadAndApplyTemplate "templates/default.html" postCtx
98+ >>= relativizeUrls
99+```
100+
101+The `saveSnapshot` function is really simple: it takes an item and returns the
102+same item, after saving it. This return value makes it easier to use
103+`saveSnapshot` in a chain of compilers as we did in the above example, but you
104+can discard it if you want.
105+
106+```haskell
107+type Snapshot = String
108+
109+saveSnapshot :: (Typeable a, Binary a)
110+ => Snapshot -> Item a -> Compiler (Item a)
111+```
112+
113+Including the post body
114+-----------------------
115+
116+With this modification, we can update our Atom code. Instead of loading the
117+compiled posts, we just load their content (i.e. the snapshot we just took).
118+
119+We update the `Context` to map `$description$` to the post body, and we're done!
120+
121+```haskell
122+create ["atom.xml"] $ do
123+ route idRoute
124+ compile $ do
125+ let feedCtx = postCtx `mappend` bodyField "description"
126+ posts <- fmap (take 10) . recentFirst =<<
127+ loadAllSnapshots "posts/*" "content"
128+ renderAtom myFeedConfiguration feedCtx posts
129+```
web/tutorials/06-versions.markdownnew file+68
@@ -0,0 +1,68 @@
1+---
2+title: Producing multiple versions of a single file
3+author: Jasper Van der Jeugt
4+type: main
5+---
6+
7+Basics
8+------
9+
10+Suppose that you want to make your writings available in raw markdown format, in
11+addition to the HTML available on your website. Is this possible using Hakyll?
12+
13+In the [previous tutorial], we explained how you can use snapshots to store an
14+item while it's being processed. However, this does not allow you to route it to
15+a different location.
16+
17+[previous tutorial]: /tutorials/05-snapshots-feeds.html
18+
19+Instead, we must use `version` in order to do this. The type signature of
20+`version` does not reveal much:
21+
22+```haskell
23+version :: String -> Rules () -> Rules ()
24+```
25+
26+So let's look at an example:
27+
28+```haskell
29+match "posts/*" $ do
30+ route $ setExtension "html"
31+ compile $ pandocCompiler
32+ >>= loadAndApplyTemplate "templates/post.html" postCtx
33+ >>= loadAndApplyTemplate "templates/default.html" postCtx
34+ >>= relativizeUrls
35+
36+match "posts/*" $ version "raw" $ do
37+ route idRoute
38+ compile getResourceBody
39+```
40+
41+Here, you can see how we produce two versions of each item in `posts/*`: one
42+`"raw"` version, and the default version.
43+
44+Attention: patterns and versions
45+--------------------------------
46+
47+However, there is one important thing to note: suppose you use `Pattern`s for a
48+function such as `loadAll`, e.g. to create an index page with all blogposts.
49+
50+```haskell
51+loadAll "posts/*" :: Compiler [Item String]
52+```
53+
54+is valid code, but **probably not** what you want to do: this will select all
55+`posts/*` items, meaning, both your HTML posts, and the raw versions. In order
56+to fix this, use any of the following:
57+
58+```haskell
59+loadAll ("posts/*" .&&. hasNoVersion) :: Compiler [Item String]
60+```
61+
62+for the default versions, i.e. the HTML pages, and:
63+
64+```haskell
65+loadAll ("posts/*" .&&. hasVersion "raw") :: Compiler [Item String]
66+```
67+
68+for the raw versions.
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdownnew file+96
@@ -0,0 +1,96 @@
1+---
2+title: A Guide to the Hakyll Module Zoo
3+author: Brent Yorgey
4+type: article
5+---
6+
7+The hakyll package [contains a bewildering array](/reference/) of modules, and
8+it's hard to know where to look when you are just getting started -- especially
9+since many of them are mostly just used by Hakyll internally and not that useful
10+to website authors. This guide provides a quick reference to the contents of the
11+most important and useful modules.
12+
13+## Core modules
14+
15+These are the modules containing the fundamental tools and concepts
16+you need to get started building a site with hakyll.
17+
18+* [Hakyll.Core.Compiler](/reference/Hakyll-Core-Compiler.html)
19+
20+ This is one of the modules you should look at first. It defines the
21+ fundamental `Compiler` type and primitive `Compiler`s as well as several
22+ variants on `load`, which allow you to bring together multiple resources to
23+ create a single output.
24+
25+* [Hakyll.Core.Routes](/reference/Hakyll-Core-Routes.html)
26+
27+ Specify where compiled items should go in the output site.
28+
29+* [Hakyll.Core.Rules](/reference/Hakyll-Core-Rules.html)
30+
31+ Specify which compilers and routes should be used on which resources. Also
32+ has combinators for versions (necessary if you want to use the same
33+ resources for multiple purposes).
34+
35+* [Hakyll.Core.Identifier.Pattern](/reference/Hakyll-Core-Identifier-Pattern.html)
36+
37+ Combinators for creating *patterns*, i.e. predicates that pick out a set of
38+ resources: filesystem globs, explicit lists, regular expressions, and so on.
39+
40+* [Hakyll.Web.Page](/reference/Hakyll-Web-Pandoc.html)
41+
42+ Provides functions for rendering your pages using pandoc, with a varying
43+ degree of high-levelness.
44+
45+* [Hakyll.Web.Template](/reference/Hakyll-Web-Template.html)
46+
47+ Templates specify how to take the content of an item and turn it into HTML
48+ output.
49+
50+* [Hakyll.Web.Template.Context](/reference/Hakyll-Web-Template-Context.html)
51+
52+ You can't do much with a `Template` if you don't have a `Context`: this
53+ module provides some predefined `Context`s and the tools to build your own.
54+
55+* [Hakyll.Main](/reference/Hakyll-Main.html)
56+
57+ The main `hakyll` function that runs the whole show. There is also a
58+ `hakyllWith` function which allows for a custom configuration.
59+
60+## Pre-packaged solutions
61+
62+These modules contain some "pre-packaged" solutions for common situations. They
63+can be used as-is, or their source can be used as inspiration for your own
64+tools.
65+
66+* [Hakyll.Web.Template.List](/reference/Hakyll-Web-Template-List.html)
67+
68+ Combine several pages into a list, such as a list of posts, images in a
69+ gallery, etc.
70+
71+* [Hakyll.Web.Feed](/reference/Hakyll-Web-Feed.html)
72+
73+ Create RSS feeds.
74+
75+* [Hakyll.Web.Tags](/reference/Hakyll-Web-Tags.html)
76+
77+ Work with tags and categories.
78+
79+## Useful utilities
80+
81+* [Hakyll.Core.UnixFilter](/reference/Hakyll-Core-UnixFilter.html)
82+
83+ Use any unix utility as a compiler.
84+
85+* [Hakyll.Core.Util.String](/reference/Hakyll-Core-Util-String.html)
86+
87+ A few utility functions for working with `String`s (trim spaces, find and
88+ replace, split on regexp).
89+
90+## Advanced customization
91+
92+* [Hakyll.Core.Writable](/reference/Hakyll-Core-Writable.html)
93+
94+ The `Writable` class is for resources which can be written to disk as part
95+ of the output site. You can specify how to write your own custom types to
96+ disk by giving them a `Writable` instance.
web/tutorials/external-add-tags-to-your-hakyll-blog.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: 'External code inclusion with Hakyll'
3+author: 'Tobias Pleyer'
4+url: 'http://blog.tpleyer.de/posts/2019-04-21-external-code-inclusion-with-hakyll.html'
5+type: external
6+---
web/tutorials/external-functionfield.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.mdnew file+6
@@ -0,0 +1,6 @@
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.markdownnew file+95
@@ -0,0 +1,95 @@
1+---
2+title: FAQ
3+author: Jasper Van der Jeugt
4+type: article
5+---
6+
7+## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
8+
9+If you get any of the errors:
10+
11+ commitBuffer: invalid argument (invalid character)
12+
13+or:
14+
15+ hGetContents: invalid argument (Invalid or incomplete multibyte or wide
16+ character)
17+
18+It means that your Hakyll executable couldn't write to (in the former case) or
19+read (in the latter) from an UTF-8 encoded file.
20+
21+On most linux distros, you can solve this by setting your `LANG` to use UTF-8,
22+using something like:
23+
24+ LANG=nl_BE.UTF-8 ./site build
25+
26+You should also add this to your `.profile`, or whatever config file you use.
27+
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+
43+## "File name does not match module name" on Mac OS
44+
45+ Hakyll.hs:1:1:
46+ File name does not match module name:
47+ Saw: `Main'
48+ Expected: `Hakyll'
49+
50+Is an error encountered on Mac OS when your configuration is named `hakyll.hs`
51+and located on a case-insensitive filesystem. A workaround is to rename it to
52+something that isn't the name of the module, for example, `site.hs`.
53+
54+## pandocCompiler/Hakyll/Pandoc eats my HTML!
55+
56+Sometimes, it can seem that HTML pages are stripped of some arbitrary tags, e.g.
57+`<div>`'s. The issue here is that, when using the default `pandocCompiler`, your
58+page passes through Pandoc. Pandoc unfortunately strips away this information,
59+giving you the "wrong" HTML.
60+
61+The solution is not to use `pandocCompiler`, but something simpler like
62+`getResourceBody`. This way, your HTML is not touched.
63+
64+## Does Hakyll support syntax highlighting?
65+
66+Syntax highlighting is enabled by default in Hakyll if you are using a somewhat
67+recent version of Pandoc (1.9 and onwards). Note that you also need to include
68+some CSS in order for this to work! This site, for example, uses the [default
69+Pandoc syntax CSS file][syntax-css].
70+
71+To highlight a code block, you need to use Pandoc's fenced code block syntax to
72+set the block's language. For example, here's how you highlight Haskell code:
73+
74+ ``` haskell
75+ fac n = foldr (*) 1 [1..n]
76+ ```
77+
78+For details, see Pandoc's user guide on [fenced code
79+blocks][pandoc-code-blocks] and [inline code][pandoc-inline-code].
80+
81+[syntax-css]: https://github.com/jaspervdj/hakyll/blob/master/web/css/syntax.css
82+[pandoc-code-blocks]: http://johnmacfarlane.net/pandoc/README.html#fenced-code-blocks
83+[pandoc-inline-code]: http://johnmacfarlane.net/pandoc/README.html#verbatim
84+
85+## When should I rebuild and when should I build?
86+
87+If you execute a `./site build`, Hakyll will build your site incrementally.
88+However, we can not detect if you edited `site.hs`. In this case, you first want
89+to compile `site.hs` again, and then do a `./site rebuild`.
90+
91+After rebuilding your site, all files will look as "modified" to the filesystem.
92+This means that when you upload your site, it will usually transfer all files --
93+this can generate more traffic than necessary, since it is possible that some
94+files were not actually modified. If you use `rsync`, you can counter this using
95+the `--checksum` option.
web/tutorials/github-pages-tutorial.mdnew file+181
@@ -0,0 +1,181 @@
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.markdownnew file+78
@@ -0,0 +1,78 @@
1+---
2+title: Hakyll 3 to Hakyll 4 migration guide
3+author: Jasper Van der Jeugt
4+type: article
5+---
6+
7+Introduction
8+------------
9+
10+This tutorial gives a quick overview on how you can port your blog/website from
11+Hakyll 3.X to Hakyll 4. A lot of changes have happened, so it might be useful to
12+read through the [tutorial series](/tutorials.html) before porting your website.
13+
14+Arrow becomes Monad
15+-------------------
16+
17+In Hakyll 3.X, `Compiler` was an instance of `Arrow`. Since Hakyll 4, `Compiler`
18+is a `Monad`. This means that previous chains such as:
19+
20+```haskell
21+compile $ someCompiler
22+ >>> someOtherCompiler
23+ >>> anotherCompiler
24+```
25+
26+Now take the general form of:
27+
28+```haskell
29+compile $ someCompiler
30+ >>= someOtherCompiler
31+ >>= anotherCompiler
32+```
33+
34+Page goes away
35+--------------
36+
37+The `Page` type in Hakyll 3.X has been removed and replaced by an `Item` type.
38+`pageCompiler` no longer exists -- where you previously used this, you probably
39+want to use `pandocCompiler` instead.
40+
41+`Page`s were manipulated using `setField`/`getField` functions in Hakyll 3.X.
42+In Hakyll 4, all metadata is completely immutable, so these functions have been
43+removed. In order to format and add fields, use a `Context` -- see the next
44+section.
45+
46+Template changes
47+----------------
48+
49+The template format has become slightly more flexible, whereas in Hakyll 3.X
50+only keys such as this were allowed:
51+
52+```html
53+<h1>$title$</h1>
54+```
55+
56+we now allow arbitrary strings. This will be really useful in the future.
57+
58+```html
59+<h1>$uppercase title$</h1>
60+```
61+
62+Some template functions have been renamed:
63+
64+- `applyTemplateCompiler` becomes: `loadAndApplyTemplate`
65+- `applySelf` becomes: `applyAsTemplate`
66+
67+Instead of setting fields in a `Page` before applying a template, we now use a
68+`Context`. More information on context can be found in
69+[this tutorial](/tutorials/04-compilers.html). For migration, you basically want
70+to map every `setField` to a field in a `Context`.
71+
72+Metacompilers go away
73+---------------------
74+
75+For tags, the [Hakyll.Web.Tags] module still provides a solution. In other
76+cases, the `preprocess` function should be able to compensate for this.
77+
78+[Hakyll.Web.Tags]: /reference/Hakyll-Web-Tags.html
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 1: Setup & Initial Customization'
3+url: 'https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 2: Generating a Sitemap XML File'
3+url: 'https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 3: Generating RSS and Atom XML Feeds'
3+url: 'https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-04-copying-static-files-for-your-build.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 4: Copying Static Files For Your Build'
3+url: 'https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 5 – Generating Custom Post Filenames From a Title Slug'
3+url: 'https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html'
4+type: robertwpearce
5+---
web/tutorials/using-clay-with-hakyll.markdownnew file+52
@@ -0,0 +1,52 @@
1+---
2+title: Using Clay with Hakyll
3+author: Jasper Van der Jeugt
4+type: article
5+---
6+
7+[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
8+written in Haskell. There are multiple options to use this together with Hakyll,
9+but in this short tutorial I focus on what I think is the best way.
10+
11+This method requires every Clay file to have a `main` function which just prints
12+the CSS. This would be an example of such a file:
13+
14+``` haskell
15+{-# LANGUAGE OverloadedStrings #-}
16+import Clay
17+
18+test :: Css
19+test = ...
20+
21+main :: IO ()
22+main = putCss test
23+```
24+
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+
36+Let's assume such a file is called `css/foo.hs`. In our compiled site, we want
37+to map this to `css/foo.css`. Hence, the route is a simple `setExtension`. For
38+compilation, we simply pass the Clay file through `runghc` with no options.
39+
40+```haskell
41+match "css/*.hs" $ do
42+ route $ setExtension "css"
43+ 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
47+```
48+
49+The major advantage of using this method (as opposed to importing the Clay files
50+in `site.hs`) is that now Hakyll will only recompile the Clay files when
51+necessary, and you don't have to manually recompile your `site.hs` each time you
52+want to tweak your CSS a little.
web/tutorials/using-teasers-in-hakyll.markdownnew file+109
@@ -0,0 +1,109 @@
1+---
2+title: Using teasers in Hakyll
3+author: Ivan Veselov
4+type: article
5+---
6+
7+## Introduction
8+
9+Sometimes it is convenient to have an excerpt of a post displayed on
10+the index page along with a "Read more..." link to encourage your
11+readers to read the rest of the post.
12+
13+You can do this in Hakyll by using "teasers" functionality.
14+
15+## Marking teasers in posts
16+
17+First, you have to put a tag in your post which separates the teaser
18+from the rest of the article. We use `<!--more-->` for this to mimic
19+the [WordPress convention](http://codex.wordpress.org/Customizing_the_Read_More):
20+
21+``` markdown
22+---
23+title: My teasering post
24+---
25+In this post I'll try to explain how to use teasers.
26+
27+<!--more-->
28+
29+And here I am proceeding with the explanation.
30+
31+```
32+
33+This is an HTML comment, so it doesn't have any visual impact on the
34+post, it is there solely for semantic purposes.
35+
36+## Referring to teasers in templates
37+
38+Now, we want to refer to the teaser in the template. We can do this
39+pretty intuitively by using `$teaser$` key:
40+
41+``` html
42+<li class="post_item">
43+ $date$ - $title$
44+ <p>Teaser: $teaser$</p>
45+ <a href="$url$">Read more</a>
46+</li>
47+```
48+
49+## Gluing together
50+
51+The only thing left is to glue those things together. An important
52+question here is on what stage of the compilation we want to extract a
53+teaser. Usually, we want to use pandoc output, i.e. raw HTML without
54+any templates applied (since we do not want some surrounding
55+JavaScript or common text from the templates to be included in the
56+teaser). We can specify this by using snapshots: we save the snapshot
57+during compilation and load it to resolve `$teaser$` key later:
58+
59+``` haskell
60+match "posts/*" $ do
61+ route $ setExtension ".html"
62+ compile $
63+ pandocCompiler
64+ -- save immediately after pandoc, but before the templates are applied
65+ >>= saveSnapshot "content"
66+ >>= loadAndApplyTemplate "templates/post.html" defaultContext
67+ ...
68+```
69+
70+You can read more about snapshots in
71+[Snapshots tutorial](/tutorials/05-snapshots-feeds.html).
72+
73+Then we use this snapshot while generating teasers using the
74+`teaserField` function:
75+
76+``` haskell
77+ loadAndApplyTemplate
78+ "template/postitem.html"
79+ (teaserField "teaser" "content" <> defaultContext)
80+ item
81+```
82+
83+Here, we've just added a new context which knows how to handle
84+`$teaser$` key to the default context (note that we passed the same
85+snapshot name `"content"` which we used while saving).
86+
87+## Optional teasers
88+
89+In case you don't add a `<!--more-->` comment, `$teaser$` will not be defined.
90+This means you can use something like:
91+
92+```html
93+$if(teaser)$
94+ $teaser$
95+$else$
96+ $body$
97+$endif$
98+```
99+
100+## Known issues
101+
102+Since we use an HTML comment `<!--more-->` to separate the teaser,
103+sometimes `pandoc` can "eat" the comment while converting (for example
104+this happens with literate Haskell source). In order to overcome this
105+problem you may try to use something like this as a separator:
106+
107+``` html
108+<div></div><!--more-->
109+```