on master help sign in
.circleci/config.ymlnew file+19
since splitsnapshots
unifiedsplit
@@ -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−2
@@ -1,2 +1 @@
1−:set -isrc -isrc-interval -itests -idist/build/autogen -DPREVIEW_SERVER
1+:set -isrc -itests -idist/build/autogen -DPREVIEW_SERVER
2−:set -optP-include -optPdist/build/autogen/cabal_macros.h
.gitignore+5−1
@@ -1,4 +1,4 @@
1−# Ignore swap files and cabal output.
1+# Ignore swap files and stack/cabal output.
2*.hi
2*.hi
3*.o
3*.o
4*.swo
4*.swo
@@ -10,6 +10,7 @@ dist
10tags
10tags
11cabal.sandbox.config
11cabal.sandbox.config
12.cabal-sandbox/
12.cabal-sandbox/
13+.stack-work
13
14
14# Ignore test builds.
15# Ignore test builds.
15tests/Main
16tests/Main
@@ -22,3 +23,6 @@ web/_cache
22web/_site
23web/_site
23web/site
24web/site
24web/reference
25web/reference
26+
27+# This is generated from the CHANGELOG
28+web/releases.markdown
.travis.ymldeleted−1
@@ -1 +0,0 @@
1−language: haskell
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
2 
2 
3All rights reserved.
3All rights reserved.
4 
4 
Makefile+6−6
@@ -1,9 +1,9 @@
1+PACKAGE="$(shell stack list-dependencies --separator='-' | grep hakyll)"
2+LOCAL_DOC_ROOT="$(shell stack path --local-doc-root)"
3+
1# Generate the docs and copy them to the website dir
4# Generate the docs and copy them to the website dir
2haddock:
5haddock:
3− cabal haddock --hyperlink-source
6+ stack build --haddock --no-haddock-deps
4− rm -rf web/reference/
7+ rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
5− cp -r dist/doc/html/hakyll/ web/reference/
6
8
7−# Run the tests
9+.PHONY: haddock
8−test:
9− runghc -isrc -itests tests/TestSuite.hs
README.markdown+5−1
@@ -1,6 +1,6 @@
1# hakyll
1# hakyll
2
2
3−[![Build Status](https://secure.travis-ci.org/jaspervdj/hakyll.png?branch=master)](http://travis-ci.org/jaspervdj/hakyll)
3+[![Build Status](https://img.shields.io/circleci/project/github/jaspervdj/hakyll.svg)](https://circleci.com/gh/jaspervdj/hakyll)
4
4
5Hakyll is a static site generator library in Haskell. More information
5Hakyll is a static site generator library in Haskell. More information
6(including a tutorial) can be found on
6(including a tutorial) can be found on
@@ -9,3 +9,7 @@ Hakyll is a static site generator library in Haskell. More information
9You can install this library using cabal:
9You can install this library using cabal:
10
10
11 cabal install hakyll
11 cabal install hakyll
12+
13+Or using stack:
14+
15+ stack install hakyll
data/example/css/default.css+122−37
@@ -1,56 +1,141 @@
1−body {
1+html {
2− color: black;
3− font-size: 16px;
2+ font-size: 62.5%;
4− margin: 0px auto 0px auto;
5− width: 600px;
6}
3}
7
4
8−div#header {
5+body {
9− border-bottom: 2px solid black;
6+ font-size: 1.6rem;
10− margin-bottom: 30px;
7+ color: #000;
11− padding: 12px 0px 12px 0px;
12}
8}
13
9
14−div#logo a {
10+header {
15− color: black;
11+ border-bottom: 0.2rem solid #000;
16− float: left;
17− font-size: 18px;
18− font-weight: bold;
19− text-decoration: none;
20}
12}
21
13
22−div#header #navigation {
14+nav {
23− text-align: right;
15+ text-align: right;
24}
16}
25
17
26−div#header #navigation a {
18+nav a {
27− color: black;
28− font-size: 18px;
19+ font-size: 1.8rem;
29− font-weight: bold;
20+ font-weight: bold;
30− margin-left: 12px;
21+ color: black;
31− text-decoration: none;
22+ text-decoration: none;
32− text-transform: uppercase;
23+ text-transform: uppercase;
33}
24}
34
25
35−div#footer {
26+footer {
27+ margin-top: 3rem;
28+ padding: 1.2rem 0;
36− border-top: solid 2px black;
29+ border-top: 0.2rem solid #000;
37− color: #555;
38− font-size: 12px;
30+ font-size: 1.2rem;
39− margin-top: 30px;
31+ color: #555;
40− padding: 12px 0px 12px 0px;
41− text-align: right;
42}
32}
43
33
44h1 {
34h1 {
45− font-size: 24px;
35+ font-size: 2.4rem;
46}
36}
47
37
48h2 {
38h2 {
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 {
49− font-size: 20px;
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+ }
50}
110}
51
111
52−div.info {
112+@media (min-width: 640px) {
53− color: #555;
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;
54− font-size: 14px;
139+ font-size: 1.8rem;
55− font-style: italic;
140+ }
56}
141}
data/example/posts/2012-08-12-spqr.markdown → data/example/posts/2015-08-12-spqr.markdownrenamed

The content is the same.

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

The content is the same.

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

The content is the same.

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

The content is the same.

data/example/site.hs+1−1
@@ -57,7 +57,7 @@ main = hakyll $ do
57 >>= loadAndApplyTemplate "templates/default.html" indexCtx
57 >>= loadAndApplyTemplate "templates/default.html" indexCtx
58 >>= relativizeUrls
58 >>= relativizeUrls
59
59
60− match "templates/*" $ compile templateCompiler
60+ match "templates/*" $ compile templateBodyCompiler
61
61
62
62
63--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
data/example/templates/default.html+16−16
@@ -1,33 +1,33 @@
1−<?xml version="1.0" encoding="UTF-8"?>
1+<!doctype html>
2−<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2+<html lang="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>
3 <head>
6− <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
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>
7 <title>My Hakyll Blog - $title$</title>
8− <link rel="stylesheet" type="text/css" href="/css/default.css" />
8+ <link rel="stylesheet" href="/css/default.css" />
9 </head>
9 </head>
10 <body>
10 <body>
11− <div id="header">
11+ <header>
12− <div id="logo">
12+ <div class="logo">
13 <a href="/">My Hakyll Blog</a>
13 <a href="/">My Hakyll Blog</a>
14 </div>
14 </div>
15− <div id="navigation">
15+ <nav>
16 <a href="/">Home</a>
16 <a href="/">Home</a>
17 <a href="/about.html">About</a>
17 <a href="/about.html">About</a>
18 <a href="/contact.html">Contact</a>
18 <a href="/contact.html">Contact</a>
19 <a href="/archive.html">Archive</a>
19 <a href="/archive.html">Archive</a>
20− </div>
20+ </nav>
21− </div>
21+ </header>
22
22
23− <div id="content">
23+ <main role="main">
24 <h1>$title$</h1>
24 <h1>$title$</h1>
25−
26 $body$
25 $body$
27− </div>
26+ </main>
27+
28− <div id="footer">
28+ <footer>
29 Site proudly generated by
29 Site proudly generated by
30 <a href="http://jaspervdj.be/hakyll">Hakyll</a>
30 <a href="http://jaspervdj.be/hakyll">Hakyll</a>
31− </div>
31+ </footer>
32 </body>
32 </body>
33</html>
33</html>
data/example/templates/post.html+11−8
@@ -1,8 +1,11 @@
1−<div class="info">
1+<article>
2+ <section class="header">
2− Posted on $date$
3+ Posted on $date$
3− $if(author)$
4+ $if(author)$
4− by $author$
5+ by $author$
5− $endif$
6+ $endif$
6−</div>
7+ </section>
7−
8+ <section>
8−$body$
9+ $body$
10+ </section>
11+</article>
data/templates/rss-item.xml+1
@@ -4,4 +4,5 @@
4 <description><![CDATA[$description$]]></description>
4 <description><![CDATA[$description$]]></description>
5 <pubDate>$published$</pubDate>
5 <pubDate>$published$</pubDate>
6 <guid>$root$$url$</guid>
6 <guid>$root$$url$</guid>
7+ <dc:creator>$authorName$</dc:creator>
7</item>
8</item>
data/templates/rss.xml+3−2
@@ -1,5 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?>
1<?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/">
3 <channel>
4 <channel>
4 <title>$title$</title>
5 <title>$title$</title>
5 <link>$root$</link>
6 <link>$root$</link>
@@ -8,5 +9,5 @@
8 type="application/rss+xml" />
9 type="application/rss+xml" />
9 <lastBuildDate>$updated$</lastBuildDate>
10 <lastBuildDate>$updated$</lastBuildDate>
10 $body$
11 $body$
11− </channel>
12+ </channel>
12</rss>
13</rss>
hakyll.cabal+154−123
@@ -1,5 +1,5 @@
1Name: hakyll
1Name: hakyll
2−Version: 4.6.2.0
2+Version: 4.13.0.0
3
3
4Synopsis: A static website compiler library
4Synopsis: A static website compiler library
5Description:
5Description:
@@ -37,15 +37,15 @@ License: BSD3
37License-File: LICENSE
37License-File: LICENSE
38Category: Web
38Category: Web
39
39
40−Cabal-Version: >= 1.8
40+Cabal-Version: >= 1.10
41Build-Type: Simple
41Build-Type: Simple
42Data-Dir: data
42Data-Dir: data
43
43
44Data-files:
44Data-files:
45− example/posts/2012-11-28-carpe-diem.markdown
45+ example/posts/2015-11-28-carpe-diem.markdown
46− example/posts/2012-10-07-rosa-rosa-rosam.markdown
46+ example/posts/2015-10-07-rosa-rosa-rosam.markdown
47− example/posts/2012-12-07-tu-quoque.markdown
47+ example/posts/2015-12-07-tu-quoque.markdown
48− example/posts/2012-08-12-spqr.markdown
48+ example/posts/2015-08-12-spqr.markdown
49 example/site.hs
49 example/site.hs
50 example/images/haskell-logo.png
50 example/images/haskell-logo.png
51 example/templates/post-list.html
51 example/templates/post-list.html
@@ -56,19 +56,28 @@ Data-files:
56 example/index.html
56 example/index.html
57 example/about.rst
57 example/about.rst
58 example/contact.markdown
58 example/contact.markdown
59− templates/atom-item.xml
60− templates/atom.xml
61− templates/rss-item.xml
62− templates/rss.xml
63
59
64Extra-source-files:
60Extra-source-files:
61+ CHANGELOG.md
65 tests/data/example.md
62 tests/data/example.md
66 tests/data/example.md.metadata
63 tests/data/example.md.metadata
67 tests/data/images/favicon.ico
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
68 tests/data/posts/2010-08-26-birthday.md
70 tests/data/posts/2010-08-26-birthday.md
71+ tests/data/posts/2018-09-26.md
69 tests/data/russian.md
72 tests/data/russian.md
73+ tests/data/strip.html
74+ tests/data/strip.html.out
70 tests/data/template.html
75 tests/data/template.html
71 tests/data/template.html.out
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
72
81
73Source-Repository head
82Source-Repository head
74 Type: git
83 Type: git
@@ -86,24 +95,43 @@ Flag checkExternal
86 Description: Include external link checking
95 Description: Include external link checking
87 Default: True
96 Default: True
88
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+
89Library
108Library
90− Ghc-Options: -Wall
109+ Ghc-Options: -Wall
91− Ghc-Prof-Options: -auto-all -caf-all
92− Hs-Source-Dirs: src
110+ Hs-Source-Dirs: lib
111+ Default-language: Haskell2010
93
112
94 Exposed-Modules:
113 Exposed-Modules:
95 Hakyll
114 Hakyll
115+ Hakyll.Commands
96 Hakyll.Core.Compiler
116 Hakyll.Core.Compiler
117+ Hakyll.Core.Compiler.Internal
97 Hakyll.Core.Configuration
118 Hakyll.Core.Configuration
98 Hakyll.Core.Dependencies
119 Hakyll.Core.Dependencies
99 Hakyll.Core.File
120 Hakyll.Core.File
100 Hakyll.Core.Identifier
121 Hakyll.Core.Identifier
101 Hakyll.Core.Identifier.Pattern
122 Hakyll.Core.Identifier.Pattern
102 Hakyll.Core.Item
123 Hakyll.Core.Item
124+ Hakyll.Core.Logger
103 Hakyll.Core.Metadata
125 Hakyll.Core.Metadata
126+ Hakyll.Core.Provider
127+ Hakyll.Core.Provider.Metadata
104 Hakyll.Core.Routes
128 Hakyll.Core.Routes
105 Hakyll.Core.Rules
129 Hakyll.Core.Rules
130+ Hakyll.Core.Rules.Internal
131+ Hakyll.Core.Runtime
132+ Hakyll.Core.Store
106 Hakyll.Core.UnixFilter
133 Hakyll.Core.UnixFilter
134+ Hakyll.Core.Util.File
107 Hakyll.Core.Util.String
135 Hakyll.Core.Util.String
108 Hakyll.Core.Writable
136 Hakyll.Core.Writable
109 Hakyll.Main
137 Hakyll.Main
@@ -111,71 +139,67 @@ Library
111 Hakyll.Web.Feed
139 Hakyll.Web.Feed
112 Hakyll.Web.Html
140 Hakyll.Web.Html
113 Hakyll.Web.Html.RelativizeUrls
141 Hakyll.Web.Html.RelativizeUrls
114− Hakyll.Web.Pandoc
115− Hakyll.Web.Pandoc.Biblio
116− Hakyll.Web.Pandoc.FileType
117− Hakyll.Web.Tags
118 Hakyll.Web.Paginate
142 Hakyll.Web.Paginate
143+ Hakyll.Web.Redirect
144+ Hakyll.Web.Tags
119 Hakyll.Web.Template
145 Hakyll.Web.Template
120 Hakyll.Web.Template.Context
146 Hakyll.Web.Template.Context
147+ Hakyll.Web.Template.Internal
148+ Hakyll.Web.Template.Internal.Element
149+ Hakyll.Web.Template.Internal.Trim
121 Hakyll.Web.Template.List
150 Hakyll.Web.Template.List
122
151
123 Other-Modules:
152 Other-Modules:
153+ Data.List.Extended
154+ Data.Yaml.Extended
124 Hakyll.Check
155 Hakyll.Check
125− Hakyll.Commands
126− Hakyll.Core.Compiler.Internal
127 Hakyll.Core.Compiler.Require
156 Hakyll.Core.Compiler.Require
157+ Hakyll.Core.Identifier.Pattern.Internal
128 Hakyll.Core.Item.SomeItem
158 Hakyll.Core.Item.SomeItem
129− Hakyll.Core.Logger
130− Hakyll.Core.Provider
131 Hakyll.Core.Provider.Internal
159 Hakyll.Core.Provider.Internal
132− Hakyll.Core.Provider.Metadata
133 Hakyll.Core.Provider.MetadataCache
160 Hakyll.Core.Provider.MetadataCache
134− Hakyll.Core.Rules.Internal
135− Hakyll.Core.Runtime
136− Hakyll.Core.Store
137− Hakyll.Core.Util.File
138 Hakyll.Core.Util.Parser
161 Hakyll.Core.Util.Parser
139− Hakyll.Web.Template.Internal
140− Hakyll.Web.Pandoc.Binary
141 Paths_hakyll
162 Paths_hakyll
142
163
143 Build-Depends:
164 Build-Depends:
144− base >= 4 && < 5,
165+ base >= 4.8 && < 5,
145− binary >= 0.5 && < 0.8,
166+ binary >= 0.5 && < 0.10,
146− blaze-html >= 0.5 && < 0.8,
167+ blaze-html >= 0.5 && < 0.10,
147− blaze-markup >= 0.5.1 && < 0.7,
168+ blaze-markup >= 0.5.1 && < 0.9,
148− bytestring >= 0.9 && < 0.11,
169+ bytestring >= 0.9 && < 0.11,
149− cmdargs >= 0.10 && < 0.11,
150− containers >= 0.3 && < 0.6,
170+ containers >= 0.3 && < 0.7,
151− cryptohash >= 0.7 && < 0.12,
171+ cryptohash >= 0.7 && < 0.12,
152− data-default >= 0.4 && < 0.6,
172+ data-default >= 0.4 && < 0.8,
153− deepseq >= 1.3 && < 1.4,
173+ deepseq >= 1.3 && < 1.5,
154− directory >= 1.0 && < 1.3,
174+ directory >= 1.0 && < 1.4,
175+ file-embed >= 0.0.10.1 && < 0.0.12,
155− filepath >= 1.0 && < 1.4,
176+ filepath >= 1.0 && < 1.5,
156− lrucache >= 1.1.1 && < 1.2,
177+ lrucache >= 1.1.1 && < 1.3,
157− mtl >= 1 && < 2.3,
178+ mtl >= 1 && < 2.3,
158− network >= 2.6 && < 2.7,
159− network-uri >= 2.6 && < 2.7,
179+ network-uri >= 2.6 && < 2.7,
160− old-locale >= 1.0 && < 1.1,
180+ optparse-applicative >= 0.12 && < 0.15,
161− old-time >= 1.0 && < 1.2,
162− pandoc >= 1.12.4 && < 1.14,
163− pandoc-citeproc >= 0.4 && < 0.7,
164− parsec >= 3.0 && < 3.2,
181+ parsec >= 3.0 && < 3.2,
165− process >= 1.0 && < 1.3,
182+ process >= 1.6 && < 1.7,
166− random >= 1.0 && < 1.2,
183+ random >= 1.0 && < 1.2,
167− regex-base >= 0.93 && < 0.94,
168− regex-tdfa >= 1.1 && < 1.3,
184+ regex-tdfa >= 1.1 && < 1.3,
185+ resourcet >= 1.1 && < 1.3,
186+ scientific >= 0.3.4 && < 0.4,
169− tagsoup >= 0.13.1 && < 0.14,
187+ tagsoup >= 0.13.1 && < 0.15,
188+ template-haskell >= 2.14 && < 2.15,
170− text >= 0.11 && < 1.3,
189+ text >= 0.11 && < 1.3,
171− time >= 1.1 && < 1.5
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
172
195
173 If flag(previewServer)
196 If flag(previewServer)
174 Build-depends:
197 Build-depends:
175− snap-core >= 0.6 && < 0.10,
198+ wai >= 3.2 && < 3.3,
176− snap-server >= 0.6 && < 0.10,
199+ warp >= 3.2 && < 3.3,
200+ wai-app-static >= 3.1 && < 3.2,
201+ http-types >= 0.9 && < 0.13,
177− fsnotify >= 0.1 && < 0.2,
202+ fsnotify >= 0.2 && < 0.4
178− system-filepath >= 0.4.6 && <= 0.5
179 Cpp-options:
203 Cpp-options:
180 -DPREVIEW_SERVER
204 -DPREVIEW_SERVER
181 Other-modules:
205 Other-modules:
@@ -184,8 +208,7 @@ Library
184
208
185 If flag(watchServer)
209 If flag(watchServer)
186 Build-depends:
210 Build-depends:
187− fsnotify >= 0.1 && < 0.2,
211+ fsnotify >= 0.2 && < 0.4
188− system-filepath >= 0.4.6 && <= 0.5
189 Cpp-options:
212 Cpp-options:
190 -DWATCH_SERVER
213 -DWATCH_SERVER
191 Other-modules:
214 Other-modules:
@@ -193,16 +216,31 @@ Library
193
216
194 If flag(checkExternal)
217 If flag(checkExternal)
195 Build-depends:
218 Build-depends:
196− http-conduit >= 2.1 && < 2.2,
219+ http-conduit >= 2.2 && < 2.4,
197− http-types >= 0.7 && < 0.9
220+ http-types >= 0.7 && < 0.13
198 Cpp-options:
221 Cpp-options:
199 -DCHECK_EXTERNAL
222 -DCHECK_EXTERNAL
200
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+
201Test-suite hakyll-tests
237Test-suite hakyll-tests
202− Type: exitcode-stdio-1.0
238+ Type: exitcode-stdio-1.0
203− Hs-source-dirs: src tests
239+ Hs-source-dirs: tests
204− Main-is: TestSuite.hs
240+ Main-is: TestSuite.hs
205− Ghc-options: -Wall
241+ Ghc-options: -Wall -threaded
242+ Default-language: Haskell2010
243+
206 Other-modules:
244 Other-modules:
207 Hakyll.Core.Dependencies.Tests
245 Hakyll.Core.Dependencies.Tests
208 Hakyll.Core.Identifier.Tests
246 Hakyll.Core.Identifier.Tests
@@ -214,83 +252,76 @@ Test-suite hakyll-tests
214 Hakyll.Core.Store.Tests
252 Hakyll.Core.Store.Tests
215 Hakyll.Core.UnixFilter.Tests
253 Hakyll.Core.UnixFilter.Tests
216 Hakyll.Core.Util.String.Tests
254 Hakyll.Core.Util.String.Tests
255+ Hakyll.Web.CompressCss.Tests
217 Hakyll.Web.Html.RelativizeUrls.Tests
256 Hakyll.Web.Html.RelativizeUrls.Tests
218 Hakyll.Web.Html.Tests
257 Hakyll.Web.Html.Tests
219− Hakyll.Web.Pandoc.FileType.Tests
258+ Hakyll.Web.Tags.Tests
220 Hakyll.Web.Template.Context.Tests
259 Hakyll.Web.Template.Context.Tests
221 Hakyll.Web.Template.Tests
260 Hakyll.Web.Template.Tests
222 TestSuite.Util
261 TestSuite.Util
223
262
224 Build-Depends:
263 Build-Depends:
225− HUnit >= 1.2 && < 1.3,
264+ hakyll,
226− QuickCheck >= 2.4 && < 2.8,
265+ QuickCheck >= 2.8 && < 2.14,
227− test-framework >= 0.4 && < 0.9,
266+ tasty >= 0.11 && < 1.3,
228− test-framework-hunit >= 0.3 && < 0.4,
267+ tasty-hunit >= 0.9 && < 0.11,
229− test-framework-quickcheck2 >= 0.3 && < 0.4,
268+ tasty-quickcheck >= 0.8 && < 0.11,
230 -- Copy pasted from hakyll dependencies:
269 -- Copy pasted from hakyll dependencies:
231− base >= 4 && < 5,
270+ base >= 4.8 && < 5,
232− binary >= 0.5 && < 0.8,
233− blaze-html >= 0.5 && < 0.8,
234− blaze-markup >= 0.5.1 && < 0.7,
235− bytestring >= 0.9 && < 0.11,
271+ bytestring >= 0.9 && < 0.11,
236− cmdargs >= 0.10 && < 0.11,
237− containers >= 0.3 && < 0.6,
272+ containers >= 0.3 && < 0.7,
238− cryptohash >= 0.7 && < 0.12,
239− data-default >= 0.4 && < 0.6,
240− deepseq >= 1.3 && < 1.4,
241− directory >= 1.0 && < 1.3,
242− filepath >= 1.0 && < 1.4,
273+ filepath >= 1.0 && < 1.5,
243− lrucache >= 1.1.1 && < 1.2,
274+ text >= 0.11 && < 1.3,
244− mtl >= 1 && < 2.3,
275+ unordered-containers >= 0.2 && < 0.3,
245− network >= 2.6 && < 2.7,
276+ yaml >= 0.8.11 && < 0.12
246− network-uri >= 2.6 && < 2.7,
247− old-locale >= 1.0 && < 1.1,
248− old-time >= 1.0 && < 1.2,
249− pandoc >= 1.12.4 && < 1.14,
250− pandoc-citeproc >= 0.4 && < 0.7,
251− parsec >= 3.0 && < 3.2,
252− process >= 1.0 && < 1.3,
253− random >= 1.0 && < 1.2,
254− regex-base >= 0.93 && < 0.94,
255− regex-tdfa >= 1.1 && < 1.3,
256− tagsoup >= 0.13.1 && < 0.14,
257− text >= 0.11 && < 1.3,
258− time >= 1.1 && < 1.5
259
277
260 If flag(previewServer)
278 If flag(previewServer)
261− Build-depends:
262− snap-core >= 0.6 && < 0.10,
263− snap-server >= 0.6 && < 0.10,
264− fsnotify >= 0.1 && < 0.2,
265− system-filepath >= 0.4.6 && <= 0.5
266 Cpp-options:
279 Cpp-options:
267 -DPREVIEW_SERVER
280 -DPREVIEW_SERVER
268− Other-modules:
269− Hakyll.Preview.Poll
270− Hakyll.Preview.Server
271
281
272 If flag(watchServer)
282 If flag(watchServer)
273− Build-depends:
274− fsnotify >= 0.1 && < 0.2,
275− system-filepath >= 0.4.6 && <= 0.5
276 Cpp-options:
283 Cpp-options:
277 -DWATCH_SERVER
284 -DWATCH_SERVER
278− Other-modules:
279− Hakyll.Preview.Poll
280
285
281 If flag(checkExternal)
286 If flag(checkExternal)
282− Build-depends:
283− http-conduit >= 2.1 && < 2.2,
284− http-types >= 0.7 && < 0.9
285 Cpp-options:
287 Cpp-options:
286 -DCHECK_EXTERNAL
288 -DCHECK_EXTERNAL
287
289
290+ If flag(usePandoc)
291+ Other-modules:
292+ Hakyll.Web.Pandoc.FileType.Tests
293+ Cpp-options:
294+ -DUSE_PANDOC
295+
288Executable hakyll-init
296Executable hakyll-init
297+ Main-is: Init.hs
289− Ghc-options: -Wall
298+ Ghc-options: -Wall -threaded
290− Hs-source-dirs: src
299+ Hs-source-dirs: src
291− Main-is: Hakyll/Init.hs
300+ Default-language: Haskell2010
301+
302+ Other-modules:
303+ Paths_hakyll
292
304
293 Build-depends:
305 Build-depends:
306+ hakyll,
294 base >= 4 && < 5,
307 base >= 4 && < 5,
295− directory >= 1.0 && < 1.3,
308+ directory >= 1.0 && < 1.4,
296− filepath >= 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
src/Hakyll.hs → lib/Hakyll.hsrenamed+7−1
@@ -20,11 +20,14 @@ module Hakyll
20 , module Hakyll.Web.Feed
20 , module Hakyll.Web.Feed
21 , module Hakyll.Web.Html
21 , module Hakyll.Web.Html
22 , module Hakyll.Web.Html.RelativizeUrls
22 , module Hakyll.Web.Html.RelativizeUrls
23+ , module Hakyll.Web.Paginate
24+#ifdef USE_PANDOC
23 , module Hakyll.Web.Pandoc
25 , module Hakyll.Web.Pandoc
24 , module Hakyll.Web.Pandoc.Biblio
26 , module Hakyll.Web.Pandoc.Biblio
25 , module Hakyll.Web.Pandoc.FileType
27 , module Hakyll.Web.Pandoc.FileType
28+#endif
29+ , module Hakyll.Web.Redirect
26 , module Hakyll.Web.Tags
30 , module Hakyll.Web.Tags
27− , module Hakyll.Web.Paginate
28 , module Hakyll.Web.Template
31 , module Hakyll.Web.Template
29 , module Hakyll.Web.Template.Context
32 , module Hakyll.Web.Template.Context
30 , module Hakyll.Web.Template.List
33 , module Hakyll.Web.Template.List
@@ -51,9 +54,12 @@ import Hakyll.Web.Feed
51import Hakyll.Web.Html
54import Hakyll.Web.Html
52import Hakyll.Web.Html.RelativizeUrls
55import Hakyll.Web.Html.RelativizeUrls
53import Hakyll.Web.Paginate
56import Hakyll.Web.Paginate
57+#ifdef USE_PANDOC
54import Hakyll.Web.Pandoc
58import Hakyll.Web.Pandoc
55import Hakyll.Web.Pandoc.Biblio
59import Hakyll.Web.Pandoc.Biblio
56import Hakyll.Web.Pandoc.FileType
60import Hakyll.Web.Pandoc.FileType
61+#endif
62+import Hakyll.Web.Redirect
57import Hakyll.Web.Tags
63import Hakyll.Web.Tags
58import Hakyll.Web.Template
64import Hakyll.Web.Template
59import Hakyll.Web.Template.Context
65import Hakyll.Web.Template.Context
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 ['?', '#'])
src/Hakyll/Commands.hs → lib/Hakyll/Commands.hsrenamed+36−31
@@ -2,7 +2,8 @@
2-- | Implementation of Hakyll commands: build, preview...
2-- | Implementation of Hakyll commands: build, preview...
3{-# LANGUAGE CPP #-}
3{-# LANGUAGE CPP #-}
4module Hakyll.Commands
4module Hakyll.Commands
5+ ( Check(..)
5− ( build
6+ , build
6 , check
7 , check
7 , clean
8 , clean
8 , preview
9 , preview
@@ -14,62 +15,69 @@ module Hakyll.Commands
14
15
15
16
16--------------------------------------------------------------------------------
17--------------------------------------------------------------------------------
17−import System.Exit (exitWith, ExitCode)
18−import System.IO.Error (catchIOError)
19−import Control.Applicative
20−import Control.Monad (void)
21import Control.Concurrent
18import Control.Concurrent
19+import System.Exit (ExitCode)
20+
22
21
23--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23+import Hakyll.Check (Check(..))
24import qualified Hakyll.Check as Check
24import qualified Hakyll.Check as Check
25import Hakyll.Core.Configuration
25import Hakyll.Core.Configuration
26−import Hakyll.Core.Logger (Verbosity)
26+import Hakyll.Core.Logger (Logger)
27+import qualified Hakyll.Core.Logger as Logger
27import Hakyll.Core.Rules
28import Hakyll.Core.Rules
28import Hakyll.Core.Rules.Internal
29import Hakyll.Core.Rules.Internal
29import Hakyll.Core.Runtime
30import Hakyll.Core.Runtime
30import Hakyll.Core.Util.File
31import Hakyll.Core.Util.File
31
32
33+
32--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
33#ifdef WATCH_SERVER
35#ifdef WATCH_SERVER
34−import Hakyll.Preview.Poll (watchUpdates)
36+import Hakyll.Preview.Poll (watchUpdates)
35#endif
37#endif
36
38
37#ifdef PREVIEW_SERVER
39#ifdef PREVIEW_SERVER
38import Hakyll.Preview.Server
40import Hakyll.Preview.Server
39#endif
41#endif
40
42
43+#ifdef mingw32_HOST_OS
44+import Control.Monad (void)
45+import System.IO.Error (catchIOError)
46+#endif
47+
41
48
42--------------------------------------------------------------------------------
49--------------------------------------------------------------------------------
43-- | Build the site
50-- | Build the site
44−build :: Configuration -> Verbosity -> Rules a -> IO ExitCode
51+build :: Configuration -> Logger -> Rules a -> IO ExitCode
45−build conf verbosity rules = fst <$> run conf verbosity rules
52+build conf logger rules = fst <$> run conf logger rules
53+
46
54
47--------------------------------------------------------------------------------
55--------------------------------------------------------------------------------
48-- | Run the checker and exit
56-- | Run the checker and exit
49−check :: Configuration -> Verbosity -> Check.Check -> IO ()
57+check :: Configuration -> Logger -> Check.Check -> IO ExitCode
50−check config verbosity check' = Check.check config verbosity check' >>= exitWith
58+check = Check.check
51
59
52
60
53--------------------------------------------------------------------------------
61--------------------------------------------------------------------------------
54-- | Remove the output directories
62-- | Remove the output directories
55−clean :: Configuration -> IO ()
63+clean :: Configuration -> Logger -> IO ()
56−clean conf = do
64+clean conf logger = do
57 remove $ destinationDirectory conf
65 remove $ destinationDirectory conf
58 remove $ storeDirectory conf
66 remove $ storeDirectory conf
59 remove $ tmpDirectory conf
67 remove $ tmpDirectory conf
60 where
68 where
61 remove dir = do
69 remove dir = do
62− putStrLn $ "Removing " ++ dir ++ "..."
70+ Logger.header logger $ "Removing " ++ dir ++ "..."
63 removeDirectory dir
71 removeDirectory dir
64
72
65
73
66--------------------------------------------------------------------------------
74--------------------------------------------------------------------------------
67-- | Preview the site
75-- | Preview the site
68−preview :: Configuration -> Verbosity -> Rules a -> Int -> IO ()
76+preview :: Configuration -> Logger -> Rules a -> Int -> IO ()
69#ifdef PREVIEW_SERVER
77#ifdef PREVIEW_SERVER
70−preview conf verbosity rules port = do
78+preview conf logger rules port = do
71 deprecatedMessage
79 deprecatedMessage
72− watch conf verbosity "0.0.0.0" port True rules
80+ watch conf logger "0.0.0.0" port True rules
73 where
81 where
74 deprecatedMessage = mapM_ putStrLn [ "The preview command has been deprecated."
82 deprecatedMessage = mapM_ putStrLn [ "The preview command has been deprecated."
75 , "Use the watch command for recompilation and serving."
83 , "Use the watch command for recompilation and serving."
@@ -82,9 +90,9 @@ preview _ _ _ _ = previewServerDisabled
82--------------------------------------------------------------------------------
90--------------------------------------------------------------------------------
83-- | Watch and recompile for changes
91-- | Watch and recompile for changes
84
92
85−watch :: Configuration -> Verbosity -> String -> Int -> Bool -> Rules a -> IO ()
93+watch :: Configuration -> Logger -> String -> Int -> Bool -> Rules a -> IO ()
86#ifdef WATCH_SERVER
94#ifdef WATCH_SERVER
87−watch conf verbosity host port runServer rules = do
95+watch conf logger host port runServer rules = do
88#ifndef mingw32_HOST_OS
96#ifndef mingw32_HOST_OS
89 _ <- forkIO $ watchUpdates conf update
97 _ <- forkIO $ watchUpdates conf update
90#else
98#else
@@ -97,31 +105,29 @@ watch conf verbosity host port runServer rules = do
97 server'
105 server'
98 where
106 where
99 update = do
107 update = do
100− (_, ruleSet) <- run conf verbosity rules
108+ (_, ruleSet) <- run conf logger rules
101 return $ rulesPattern ruleSet
109 return $ rulesPattern ruleSet
102 loop = threadDelay 100000 >> loop
110 loop = threadDelay 100000 >> loop
103− server' = if runServer then server conf host port else loop
111+ server' = if runServer then server conf logger host port else loop
104#else
112#else
105watch _ _ _ _ _ _ = watchServerDisabled
113watch _ _ _ _ _ _ = watchServerDisabled
106#endif
114#endif
107
115
108--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
109-- | Rebuild the site
117-- | Rebuild the site
110−rebuild :: Configuration -> Verbosity -> Rules a -> IO ExitCode
118+rebuild :: Configuration -> Logger -> Rules a -> IO ExitCode
111−rebuild conf verbosity rules =
119+rebuild conf logger rules =
112− clean conf >> build conf verbosity rules
120+ clean conf logger >> build conf logger rules
113
121
114--------------------------------------------------------------------------------
122--------------------------------------------------------------------------------
115-- | Start a server
123-- | Start a server
116−server :: Configuration -> String -> Int -> IO ()
124+server :: Configuration -> Logger -> String -> Int -> IO ()
117#ifdef PREVIEW_SERVER
125#ifdef PREVIEW_SERVER
118−server conf host port = do
126+server conf logger host port = do
119 let destination = destinationDirectory conf
127 let destination = destinationDirectory conf
120− staticServer destination preServeHook host port
128+ staticServer logger destination host port
121− where
122− preServeHook _ = return ()
123#else
129#else
124−server _ _ _ = previewServerDisabled
130+server _ _ _ _ = previewServerDisabled
125#endif
131#endif
126
132
127
133
@@ -156,4 +162,3 @@ watchServerDisabled =
156 , "Alternatively, use an external tool to serve your site directory."
162 , "Alternatively, use an external tool to serve your site directory."
157 ]
163 ]
158#endif
164#endif
159−
src/Hakyll/Core/Compiler.hs → lib/Hakyll/Core/Compiler.hsrenamed+48−6
@@ -24,14 +24,16 @@ module Hakyll.Core.Compiler
24 , cached
24 , cached
25 , unsafeCompiler
25 , unsafeCompiler
26 , debugCompiler
26 , debugCompiler
27+ , noResult
28+ , withErrorMessage
27 ) where
29 ) where
28
30
29
31
30--------------------------------------------------------------------------------
32--------------------------------------------------------------------------------
31−import Control.Applicative ((<$>))
33+import Control.Monad (unless, when, (>=>))
32−import Control.Monad (when)
33import Data.Binary (Binary)
34import Data.Binary (Binary)
34import Data.ByteString.Lazy (ByteString)
35import Data.ByteString.Lazy (ByteString)
36+import qualified Data.List.NonEmpty as NonEmpty
35import Data.Typeable (Typeable)
37import Data.Typeable (Typeable)
36import System.Environment (getProgName)
38import System.Environment (getProgName)
37import System.FilePath (takeExtension)
39import System.FilePath (takeExtension)
@@ -63,6 +65,7 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
63
65
64
66
65--------------------------------------------------------------------------------
67--------------------------------------------------------------------------------
68+-- | Create an item from the underlying identifier and a given value.
66makeItem :: a -> Compiler (Item a)
69makeItem :: a -> Compiler (Item a)
67makeItem x = do
70makeItem x = do
68 identifier <- getUnderlying
71 identifier <- getUnderlying
@@ -83,19 +86,20 @@ getRoute identifier = do
83
86
84
87
85--------------------------------------------------------------------------------
88--------------------------------------------------------------------------------
86−-- | Get the body of the underlying resource
89+-- | Get the full contents of the matched source file as a string,
90+-- but without metadata preamble, if there was one.
87getResourceBody :: Compiler (Item String)
91getResourceBody :: Compiler (Item String)
88getResourceBody = getResourceWith resourceBody
92getResourceBody = getResourceWith resourceBody
89
93
90
94
91--------------------------------------------------------------------------------
95--------------------------------------------------------------------------------
92−-- | Get the resource we are compiling as a string
96+-- | Get the full contents of the matched source file as a string.
93getResourceString :: Compiler (Item String)
97getResourceString :: Compiler (Item String)
94getResourceString = getResourceWith resourceString
98getResourceString = getResourceWith resourceString
95
99
96
100
97--------------------------------------------------------------------------------
101--------------------------------------------------------------------------------
98−-- | Get the resource we are compiling as a lazy bytestring
102+-- | Get the full contents of the matched source file as a lazy bytestring.
99getResourceLBS :: Compiler (Item ByteString)
103getResourceLBS :: Compiler (Item ByteString)
100getResourceLBS = getResourceWith resourceLBS
104getResourceLBS = getResourceWith resourceLBS
101
105
@@ -141,6 +145,10 @@ saveSnapshot snapshot item = do
141
145
142
146
143--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
148+-- | Turn on caching for a compilation value to avoid recomputing it
149+-- on subsequent Hakyll runs.
150+-- The storage key consists of the underlying identifier of the compiled
151+-- ressource and the given name.
144cached :: (Binary a, Typeable a)
152cached :: (Binary a, Typeable a)
145 => String
153 => String
146 -> Compiler a
154 -> Compiler a
@@ -149,6 +157,10 @@ cached name compiler = do
149 id' <- compilerUnderlying <$> compilerAsk
157 id' <- compilerUnderlying <$> compilerAsk
150 store <- compilerStore <$> compilerAsk
158 store <- compilerStore <$> compilerAsk
151 provider <- compilerProvider <$> 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+
152 let modified = resourceModified provider id'
164 let modified = resourceModified provider id'
153 if modified
165 if modified
154 then do
166 then do
@@ -166,14 +178,44 @@ cached name compiler = do
166 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
178 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
167 "Try running: " ++ progName ++ " clean"
179 "Try running: " ++ progName ++ " clean"
168
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+
169
186
170--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
188+-- | Run an IO computation without dependencies in a Compiler
171unsafeCompiler :: IO a -> Compiler a
189unsafeCompiler :: IO a -> Compiler a
172unsafeCompiler = compilerUnsafeIO
190unsafeCompiler = compilerUnsafeIO
173
191
174
192
175--------------------------------------------------------------------------------
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+--------------------------------------------------------------------------------
176−-- | Compiler for debugging purposes
217+-- | Compiler for debugging purposes.
218+-- Passes a message to the debug logger that is printed in verbose mode.
177debugCompiler :: String -> Compiler ()
219debugCompiler :: String -> Compiler ()
178debugCompiler msg = do
220debugCompiler msg = do
179 logger <- compilerLogger <$> compilerAsk
221 logger <- compilerLogger <$> compilerAsk
src/Hakyll/Core/Compiler/Internal.hs → lib/Hakyll/Core/Compiler/Internal.hsrenamed+144−45
@@ -1,5 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Internally used compiler module
2-- | Internally used compiler module
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -9,31 +11,41 @@ module Hakyll.Core.Compiler.Internal
9 Snapshot
11 Snapshot
10 , CompilerRead (..)
12 , CompilerRead (..)
11 , CompilerWrite (..)
13 , CompilerWrite (..)
14+ , CompilerErrors (..)
12 , CompilerResult (..)
15 , CompilerResult (..)
13 , Compiler (..)
16 , Compiler (..)
14 , runCompiler
17 , runCompiler
15
18
16 -- * Core operations
19 -- * Core operations
20+ , compilerResult
17 , compilerTell
21 , compilerTell
18 , compilerAsk
22 , compilerAsk
23+ , compilerUnsafeIO
24+
25+ -- * Error operations
19 , compilerThrow
26 , compilerThrow
27+ , compilerNoResult
20 , compilerCatch
28 , compilerCatch
21− , compilerResult
29+ , compilerTry
22− , compilerUnsafeIO
30+ , compilerErrorMessages
23
31
24 -- * Utilities
32 -- * Utilities
33+ , compilerDebugEntries
25 , compilerTellDependencies
34 , compilerTellDependencies
26 , compilerTellCacheHits
35 , compilerTellCacheHits
27 ) where
36 ) where
28
37
29
38
30--------------------------------------------------------------------------------
39--------------------------------------------------------------------------------
31−import Control.Applicative (Alternative (..),
40+import Control.Applicative (Alternative (..))
32− Applicative (..), (<$>))
33import Control.Exception (SomeException, handle)
41import Control.Exception (SomeException, handle)
34import Control.Monad (forM_)
42import Control.Monad (forM_)
35−import Control.Monad.Error (MonadError (..))
43+import Control.Monad.Except (MonadError (..))
36−import Data.Monoid (Monoid (..))
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
37import Data.Set (Set)
49import Data.Set (Set)
38import qualified Data.Set as S
50import qualified Data.Set as S
39
51
@@ -43,7 +55,6 @@ import Hakyll.Core.Configuration
43import Hakyll.Core.Dependencies
55import Hakyll.Core.Dependencies
44import Hakyll.Core.Identifier
56import Hakyll.Core.Identifier
45import Hakyll.Core.Identifier.Pattern
57import Hakyll.Core.Identifier.Pattern
46−import Hakyll.Core.Logger (Logger)
47import qualified Hakyll.Core.Logger as Logger
58import qualified Hakyll.Core.Logger as Logger
48import Hakyll.Core.Metadata
59import Hakyll.Core.Metadata
49import Hakyll.Core.Provider
60import Hakyll.Core.Provider
@@ -73,7 +84,7 @@ data CompilerRead = CompilerRead
73 , -- | Compiler store
84 , -- | Compiler store
74 compilerStore :: Store
85 compilerStore :: Store
75 , -- | Logger
86 , -- | Logger
76− compilerLogger :: Logger
87+ compilerLogger :: Logger.Logger
77 }
88 }
78
89
79
90
@@ -85,18 +96,46 @@ data CompilerWrite = CompilerWrite
85
96
86
97
87--------------------------------------------------------------------------------
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
88instance Monoid CompilerWrite where
108instance Monoid CompilerWrite where
89 mempty = CompilerWrite [] 0
109 mempty = CompilerWrite [] 0
90 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
110 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
91 CompilerWrite (d1 ++ d2) (h1 + h2)
111 CompilerWrite (d1 ++ d2) (h1 + h2)
112+#endif
92
113
93
114
94--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
95−data CompilerResult a where
116+-- | Distinguishes reasons in a 'CompilerError'
117+data CompilerErrors a
118+ -- | One or more exceptions occured during compilation
119+ = CompilationFailure (NonEmpty a)
120+ -- | Absence of any result, most notably in template contexts. May still
121+ -- have error messages.
122+ | CompilationNoResult [a]
123+ deriving Functor
124+
125+
126+-- | Unwrap a `CompilerErrors`
127+compilerErrorMessages :: CompilerErrors a -> [a]
128+compilerErrorMessages (CompilationFailure x) = NonEmpty.toList x
129+compilerErrorMessages (CompilationNoResult x) = x
130+
131+
132+--------------------------------------------------------------------------------
133+-- | An intermediate result of a compilation step
134+data CompilerResult a
96− CompilerDone :: a -> CompilerWrite -> CompilerResult a
135+ = CompilerDone a CompilerWrite
97− CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
136+ | CompilerSnapshot Snapshot (Compiler a)
98− CompilerError :: [String] -> CompilerResult a
99− CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
100
139
101
140
102--------------------------------------------------------------------------------
141--------------------------------------------------------------------------------
@@ -114,14 +153,14 @@ instance Functor Compiler where
114 return $ case res of
153 return $ case res of
115 CompilerDone x w -> CompilerDone (f x) w
154 CompilerDone x w -> CompilerDone (f x) w
116 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
155 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
117− CompilerError e -> CompilerError e
118 CompilerRequire i c' -> CompilerRequire i (fmap f c')
156 CompilerRequire i c' -> CompilerRequire i (fmap f c')
157+ CompilerError e -> CompilerError e
119 {-# INLINE fmap #-}
158 {-# INLINE fmap #-}
120
159
121
160
122--------------------------------------------------------------------------------
161--------------------------------------------------------------------------------
123instance Monad Compiler where
162instance Monad Compiler where
124− return x = Compiler $ \_ -> return $ CompilerDone x mempty
163+ return x = compilerResult $ CompilerDone x mempty
125 {-# INLINE return #-}
164 {-# INLINE return #-}
126
165
127 Compiler c >>= f = Compiler $ \r -> do
166 Compiler c >>= f = Compiler $ \r -> do
@@ -131,15 +170,17 @@ instance Monad Compiler where
131 res' <- unCompiler (f x) r
170 res' <- unCompiler (f x) r
132 return $ case res' of
171 return $ case res' of
133 CompilerDone y w' -> CompilerDone y (w `mappend` w')
172 CompilerDone y w' -> CompilerDone y (w `mappend` w')
134− CompilerSnapshot s c' -> CompilerSnapshot s c'
173+ CompilerSnapshot s c' -> CompilerSnapshot s $ do
135− CompilerError e -> CompilerError e
174+ compilerTell w -- Save dependencies!
175+ c'
136 CompilerRequire i c' -> CompilerRequire i $ do
176 CompilerRequire i c' -> CompilerRequire i $ do
137 compilerTell w -- Save dependencies!
177 compilerTell w -- Save dependencies!
138 c'
178 c'
179+ CompilerError e -> CompilerError e
139
180
140 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
181 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
141− CompilerError e -> return $ CompilerError e
142 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
182 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
143 {-# INLINE (>>=) #-}
184 {-# INLINE (>>=) #-}
144
185
145 fail = compilerThrow . return
186 fail = compilerThrow . return
@@ -156,87 +197,145 @@ instance Applicative Compiler where
156
197
157
198
158--------------------------------------------------------------------------------
199--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
159instance MonadMetadata Compiler where
201instance MonadMetadata Compiler where
160 getMetadata = compilerGetMetadata
202 getMetadata = compilerGetMetadata
161 getMatches = compilerGetMatches
203 getMatches = compilerGetMatches
162
204
163
205
164--------------------------------------------------------------------------------
206--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
165instance MonadError [String] Compiler where
209instance MonadError [String] Compiler where
166− throwError = compilerThrow
210+ throwError = compilerThrow
167− catchError = compilerCatch
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
168
212
169
213
170--------------------------------------------------------------------------------
214--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
171runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
216runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
172runCompiler compiler read' = handle handler $ unCompiler compiler read'
217runCompiler compiler read' = handle handler $ unCompiler compiler read'
173 where
218 where
174 handler :: SomeException -> IO (CompilerResult a)
219 handler :: SomeException -> IO (CompilerResult a)
175− handler e = return $ CompilerError [show e]
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
176
221
177
222
178--------------------------------------------------------------------------------
223--------------------------------------------------------------------------------
224+-- | Trying alternative compilers if the first fails, regardless whether through
225+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226+-- Aggregates error messages if all fail.
179instance Alternative Compiler where
227instance Alternative Compiler where
180− empty = compilerThrow []
228+ empty = compilerNoResult []
181− x <|> y = compilerCatch x $ \es -> do
229+ x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
182− logger <- compilerLogger <$> compilerAsk
230+ case (rx, ry) of
183− forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
231+ (CompilationFailure xs, CompilationFailure ys) ->
232+ compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys
233+ (CompilationFailure xs, CompilationNoResult ys) ->
234+ debug ys >> compilerThrow (NonEmpty.toList xs)
235+ (CompilationNoResult xs, CompilationFailure ys) ->
236+ debug xs >> compilerThrow (NonEmpty.toList ys)
237+ (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys
238+ ))
239+ where
184− "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
240+ debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
185− y
186 {-# INLINE (<|>) #-}
241 {-# INLINE (<|>) #-}
187
242
188
243
189--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
245+-- | Put the result back in a compiler
246+compilerResult :: CompilerResult a -> Compiler a
247+compilerResult x = Compiler $ \_ -> return x
248+{-# INLINE compilerResult #-}
249+
250+
251+--------------------------------------------------------------------------------
252+-- | Get the current environment
190compilerAsk :: Compiler CompilerRead
253compilerAsk :: Compiler CompilerRead
191compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
254compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
192{-# INLINE compilerAsk #-}
255{-# INLINE compilerAsk #-}
193
256
194
257
195--------------------------------------------------------------------------------
258--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
196compilerTell :: CompilerWrite -> Compiler ()
260compilerTell :: CompilerWrite -> Compiler ()
197−compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
261+compilerTell = compilerResult . CompilerDone ()
198{-# INLINE compilerTell #-}
262{-# INLINE compilerTell #-}
199
263
200
264
201--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
266+-- | Run an IO computation without dependencies in a Compiler
267+compilerUnsafeIO :: IO a -> Compiler a
268+compilerUnsafeIO io = Compiler $ \_ -> do
269+ x <- io
270+ return $ CompilerDone x mempty
271+{-# INLINE compilerUnsafeIO #-}
272+
273+
274+--------------------------------------------------------------------------------
275+-- | Throw errors in the 'Compiler'.
276+--
277+-- If no messages are given, this is considered a 'CompilationNoResult' error.
278+-- Otherwise, it is treated as a proper compilation failure.
202compilerThrow :: [String] -> Compiler a
279compilerThrow :: [String] -> Compiler a
203−compilerThrow es = Compiler $ \_ -> return $ CompilerError es
280+compilerThrow = compilerResult . CompilerError .
204−{-# INLINE compilerThrow #-}
281+ maybe (CompilationNoResult []) CompilationFailure .
282+ NonEmpty.nonEmpty
283+
284+-- | Put a 'CompilerError' with multiple messages as 'CompilationNoResult'
285+compilerNoResult :: [String] -> Compiler a
286+compilerNoResult = compilerResult . CompilerError . CompilationNoResult
205
287
206
288
207--------------------------------------------------------------------------------
289--------------------------------------------------------------------------------
208−compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
290+-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'
291+--
292+-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right
293+compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
294+compilerTry (Compiler x) = Compiler $ \r -> do
295+ res <- x r
296+ case res of
297+ CompilerDone res' w -> return (CompilerDone (Right res') w)
298+ CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))
299+ CompilerRequire i c -> return (CompilerRequire i (compilerTry c))
300+ CompilerError e -> return (CompilerDone (Left e) mempty)
301+{-# INLINE compilerTry #-}
302+
303+
304+--------------------------------------------------------------------------------
305+-- | Allows you to recover from 'CompilerError's.
306+-- Uses the same parameter order as 'catchError' so that it can be used infix.
307+--
308+-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
209compilerCatch (Compiler x) f = Compiler $ \r -> do
310compilerCatch (Compiler x) f = Compiler $ \r -> do
210 res <- x r
311 res <- x r
211 case res of
312 case res of
212 CompilerDone res' w -> return (CompilerDone res' w)
313 CompilerDone res' w -> return (CompilerDone res' w)
213 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
314 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
214− CompilerError e -> unCompiler (f e) r
215 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
315 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316+ CompilerError e -> unCompiler (f e) r
216{-# INLINE compilerCatch #-}
317{-# INLINE compilerCatch #-}
217
318
218
319
219--------------------------------------------------------------------------------
320--------------------------------------------------------------------------------
220−-- | Put the result back in a compiler
321+compilerDebugLog :: [String] -> Compiler ()
221−compilerResult :: CompilerResult a -> Compiler a
322+compilerDebugLog ms = do
222−compilerResult x = Compiler $ \_ -> return x
323+ logger <- compilerLogger <$> compilerAsk
223−{-# INLINE compilerResult #-}
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
224−
225
325
226--------------------------------------------------------------------------------
326--------------------------------------------------------------------------------
227−compilerUnsafeIO :: IO a -> Compiler a
327+-- | Pass a list of messages with a heading to the debug logger
228−compilerUnsafeIO io = Compiler $ \_ -> do
328+compilerDebugEntries :: String -> [String] -> Compiler ()
229− x <- io
329+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
230− return $ CompilerDone x mempty
330+ where
231−{-# INLINE compilerUnsafeIO #-}
331+ indent = unlines . map (" "++) . lines
232
332
233
333
234--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
235compilerTellDependencies :: [Dependency] -> Compiler ()
335compilerTellDependencies :: [Dependency] -> Compiler ()
236compilerTellDependencies ds = do
336compilerTellDependencies ds = do
237− logger <- compilerLogger <$> compilerAsk
337+ compilerDebugLog $ map (\d ->
238− forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
239− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
240 compilerTell mempty {compilerDependencies = ds}
339 compilerTell mempty {compilerDependencies = ds}
241{-# INLINE compilerTellDependencies #-}
340{-# INLINE compilerTellDependencies #-}
242
341
src/Hakyll/Core/Compiler/Require.hs → lib/Hakyll/Core/Compiler/Require.hsrenamed+2−1
@@ -13,7 +13,6 @@ module Hakyll.Core.Compiler.Require
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
16−import Control.Applicative ((<$>))
17import Control.Monad (when)
16import Control.Monad (when)
18import Data.Binary (Binary)
17import Data.Binary (Binary)
19import qualified Data.Set as S
18import qualified Data.Set as S
@@ -92,6 +91,7 @@ loadBody id' = loadSnapshotBody id' final
92
91
93
92
94--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
94+-- | A shortcut for only requiring the body for a specific snapshot of an item
95loadSnapshotBody :: (Binary a, Typeable a)
95loadSnapshotBody :: (Binary a, Typeable a)
96 => Identifier -> Snapshot -> Compiler a
96 => Identifier -> Snapshot -> Compiler a
97loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
97loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -104,6 +104,7 @@ loadAll pattern = loadAllSnapshots pattern final
104
104
105
105
106--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
107+-- | Load a specific snapshot for each of dynamic list of items
107loadAllSnapshots :: (Binary a, Typeable a)
108loadAllSnapshots :: (Binary a, Typeable a)
108 => Pattern -> Snapshot -> Compiler [Item a]
109 => Pattern -> Snapshot -> Compiler [Item a]
109loadAllSnapshots pattern snapshot = do
110loadAllSnapshots pattern snapshot = do
src/Hakyll/Core/Configuration.hs → lib/Hakyll/Core/Configuration.hsrenamed

The content is the same.

src/Hakyll/Core/Dependencies.hs → lib/Hakyll/Core/Dependencies.hsrenamed−1
@@ -8,7 +8,6 @@ module Hakyll.Core.Dependencies
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import Control.Applicative ((<$>), (<*>))
12import Control.Monad (foldM, forM_, unless, when)
11import Control.Monad (foldM, forM_, unless, when)
13import Control.Monad.Reader (ask)
12import Control.Monad.Reader (ask)
14import Control.Monad.RWS (RWS, runRWS)
13import Control.Monad.RWS (RWS, runRWS)
src/Hakyll/Core/File.hs → lib/Hakyll/Core/File.hsrenamed+11−4
@@ -1,5 +1,6 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Exports simple compilers to just copy files
2-- | Exports simple compilers to just copy files
3+{-# LANGUAGE CPP #-}
3{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5module Hakyll.Core.File
6module Hakyll.Core.File
@@ -11,10 +12,14 @@ module Hakyll.Core.File
11
12
12
13
13--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
14−import Control.Applicative ((<$>))
15import Data.Binary (Binary (..))
15import Data.Binary (Binary (..))
16import Data.Typeable (Typeable)
16import Data.Typeable (Typeable)
17−import System.Directory (copyFile, doesFileExist,
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,
18 renameFile)
23 renameFile)
19import System.FilePath ((</>))
24import System.FilePath ((</>))
20import System.Random (randomIO)
25import System.Random (randomIO)
@@ -39,9 +44,11 @@ newtype CopyFile = CopyFile FilePath
39
44
40--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
41instance Writable CopyFile where
46instance Writable CopyFile where
47+#if MIN_VERSION_directory(1,2,6)
48+ write dst (Item _ (CopyFile src)) = copyFileWithMetadata src dst
49+#else
42 write dst (Item _ (CopyFile src)) = copyFile src dst
50 write dst (Item _ (CopyFile src)) = copyFile src dst
43−
51+#endif
44−
45--------------------------------------------------------------------------------
52--------------------------------------------------------------------------------
46copyFileCompiler :: Compiler (Item CopyFile)
53copyFileCompiler :: Compiler (Item CopyFile)
47copyFileCompiler = do
54copyFileCompiler = do
src/Hakyll/Core/Identifier.hs → lib/Hakyll/Core/Identifier.hsrenamed+1−2
@@ -19,7 +19,6 @@ module Hakyll.Core.Identifier
19
19
20
20
21--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
22−import Control.Applicative ((<$>), (<*>))
23import Control.DeepSeq (NFData (..))
22import Control.DeepSeq (NFData (..))
24import Data.List (intercalate)
23import Data.List (intercalate)
25import System.FilePath (dropTrailingPathSeparator, splitPath)
24import System.FilePath (dropTrailingPathSeparator, splitPath)
@@ -63,7 +62,7 @@ instance Show Identifier where
63
62
64--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
65-- | Parse an identifier from a string
64-- | Parse an identifier from a string
66−fromFilePath :: String -> Identifier
65+fromFilePath :: FilePath -> Identifier
67fromFilePath = Identifier Nothing .
66fromFilePath = Identifier Nothing .
68 intercalate "/" . filter (not . null) . split'
67 intercalate "/" . filter (not . null) . split'
69 where
68 where
src/Hakyll/Core/Identifier/Pattern.hs → lib/Hakyll/Core/Identifier/Pattern.hsrenamed+14−74
@@ -27,7 +27,8 @@
27-- * @\"foo\/*.html\"@ will match all HTML files in the @\"foo\/\"@ directory.
27-- * @\"foo\/*.html\"@ will match all HTML files in the @\"foo\/\"@ directory.
28--
28--
29-- The 'capture' function allows the user to get access to the elements captured
29-- The 'capture' function allows the user to get access to the elements captured
30−-- by the capture elements in the pattern.
30+-- by the capture elements in a glob or regex pattern.
31+{-# OPTIONS_GHC -fno-warn-orphans #-}
31module Hakyll.Core.Identifier.Pattern
32module Hakyll.Core.Identifier.Pattern
32 ( -- * The pattern type
33 ( -- * The pattern type
33 Pattern
34 Pattern
@@ -57,79 +58,22 @@ module Hakyll.Core.Identifier.Pattern
57
58
58
59
59--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
60−import Control.Applicative (pure, (<$>), (<*>))
61+import Control.Arrow ((&&&), (>>>))
61−import Control.Arrow ((&&&), (>>>))
62−import Control.Monad (msum)
62+import Control.Monad (msum)
63−import Data.Binary (Binary (..), getWord8, putWord8)
64−import Data.List (inits, isPrefixOf, tails)
63+import Data.List (inits, isPrefixOf,
64+ tails)
65−import Data.Maybe (isJust)
65+import Data.Maybe (isJust)
66−import Data.Monoid (Monoid, mappend, mempty)
67−import Data.Set (Set)
68−import qualified Data.Set as S
66+import qualified Data.Set as S
69
67
70
68
71--------------------------------------------------------------------------------
69--------------------------------------------------------------------------------
72−import GHC.Exts (IsString, fromString)
70+import GHC.Exts (IsString, fromString)
73−import Text.Regex.TDFA ((=~))
71+import Text.Regex.TDFA ((=~))
74
72
75
73
76--------------------------------------------------------------------------------
74--------------------------------------------------------------------------------
77import Hakyll.Core.Identifier
75import Hakyll.Core.Identifier
78−
76+import Hakyll.Core.Identifier.Pattern.Internal
79−
80−--------------------------------------------------------------------------------
81−-- | Elements of a glob pattern
82−data GlobComponent
83− = Capture
84− | CaptureMany
85− | Literal String
86− deriving (Eq, Show)
87−
88−
89−--------------------------------------------------------------------------------
90−instance Binary GlobComponent where
91− put Capture = putWord8 0
92− put CaptureMany = putWord8 1
93− put (Literal s) = putWord8 2 >> put s
94−
95− get = getWord8 >>= \t -> case t of
96− 0 -> pure Capture
97− 1 -> pure CaptureMany
98− 2 -> Literal <$> get
99− _ -> error "Data.Binary.get: Invalid GlobComponent"
100−
101−
102−--------------------------------------------------------------------------------
103−-- | Type that allows matching on identifiers
104−data Pattern
105− = Everything
106− | Complement Pattern
107− | And Pattern Pattern
108− | Glob [GlobComponent]
109− | List (Set Identifier)
110− | Regex String
111− | Version (Maybe String)
112− deriving (Show)
113−
114−
115−--------------------------------------------------------------------------------
116−instance Binary Pattern where
117− put Everything = putWord8 0
118− put (Complement p) = putWord8 1 >> put p
119− put (And x y) = putWord8 2 >> put x >> put y
120− put (Glob g) = putWord8 3 >> put g
121− put (List is) = putWord8 4 >> put is
122− put (Regex r) = putWord8 5 >> put r
123− put (Version v) = putWord8 6 >> put v
124−
125− get = getWord8 >>= \t -> case t of
126− 0 -> pure Everything
127− 1 -> Complement <$> get
128− 2 -> And <$> get <*> get
129− 3 -> Glob <$> get
130− 4 -> List <$> get
131− 5 -> Regex <$> get
132− _ -> Version <$> get
133
77
134
78
135--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
@@ -137,12 +81,6 @@ instance IsString Pattern where
137 fromString = fromGlob
81 fromString = fromGlob
138
82
139
83
140−--------------------------------------------------------------------------------
141−instance Monoid Pattern where
142− mempty = Everything
143− mappend = (.&&.)
144−
145−
146--------------------------------------------------------------------------------
84--------------------------------------------------------------------------------
147-- | Parse a pattern from a string
85-- | Parse a pattern from a string
148fromGlob :: String -> Pattern
86fromGlob :: String -> Pattern
@@ -262,9 +200,11 @@ splits = inits &&& tails >>> uncurry zip >>> reverse
262
200
263
201
264--------------------------------------------------------------------------------
202--------------------------------------------------------------------------------
265−-- | Match a glob against a pattern, generating a list of captures
203+-- | Match a glob or regex pattern against an identifier, generating a list of captures
266capture :: Pattern -> Identifier -> Maybe [String]
204capture :: Pattern -> Identifier -> Maybe [String]
267capture (Glob p) i = capture' p (toFilePath i)
205capture (Glob p) i = capture' p (toFilePath i)
206+capture (Regex pat) i = Just groups
207+ where (_, _, _, groups) = ((toFilePath i) =~ pat) :: (String, String, String, [String])
268capture _ _ = Nothing
208capture _ _ = Nothing
269
209
270
210
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
src/Hakyll/Core/Item.hs → lib/Hakyll/Core/Item.hsrenamed−2
@@ -10,10 +10,8 @@ module Hakyll.Core.Item
10
10
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
13−import Control.Applicative (Applicative, (<$>), (<*>))
14import Data.Binary (Binary (..))
13import Data.Binary (Binary (..))
15import Data.Foldable (Foldable (..))
14import Data.Foldable (Foldable (..))
16−import Data.Traversable (Traversable (..))
17import Data.Typeable (Typeable)
15import Data.Typeable (Typeable)
18import Prelude hiding (foldr)
16import Prelude hiding (foldr)
19
17
src/Hakyll/Core/Item/SomeItem.hs → lib/Hakyll/Core/Item/SomeItem.hsrenamed

The content is the same.

src/Hakyll/Core/Logger.hs → lib/Hakyll/Core/Logger.hsrenamed+9−4
@@ -13,12 +13,12 @@ module Hakyll.Core.Logger
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
16−import Control.Applicative (pure, (<$>), (<*>))
17import Control.Concurrent (forkIO)
16import Control.Concurrent (forkIO)
18import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
17import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
19import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
18import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
20import Control.Monad (forever)
19import Control.Monad (forever)
21import Control.Monad.Trans (MonadIO, liftIO)
20import Control.Monad.Trans (MonadIO, liftIO)
21+import Data.List (intercalate)
22import Prelude hiding (error)
22import Prelude hiding (error)
23
23
24
24
@@ -80,7 +80,7 @@ string l v m
80
80
81--------------------------------------------------------------------------------
81--------------------------------------------------------------------------------
82error :: MonadIO m => Logger -> String -> m ()
82error :: MonadIO m => Logger -> String -> m ()
83−error l m = string l Error $ " [ERROR] " ++ m
83+error l m = string l Error $ " [ERROR] " ++ indent m
84
84
85
85
86--------------------------------------------------------------------------------
86--------------------------------------------------------------------------------
@@ -90,9 +90,14 @@ header l = string l Message
90
90
91--------------------------------------------------------------------------------
91--------------------------------------------------------------------------------
92message :: MonadIO m => Logger -> String -> m ()
92message :: MonadIO m => Logger -> String -> m ()
93−message l m = string l Message $ " " ++ m
93+message l m = string l Message $ " " ++ indent m
94
94
95
95
96--------------------------------------------------------------------------------
96--------------------------------------------------------------------------------
97debug :: MonadIO m => Logger -> String -> m ()
97debug :: MonadIO m => Logger -> String -> m ()
98−debug l m = string l Debug $ " [DEBUG] " ++ m
98+debug l m = string l Debug $ " [DEBUG] " ++ indent m
99+
100+
101+--------------------------------------------------------------------------------
102+indent :: String -> String
103+indent = intercalate "\n " . lines
lib/Hakyll/Core/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"
src/Hakyll/Core/Provider.hs → lib/Hakyll/Core/Provider.hsrenamed

The content is the same.

src/Hakyll/Core/Provider/Internal.hs → lib/Hakyll/Core/Provider/Internal.hsrenamed−2
@@ -20,7 +20,6 @@ module Hakyll.Core.Provider.Internal
20
20
21
21
22--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23−import Control.Applicative ((<$>), (<*>))
24import Control.DeepSeq (NFData (..), deepseq)
23import Control.DeepSeq (NFData (..), deepseq)
25import Control.Monad (forM)
24import Control.Monad (forM)
26import Data.Binary (Binary (..))
25import Data.Binary (Binary (..))
@@ -28,7 +27,6 @@ import qualified Data.ByteString.Lazy as BL
28import Data.Map (Map)
27import Data.Map (Map)
29import qualified Data.Map as M
28import qualified Data.Map as M
30import Data.Maybe (fromMaybe)
29import Data.Maybe (fromMaybe)
31−import Data.Monoid (mempty)
32import Data.Set (Set)
30import Data.Set (Set)
33import qualified Data.Set as S
31import qualified Data.Set as S
34import Data.Time (Day (..), UTCTime (..))
32import Data.Time (Day (..), UTCTime (..))
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"
src/Hakyll/Core/Provider/MetadataCache.hs → lib/Hakyll/Core/Provider/MetadataCache.hsrenamed+3−6
@@ -8,9 +8,6 @@ module Hakyll.Core.Provider.MetadataCache
8
8
9--------------------------------------------------------------------------------
9--------------------------------------------------------------------------------
10import Control.Monad (unless)
10import Control.Monad (unless)
11−import qualified Data.Map as M
12−
13−--------------------------------------------------------------------------------
14import Hakyll.Core.Identifier
11import Hakyll.Core.Identifier
15import Hakyll.Core.Metadata
12import Hakyll.Core.Metadata
16import Hakyll.Core.Provider.Internal
13import Hakyll.Core.Provider.Internal
@@ -21,11 +18,11 @@ import qualified Hakyll.Core.Store as Store
21--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
22resourceMetadata :: Provider -> Identifier -> IO Metadata
19resourceMetadata :: Provider -> Identifier -> IO Metadata
23resourceMetadata p r
20resourceMetadata p r
24− | not (resourceExists p r) = return M.empty
21+ | not (resourceExists p r) = return mempty
25 | otherwise = do
22 | otherwise = do
26 -- TODO keep time in md cache
23 -- TODO keep time in md cache
27 load p r
24 load p r
28− Store.Found md <- Store.get (providerStore p)
25+ Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
29 [name, toFilePath r, "metadata"]
26 [name, toFilePath r, "metadata"]
30 return md
27 return md
31
28
@@ -52,7 +49,7 @@ load p r = do
52 mmof <- Store.isMember store mdk
49 mmof <- Store.isMember store mdk
53 unless mmof $ do
50 unless mmof $ do
54 (md, body) <- loadMetadata p r
51 (md, body) <- loadMetadata p r
55− Store.set store mdk md
52+ Store.set store mdk (BinaryMetadata md)
56 Store.set store bk body
53 Store.set store bk body
57 where
54 where
58 store = providerStore p
55 store = providerStore p
src/Hakyll/Core/Routes.hs → lib/Hakyll/Core/Routes.hsrenamed+17−1
@@ -25,6 +25,7 @@
25-- not appear in your site directory.
25-- not appear in your site directory.
26--
26--
27-- * If an item matches multiple routes, the first rule will be chosen.
27-- * If an item matches multiple routes, the first rule will be chosen.
28+{-# LANGUAGE CPP #-}
28{-# LANGUAGE Rank2Types #-}
29{-# LANGUAGE Rank2Types #-}
29module Hakyll.Core.Routes
30module Hakyll.Core.Routes
30 ( UsedMetadata
31 ( UsedMetadata
@@ -42,7 +43,9 @@ module Hakyll.Core.Routes
42
43
43
44
44--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
45−import Data.Monoid (Monoid, mappend, mempty)
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
46import System.FilePath (replaceExtension)
49import System.FilePath (replaceExtension)
47
50
48
51
@@ -75,6 +78,18 @@ newtype Routes = Routes
75
78
76
79
77--------------------------------------------------------------------------------
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
78instance Monoid Routes where
93instance Monoid Routes where
79 mempty = Routes $ \_ _ -> return (Nothing, False)
94 mempty = Routes $ \_ _ -> return (Nothing, False)
80 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
95 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
@@ -82,6 +97,7 @@ instance Monoid Routes where
82 case mfp of
97 case mfp of
83 Nothing -> g p id'
98 Nothing -> g p id'
84 Just _ -> return (mfp, um)
99 Just _ -> return (mfp, um)
100+#endif
85
101
86
102
87--------------------------------------------------------------------------------
103--------------------------------------------------------------------------------
src/Hakyll/Core/Rules.hs → lib/Hakyll/Core/Rules.hsrenamed+14−5
@@ -19,6 +19,7 @@
19module Hakyll.Core.Rules
19module Hakyll.Core.Rules
20 ( Rules
20 ( Rules
21 , match
21 , match
22+ , matchMetadata
22 , create
23 , create
23 , version
24 , version
24 , compile
25 , compile
@@ -32,13 +33,11 @@ module Hakyll.Core.Rules
32
33
33
34
34--------------------------------------------------------------------------------
35--------------------------------------------------------------------------------
35−import Control.Applicative ((<$>))
36import Control.Monad.Reader (ask, local)
36import Control.Monad.Reader (ask, local)
37import Control.Monad.State (get, modify, put)
37import Control.Monad.State (get, modify, put)
38import Control.Monad.Trans (liftIO)
38import Control.Monad.Trans (liftIO)
39import Control.Monad.Writer (censor, tell)
39import Control.Monad.Writer (censor, tell)
40import Data.Maybe (fromMaybe)
40import Data.Maybe (fromMaybe)
41−import Data.Monoid (mempty)
42import qualified Data.Set as S
41import qualified Data.Set as S
43
42
44
43
@@ -120,16 +119,26 @@ flush = Rules $ do
120
119
121
120
122--------------------------------------------------------------------------------
121--------------------------------------------------------------------------------
123−match :: Pattern -> Rules () -> Rules ()
122+matchInternal :: Pattern -> Rules [Identifier] -> Rules () -> Rules ()
124−match pattern rules = do
123+matchInternal pattern getIDs rules = do
125 tellPattern pattern
124 tellPattern pattern
126 flush
125 flush
127− ids <- getMatches pattern
126+ ids <- getIDs
128 tellResources ids
127 tellResources ids
129 Rules $ local (setMatches ids) $ unRules $ rules >> flush
128 Rules $ local (setMatches ids) $ unRules $ rules >> flush
130 where
129 where
131 setMatches ids env = env {rulesMatches = ids}
130 setMatches ids env = env {rulesMatches = ids}
132
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+
133
142
134--------------------------------------------------------------------------------
143--------------------------------------------------------------------------------
135create :: [Identifier] -> Rules () -> Rules ()
144create :: [Identifier] -> Rules () -> Rules ()
src/Hakyll/Core/Rules/Internal.hs → lib/Hakyll/Core/Rules/Internal.hsrenamed+14−2
@@ -1,4 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE Rank2Types #-}
4{-# LANGUAGE Rank2Types #-}
4module Hakyll.Core.Rules.Internal
5module Hakyll.Core.Rules.Internal
@@ -12,12 +13,13 @@ module Hakyll.Core.Rules.Internal
12
13
13
14
14--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
15−import Control.Applicative (Applicative, (<$>))
16import Control.Monad.Reader (ask)
16import Control.Monad.Reader (ask)
17import Control.Monad.RWS (RWST, runRWST)
17import Control.Monad.RWS (RWST, runRWST)
18import Control.Monad.Trans (liftIO)
18import Control.Monad.Trans (liftIO)
19import qualified Data.Map as M
19import qualified Data.Map as M
20−import Data.Monoid (Monoid, mappend, mempty)
20+#if MIN_VERSION_base(4,9,0)
21+import Data.Semigroup (Semigroup (..))
22+#endif
21import Data.Set (Set)
23import Data.Set (Set)
22
24
23
25
@@ -54,10 +56,20 @@ data RuleSet = RuleSet
54
56
55
57
56--------------------------------------------------------------------------------
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
57instance Monoid RuleSet where
68instance Monoid RuleSet where
58 mempty = RuleSet mempty mempty mempty mempty
69 mempty = RuleSet mempty mempty mempty mempty
59 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
70 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
60 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
71 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72+#endif
61
73
62
74
63--------------------------------------------------------------------------------
75--------------------------------------------------------------------------------
src/Hakyll/Core/Runtime.hs → lib/Hakyll/Core/Runtime.hsrenamed+11−14
@@ -5,9 +5,8 @@ module Hakyll.Core.Runtime
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Control.Applicative ((<$>))
9import Control.Monad (unless)
8import Control.Monad (unless)
10−import Control.Monad.Error (ErrorT, runErrorT, throwError)
9+import Control.Monad.Except (ExceptT, runExceptT, throwError)
11import Control.Monad.Reader (ask)
10import Control.Monad.Reader (ask)
12import Control.Monad.RWS (RWST, runRWST)
11import Control.Monad.RWS (RWST, runRWST)
13import Control.Monad.State (get, modify)
12import Control.Monad.State (get, modify)
@@ -15,7 +14,6 @@ import Control.Monad.Trans (liftIO)
15import Data.List (intercalate)
14import Data.List (intercalate)
16import Data.Map (Map)
15import Data.Map (Map)
17import qualified Data.Map as M
16import qualified Data.Map as M
18−import Data.Monoid (mempty)
19import Data.Set (Set)
17import Data.Set (Set)
20import qualified Data.Set as S
18import qualified Data.Set as S
21import System.Exit (ExitCode (..))
19import System.Exit (ExitCode (..))
@@ -30,7 +28,7 @@ import Hakyll.Core.Dependencies
30import Hakyll.Core.Identifier
28import Hakyll.Core.Identifier
31import Hakyll.Core.Item
29import Hakyll.Core.Item
32import Hakyll.Core.Item.SomeItem
30import Hakyll.Core.Item.SomeItem
33−import Hakyll.Core.Logger (Logger, Verbosity)
31+import Hakyll.Core.Logger (Logger)
34import qualified Hakyll.Core.Logger as Logger
32import qualified Hakyll.Core.Logger as Logger
35import Hakyll.Core.Provider
33import Hakyll.Core.Provider
36import Hakyll.Core.Routes
34import Hakyll.Core.Routes
@@ -42,10 +40,9 @@ import Hakyll.Core.Writable
42
40
43
41
44--------------------------------------------------------------------------------
42--------------------------------------------------------------------------------
45−run :: Configuration -> Verbosity -> Rules a -> IO (ExitCode, RuleSet)
43+run :: Configuration -> Logger -> Rules a -> IO (ExitCode, RuleSet)
46−run config verbosity rules = do
44+run config logger rules = do
47 -- Initialization
45 -- Initialization
48− logger <- Logger.new verbosity
49 Logger.header logger "Initialising..."
46 Logger.header logger "Initialising..."
50 Logger.message logger "Creating store..."
47 Logger.message logger "Creating store..."
51 store <- Store.new (inMemoryCache config) $ storeDirectory config
48 store <- Store.new (inMemoryCache config) $ storeDirectory config
@@ -78,7 +75,7 @@ run config verbosity rules = do
78 }
75 }
79
76
80 -- Run the program and fetch the resulting state
77 -- Run the program and fetch the resulting state
81− result <- runErrorT $ runRWST build read' state
78+ result <- runExceptT $ runRWST build read' state
82 case result of
79 case result of
83 Left e -> do
80 Left e -> do
84 Logger.error logger e
81 Logger.error logger e
@@ -118,7 +115,7 @@ data RuntimeState = RuntimeState
118
115
119
116
120--------------------------------------------------------------------------------
117--------------------------------------------------------------------------------
121−type Runtime a = RWST RuntimeRead () RuntimeState (ErrorT String IO) a
118+type Runtime a = RWST RuntimeRead () RuntimeState (ExceptT String IO) a
122
119
123
120
124--------------------------------------------------------------------------------
121--------------------------------------------------------------------------------
@@ -202,19 +199,19 @@ chase trail id'
202 result <- liftIO $ runCompiler compiler read'
199 result <- liftIO $ runCompiler compiler read'
203 case result of
200 case result of
204 -- Rethrow error
201 -- Rethrow error
205− CompilerError [] -> throwError
202+ CompilerError e -> throwError $ case compilerErrorMessages e of
206− "Compiler failed but no info given, try running with -v?"
203+ [] -> "Compiler failed but no info given, try running with -v?"
207− CompilerError es -> throwError $ intercalate "; " es
204+ es -> intercalate "; " es
208
205
209 -- Signal that a snapshot was saved ->
206 -- Signal that a snapshot was saved ->
210 CompilerSnapshot snapshot c -> do
207 CompilerSnapshot snapshot c -> do
211− -- Update info and just continue.
208+ -- Update info. The next 'chase' will pick us again at some
209+ -- point so we can continue then.
212 modify $ \s -> s
210 modify $ \s -> s
213 { runtimeSnapshots =
211 { runtimeSnapshots =
214 S.insert (id', snapshot) (runtimeSnapshots s)
212 S.insert (id', snapshot) (runtimeSnapshots s)
215 , runtimeTodo = M.insert id' c (runtimeTodo s)
213 , runtimeTodo = M.insert id' c (runtimeTodo s)
216 }
214 }
217− chase trail id'
218
215
219 -- Huge success
216 -- Huge success
220 CompilerDone (SomeItem item) cwrite -> do
217 CompilerDone (SomeItem item) cwrite -> do
src/Hakyll/Core/Store.hs → lib/Hakyll/Core/Store.hsrenamed+26−24
@@ -16,8 +16,6 @@ module Hakyll.Core.Store
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−import Control.Applicative ((<$>))
20−import Control.Exception (IOException, handle)
21import qualified Crypto.Hash.MD5 as MD5
19import qualified Crypto.Hash.MD5 as MD5
22import Data.Binary (Binary, decode, encodeFile)
20import Data.Binary (Binary, decode, encodeFile)
23import qualified Data.ByteString as B
21import qualified Data.ByteString as B
@@ -28,11 +26,13 @@ import Data.Maybe (isJust)
28import qualified Data.Text as T
26import qualified Data.Text as T
29import qualified Data.Text.Encoding as T
27import qualified Data.Text.Encoding as T
30import Data.Typeable (TypeRep, Typeable, cast, typeOf)
28import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29+import Numeric (showHex)
31import System.Directory (createDirectoryIfMissing)
30import System.Directory (createDirectoryIfMissing)
32import System.Directory (doesFileExist, removeFile)
31import System.Directory (doesFileExist, removeFile)
33import System.FilePath ((</>))
32import System.FilePath ((</>))
34import System.IO (IOMode (..), hClose, openFile)
33import System.IO (IOMode (..), hClose, openFile)
35−import Text.Printf (printf)
34+import System.IO.Error (catchIOError, ioeSetFileName,
35+ ioeSetLocation, modifyIOError)
36
36
37
37
38--------------------------------------------------------------------------------
38--------------------------------------------------------------------------------
@@ -85,6 +85,14 @@ new inMemory directory = do
85 where
85 where
86 csize = Just 500
86 csize = Just 500
87
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)
88
96
89--------------------------------------------------------------------------------
97--------------------------------------------------------------------------------
90-- | Auxiliary: add an item to the in-memory cache
98-- | Auxiliary: add an item to the in-memory cache
@@ -125,17 +133,16 @@ cacheDelete (Store _ (Just lru)) key = do
125--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
126-- | Store an item
134-- | Store an item
127set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
135set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
128−set store identifier value = do
136+set store identifier value = withStore store "set" (\key path -> do
129− encodeFile (storeDirectory store </> key) value
137+ encodeFile path value
130 cacheInsert store key value
138 cacheInsert store key value
131− where
132− key = hash identifier
139+ ) identifier
133
140
134
141
135--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
136-- | Load an item
143-- | Load an item
137get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
144get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
138−get store identifier = do
145+get store = withStore store "get" $ \key path -> do
139 -- First check the in-memory map
146 -- First check the in-memory map
140 ref <- cacheLookup store key
147 ref <- cacheLookup store key
141 case ref of
148 case ref of
@@ -147,17 +154,14 @@ get store identifier = do
147 then return NotFound
154 then return NotFound
148 -- Found in the filesystem
155 -- Found in the filesystem
149 else do
156 else do
150− v <- decodeClose
157+ v <- decodeClose path
151 cacheInsert store key v
158 cacheInsert store key v
152 return $ Found v
159 return $ Found v
153 -- Found in the in-memory map (or wrong type), just return
160 -- Found in the in-memory map (or wrong type), just return
154 s -> return s
161 s -> return s
155 where
162 where
156− key = hash identifier
157− path = storeDirectory store </> key
158−
159 -- 'decodeFile' from Data.Binary which closes the file ASAP
163 -- 'decodeFile' from Data.Binary which closes the file ASAP
160− decodeClose = do
164+ decodeClose path = do
161 h <- openFile path ReadMode
165 h <- openFile path ReadMode
162 lbs <- BL.hGetContents h
166 lbs <- BL.hGetContents h
163 BL.length lbs `seq` hClose h
167 BL.length lbs `seq` hClose h
@@ -167,32 +171,30 @@ get store identifier = do
167--------------------------------------------------------------------------------
171--------------------------------------------------------------------------------
168-- | Strict function
172-- | Strict function
169isMember :: Store -> [String] -> IO Bool
173isMember :: Store -> [String] -> IO Bool
170−isMember store identifier = do
174+isMember store = withStore store "isMember" $ \key path -> do
171 inCache <- cacheIsMember store key
175 inCache <- cacheIsMember store key
172 if inCache then return True else doesFileExist path
176 if inCache then return True else doesFileExist path
173− where
174− key = hash identifier
175− path = storeDirectory store </> key
176
177
177
178
178--------------------------------------------------------------------------------
179--------------------------------------------------------------------------------
179-- | Delete an item
180-- | Delete an item
180delete :: Store -> [String] -> IO ()
181delete :: Store -> [String] -> IO ()
181−delete store identifier = do
182+delete store = withStore store "delete" $ \key path -> do
182 cacheDelete store key
183 cacheDelete store key
183− deleteFile $ storeDirectory store </> key
184+ deleteFile path
184− where
185− key = hash identifier
186
185
187
186
188--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
189-- | Delete a file unless it doesn't exist...
188-- | Delete a file unless it doesn't exist...
190deleteFile :: FilePath -> IO ()
189deleteFile :: FilePath -> IO ()
191−deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
190+deleteFile = (`catchIOError` \_ -> return ()) . removeFile
192
191
193
192
194--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
195-- | Mostly meant for internal usage
194-- | Mostly meant for internal usage
196hash :: [String] -> String
195hash :: [String] -> String
197−hash = concatMap (printf "%02x") . B.unpack .
198− MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
196+hash = toHex . B.unpack . MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
197+ where
198+ toHex [] = ""
199+ toHex (x : xs) | x < 16 = '0' : showHex x (toHex xs)
200+ | otherwise = showHex x (toHex xs)
src/Hakyll/Core/UnixFilter.hs → lib/Hakyll/Core/UnixFilter.hsrenamed+3−3
@@ -16,7 +16,6 @@ import Control.Monad (forM_)
16import Data.ByteString.Lazy (ByteString)
16import Data.ByteString.Lazy (ByteString)
17import qualified Data.ByteString.Lazy as LB
17import qualified Data.ByteString.Lazy as LB
18import Data.IORef (newIORef, readIORef, writeIORef)
18import Data.IORef (newIORef, readIORef, writeIORef)
19−import Data.Monoid (Monoid, mempty)
20import System.Exit (ExitCode (..))
19import System.Exit (ExitCode (..))
21import System.IO (Handle, hClose, hFlush, hGetContents,
20import System.IO (Handle, hClose, hFlush, hGetContents,
22 hPutStr, hSetEncoding, localeEncoding)
21 hPutStr, hSetEncoding, localeEncoding)
@@ -30,7 +29,7 @@ import Hakyll.Core.Compiler
30-- | Use a unix filter as compiler. For example, we could use the 'rev' program
29-- | Use a unix filter as compiler. For example, we could use the 'rev' program
31-- as a compiler.
30-- as a compiler.
32--
31--
33−-- > rev :: Compiler String
32+-- > rev :: Compiler (Item String)
34-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
33-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
35--
34--
36-- A more realistic example: one can use this to call, for example, the sass
35-- A more realistic example: one can use this to call, for example, the sass
@@ -93,7 +92,8 @@ unixFilterWith writer reader programName args input = do
93 ExitSuccess -> return output
92 ExitSuccess -> return output
94 ExitFailure e -> fail $
93 ExitFailure e -> fail $
95 "Hakyll.Core.UnixFilter.unixFilterWith: " ++
94 "Hakyll.Core.UnixFilter.unixFilterWith: " ++
96− unwords (programName : args) ++ " gave exit code " ++ show e
95+ unwords (programName : args) ++ " gave exit code " ++ show e ++
96+ ". (Error: " ++ err ++ ")"
97
97
98
98
99--------------------------------------------------------------------------------
99--------------------------------------------------------------------------------
src/Hakyll/Core/Util/File.hs → lib/Hakyll/Core/Util/File.hsrenamed−1
@@ -8,7 +8,6 @@ module Hakyll.Core.Util.File
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import Control.Applicative ((<$>))
12import Control.Monad (filterM, forM, when)
11import Control.Monad (filterM, forM, when)
13import System.Directory (createDirectoryIfMissing,
12import System.Directory (createDirectoryIfMissing,
14 doesDirectoryExist, getDirectoryContents,
13 doesDirectoryExist, getDirectoryContents,
src/Hakyll/Core/Util/Parser.hs → lib/Hakyll/Core/Util/Parser.hsrenamed+10−4
@@ -2,13 +2,12 @@
2-- | Parser utilities
2-- | Parser utilities
3module Hakyll.Core.Util.Parser
3module Hakyll.Core.Util.Parser
4 ( metadataKey
4 ( metadataKey
5− , reservedKeys
6 ) where
5 ) where
7
6
8
7
9--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
10−import Control.Applicative ((<$>), (<*>), (<|>))
9+import Control.Applicative ((<|>))
11−import Control.Monad (mzero)
10+import Control.Monad (guard, mzero, void)
12import qualified Text.Parsec as P
11import qualified Text.Parsec as P
13import Text.Parsec.String (Parser)
12import Text.Parsec.String (Parser)
14
13
@@ -16,7 +15,14 @@ import Text.Parsec.String (Parser)
16--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
17metadataKey :: Parser String
16metadataKey :: Parser String
18metadataKey = do
17metadataKey = 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+
19− i <- (:) <$> P.letter <*> (P.many $ P.alphaNum <|> P.oneOf "_-.")
25+ i <- (:) <$> P.letter <*> P.many (P.alphaNum <|> P.oneOf "_." <|> hyphon)
20 if i `elem` reservedKeys then mzero else return i
26 if i `elem` reservedKeys then mzero else return i
21
27
22
28
src/Hakyll/Core/Util/String.hs → lib/Hakyll/Core/Util/String.hsrenamed+2−1
@@ -1,3 +1,4 @@
1+{-# LANGUAGE FlexibleContexts #-}
1--------------------------------------------------------------------------------
2--------------------------------------------------------------------------------
2-- | Miscellaneous string manipulation functions.
3-- | Miscellaneous string manipulation functions.
3module Hakyll.Core.Util.String
4module Hakyll.Core.Util.String
@@ -26,7 +27,7 @@ trim = reverse . trim' . reverse . trim'
26--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
27-- | A simple (but inefficient) regex replace funcion
28-- | A simple (but inefficient) regex replace funcion
28replaceAll :: String -- ^ Pattern
29replaceAll :: String -- ^ Pattern
29− -> (String -> String) -- ^ Replacement (called on capture)
30+ -> (String -> String) -- ^ Replacement (called on match)
30 -> String -- ^ Source string
31 -> String -- ^ Source string
31 -> String -- ^ Result
32 -> String -- ^ Result
32replaceAll pattern f source = replaceAll' source
33replaceAll pattern f source = replaceAll' source
src/Hakyll/Core/Writable.hs → lib/Hakyll/Core/Writable.hsrenamed

The content is the same.

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 #-}
src/Hakyll/Preview/Poll.hs → lib/Hakyll/Preview/Poll.hsrenamed+12−22
@@ -12,17 +12,16 @@ import Control.Concurrent.MVar (newEmptyMVar, takeMVar,
12import Control.Exception (AsyncException, fromException,
12import Control.Exception (AsyncException, fromException,
13 handle, throw)
13 handle, throw)
14import Control.Monad (forever, void, when)
14import Control.Monad (forever, void, when)
15−import Filesystem.Path.CurrentOS (decodeString, encodeString)
16import System.Directory (canonicalizePath)
15import System.Directory (canonicalizePath)
17−import System.FilePath (pathSeparators, (</>))
16+import System.FilePath (pathSeparators)
18−import System.FSNotify (Event (..), startManager,
17+import qualified System.FSNotify as FSNotify
19− watchTree)
20
18
21#ifdef mingw32_HOST_OS
19#ifdef mingw32_HOST_OS
22import Control.Concurrent (threadDelay)
20import Control.Concurrent (threadDelay)
23import Control.Exception (IOException, throw, try)
21import Control.Exception (IOException, throw, try)
24import System.Directory (doesFileExist)
22import System.Directory (doesFileExist)
25import System.Exit (exitFailure)
23import System.Exit (exitFailure)
24+import System.FilePath ((</>))
26import System.IO (Handle, IOMode (ReadMode),
25import System.IO (Handle, IOMode (ReadMode),
27 hClose, openFile)
26 hClose, openFile)
28import System.IO.Error (isPermissionError)
27import System.IO.Error (isPermissionError)
@@ -40,16 +39,16 @@ import Hakyll.Core.Identifier.Pattern
40-- a site as soon as any changes occur
39-- a site as soon as any changes occur
41watchUpdates :: Configuration -> IO Pattern -> IO ()
40watchUpdates :: Configuration -> IO Pattern -> IO ()
42watchUpdates conf update = do
41watchUpdates conf update = do
43− let providerDir = decodeString $ providerDirectory conf
42+ let providerDir = providerDirectory conf
44 shouldBuild <- newEmptyMVar
43 shouldBuild <- newEmptyMVar
45 pattern <- update
44 pattern <- update
46 fullProviderDir <- canonicalizePath $ providerDirectory conf
45 fullProviderDir <- canonicalizePath $ providerDirectory conf
47− manager <- startManager
46+ manager <- FSNotify.startManager
48
47
49 let allowed event = do
48 let allowed event = do
50 -- Absolute path of the changed file. This must be inside provider
49 -- Absolute path of the changed file. This must be inside provider
51 -- dir, since that's the only dir we're watching.
50 -- dir, since that's the only dir we're watching.
52− let path = eventPath event
51+ let path = FSNotify.eventPath event
53 relative = dropWhile (`elem` pathSeparators) $
52 relative = dropWhile (`elem` pathSeparators) $
54 drop (length fullProviderDir) path
53 drop (length fullProviderDir) path
55 identifier = fromFilePath relative
54 identifier = fromFilePath relative
@@ -65,11 +64,11 @@ watchUpdates conf update = do
65 (\e -> case fromException e of
64 (\e -> case fromException e of
66 Nothing -> putStrLn (show e)
65 Nothing -> putStrLn (show e)
67 Just async -> throw (async :: AsyncException))
66 Just async -> throw (async :: AsyncException))
68− (update' event $ encodeString providerDir)
67+ (update' event providerDir)
69
68
70 -- Send an event whenever something occurs so that the thread described
69 -- Send an event whenever something occurs so that the thread described
71 -- above will do a build.
70 -- above will do a build.
72− void $ watchTree manager providerDir (not . isRemove) $ \event -> do
71+ void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
73 allowed' <- allowed event
72 allowed' <- allowed event
74 when allowed' $ void $ tryPutMVar shouldBuild event
73 when allowed' $ void $ tryPutMVar shouldBuild event
75 where
74 where
@@ -77,7 +76,7 @@ watchUpdates conf update = do
77 update' _ _ = void update
76 update' _ _ = void update
78#else
77#else
79 update' event provider = do
78 update' event provider = do
80− let path = provider </> eventPath event
79+ let path = provider </> FSNotify.eventPath event
81 -- on windows, a 'Modified' event is also sent on file deletion
80 -- on windows, a 'Modified' event is also sent on file deletion
82 fileExists <- doesFileExist path
81 fileExists <- doesFileExist path
83
82
@@ -105,15 +104,6 @@ watchUpdates conf update = do
105
104
106
105
107--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
108−eventPath :: Event -> FilePath
109−eventPath evt = encodeString $ evtPath evt
110− where
111− evtPath (Added p _) = p
112− evtPath (Modified p _) = p
113− evtPath (Removed p _) = p
114−
115−
116−--------------------------------------------------------------------------------
117−isRemove :: Event -> Bool
107+isRemove :: FSNotify.Event -> Bool
118−isRemove (Removed _ _) = True
108+isRemove (FSNotify.Removed {}) = True
119−isRemove _ = False
109+isRemove _ = False
lib/Hakyll/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
src/Hakyll/Web/Feed.hs → lib/Hakyll/Web/Feed.hsrenamed+69−26
@@ -1,3 +1,6 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+
1--------------------------------------------------------------------------------
4--------------------------------------------------------------------------------
2-- | A Module that allows easy rendering of RSS feeds.
5-- | A Module that allows easy rendering of RSS feeds.
3--
6--
@@ -20,25 +23,40 @@ module Hakyll.Web.Feed
20 ( FeedConfiguration (..)
23 ( FeedConfiguration (..)
21 , renderRss
24 , renderRss
22 , renderAtom
25 , renderAtom
26+ , renderRssWithTemplates
27+ , renderAtomWithTemplates
23 ) where
28 ) where
24
29
25
30
26−--------------------------------------------------------------------------------
27−import Control.Monad ((<=<))
28−import Data.Monoid (mconcat)
29−
30−
31--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
32import Hakyll.Core.Compiler
32import Hakyll.Core.Compiler
33−import Hakyll.Core.Compiler.Internal
34import Hakyll.Core.Item
33import Hakyll.Core.Item
34+import Hakyll.Core.Util.String (replaceAll)
35import Hakyll.Web.Template
35import Hakyll.Web.Template
36import Hakyll.Web.Template.Context
36import Hakyll.Web.Template.Context
37import Hakyll.Web.Template.List
37import Hakyll.Web.Template.List
38
38
39
39
40--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
41−import Paths_hakyll
41+import Data.FileEmbed (makeRelativeToProject)
42+
43+
44+--------------------------------------------------------------------------------
45+rssTemplate :: Template
46+rssTemplate =
47+ $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate)
48+
49+rssItemTemplate :: Template
50+rssItemTemplate =
51+ $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate)
52+
53+atomTemplate :: Template
54+atomTemplate =
55+ $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate)
56+
57+atomItemTemplate :: Template
58+atomItemTemplate =
59+ $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate)
42
60
43
61
44--------------------------------------------------------------------------------
62--------------------------------------------------------------------------------
@@ -59,25 +77,27 @@ data FeedConfiguration = FeedConfiguration
59
77
60--------------------------------------------------------------------------------
78--------------------------------------------------------------------------------
61-- | Abstract function to render any feed.
79-- | Abstract function to render any feed.
62−renderFeed :: FilePath -- ^ Feed template
80+renderFeed :: Template -- ^ Default feed template
63− -> FilePath -- ^ Item template
81+ -> Template -- ^ Default item template
64 -> FeedConfiguration -- ^ Feed configuration
82 -> FeedConfiguration -- ^ Feed configuration
65 -> Context String -- ^ Context for the items
83 -> Context String -- ^ Context for the items
66 -> [Item String] -- ^ Input items
84 -> [Item String] -- ^ Input items
67 -> Compiler (Item String) -- ^ Resulting item
85 -> Compiler (Item String) -- ^ Resulting item
68−renderFeed feedPath itemPath config itemContext items = do
86+renderFeed feedTpl itemTpl config itemContext items = do
69− feedTpl <- compilerUnsafeIO $ loadTemplate feedPath
87+ protectedItems <- mapM (applyFilter protectCDATA) items
70− itemTpl <- compilerUnsafeIO $ loadTemplate itemPath
71−
72− body <- makeItem =<< applyTemplateList itemTpl itemContext' items
88+ body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
73 applyTemplate feedTpl feedContext body
89 applyTemplate feedTpl feedContext body
74 where
90 where
75− -- Auxiliary: load a template from a datafile
91+ applyFilter :: (Monad m,Functor f) => (String -> String) -> f String -> m (f String)
76− loadTemplate = fmap readTemplate . readFile <=< getDataFileName
92+ applyFilter tr str = return $ fmap tr str
93+ protectCDATA :: String -> String
94+ protectCDATA = replaceAll "]]>" (const "]]&gt;")
77
95
78 itemContext' = mconcat
96 itemContext' = mconcat
97+ [ itemContext
79− [ constField "root" (feedRoot config)
98+ , constField "root" (feedRoot config)
80− , itemContext
99+ , constField "authorName" (feedAuthorName config)
100+ , constField "authorEmail" (feedAuthorEmail config)
81 ]
101 ]
82
102
83 feedContext = mconcat
103 feedContext = mconcat
@@ -97,8 +117,35 @@ renderFeed feedPath itemPath config itemContext items = do
97 updatedField = field "updated" $ \_ -> case items of
117 updatedField = field "updated" $ \_ -> case items of
98 [] -> return "Unknown"
118 [] -> return "Unknown"
99 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
119 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
100− ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
101 StringField s -> return s
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)
102
149
103
150
104--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
@@ -107,9 +154,7 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
107 -> Context String -- ^ Item context
154 -> Context String -- ^ Item context
108 -> [Item String] -- ^ Feed items
155 -> [Item String] -- ^ Feed items
109 -> Compiler (Item String) -- ^ Resulting feed
156 -> Compiler (Item String) -- ^ Resulting feed
110−renderRss config context = renderFeed
157+renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
111− "templates/rss.xml" "templates/rss-item.xml" config
112− (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
113
158
114
159
115--------------------------------------------------------------------------------
160--------------------------------------------------------------------------------
@@ -118,13 +163,11 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
118 -> Context String -- ^ Item context
163 -> Context String -- ^ Item context
119 -> [Item String] -- ^ Feed items
164 -> [Item String] -- ^ Feed items
120 -> Compiler (Item String) -- ^ Resulting feed
165 -> Compiler (Item String) -- ^ Resulting feed
121−renderAtom config context = renderFeed
166+renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
122− "templates/atom.xml" "templates/atom-item.xml" config
123− (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
124
167
125
168
126--------------------------------------------------------------------------------
169--------------------------------------------------------------------------------
127-- | Copies @$updated$@ from @$published$@ if it is not already set.
170-- | Copies @$updated$@ from @$published$@ if it is not already set.
128makeItemContext :: String -> Context a -> Context a
171makeItemContext :: String -> Context a -> Context a
129makeItemContext fmt context = mconcat
172makeItemContext fmt context = mconcat
130− [dateField "published" fmt, context, dateField "updated" fmt]
173+ [context, dateField "published" fmt, dateField "updated" fmt]
src/Hakyll/Web/Html.hs → lib/Hakyll/Web/Html.hsrenamed+6−2
@@ -3,6 +3,7 @@
3module Hakyll.Web.Html
3module Hakyll.Web.Html
4 ( -- * Generic
4 ( -- * Generic
5 withTags
5 withTags
6+ , withTagList
6
7
7 -- * Headers
8 -- * Headers
8 , demoteHeaders
9 , demoteHeaders
@@ -36,8 +37,11 @@ import Network.URI (isUnreserved, escapeURIString)
36--------------------------------------------------------------------------------
37--------------------------------------------------------------------------------
37-- | Map over all tags in the document
38-- | Map over all tags in the document
38withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
39withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
39−withTags f = renderTags' . map f . parseTags'
40+withTags = withTagList . map
40
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'
41
45
42--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
43-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
47-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
@@ -55,7 +59,7 @@ demoteHeaders = withTags $ \tag -> case tag of
55
59
56--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
57isUrlAttribute :: String -> Bool
61isUrlAttribute :: String -> Bool
58−isUrlAttribute = (`elem` ["src", "href", "data"])
62+isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
59
63
60
64
61--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
src/Hakyll/Web/Html/RelativizeUrls.hs → lib/Hakyll/Web/Html/RelativizeUrls.hsrenamed

The content is the same.

src/Hakyll/Web/Paginate.hs → lib/Hakyll/Web/Paginate.hsrenamed+29−3
@@ -11,9 +11,9 @@ module Hakyll.Web.Paginate
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
14−import Control.Monad (forM_)
14+import Control.Applicative (empty)
15+import Control.Monad (forM_, forM)
15import qualified Data.Map as M
16import qualified Data.Map as M
16−import Data.Monoid (mconcat)
17import qualified Data.Set as S
17import qualified Data.Set as S
18
18
19
19
@@ -38,7 +38,7 @@ data Paginate = Paginate
38 { paginateMap :: M.Map PageNumber [Identifier]
38 { paginateMap :: M.Map PageNumber [Identifier]
39 , paginateMakeId :: PageNumber -> Identifier
39 , paginateMakeId :: PageNumber -> Identifier
40 , paginateDependency :: Dependency
40 , paginateDependency :: Dependency
41− } deriving (Show)
41+ }
42
42
43
43
44--------------------------------------------------------------------------------
44--------------------------------------------------------------------------------
@@ -94,6 +94,18 @@ paginatePage pag pageNumber
94-- | A default paginate context which provides the following keys:
94-- | A default paginate context which provides the following keys:
95--
95--
96--
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@
97paginateContext :: Paginate -> PageNumber -> Context a
109paginateContext :: Paginate -> PageNumber -> Context a
98paginateContext pag currentPage = mconcat
110paginateContext pag currentPage = mconcat
99 [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
111 [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
@@ -107,6 +119,20 @@ paginateContext pag currentPage = mconcat
107 , field "currentPageNum" $ \i -> thisPage i >>= num
119 , field "currentPageNum" $ \i -> thisPage i >>= num
108 , field "currentPageUrl" $ \i -> thisPage i >>= url
120 , field "currentPageUrl" $ \i -> thisPage i >>= url
109 , constField "numPages" $ show $ paginateNumPages pag
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+
110 ]
136 ]
111 where
137 where
112 lastPage = paginateNumPages pag
138 lastPage = paginateNumPages pag
src/Hakyll/Web/Pandoc.hs → lib/Hakyll/Web/Pandoc.hsrenamed+30−18
@@ -22,10 +22,9 @@ module Hakyll.Web.Pandoc
22
22
23
23
24--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
25−import Control.Applicative ((<$>))
26−import qualified Data.Set as S
25+import qualified Data.Text as T
27−import Data.Traversable (traverse)
28import Text.Pandoc
26import Text.Pandoc
27+import Text.Pandoc.Highlighting (pygments)
29
28
30
29
31--------------------------------------------------------------------------------
30--------------------------------------------------------------------------------
@@ -36,23 +35,31 @@ import Hakyll.Web.Pandoc.FileType
36
35
37--------------------------------------------------------------------------------
36--------------------------------------------------------------------------------
38-- | Read a string using pandoc, with the default options
37-- | Read a string using pandoc, with the default options
38+readPandoc
39−readPandoc :: Item String -- ^ String to read
39+ :: Item String -- ^ String to read
40− -> Item Pandoc -- ^ Resulting document
40+ -> Compiler (Item Pandoc) -- ^ Resulting document
41readPandoc = readPandocWith defaultHakyllReaderOptions
41readPandoc = readPandocWith defaultHakyllReaderOptions
42
42
43
43
44--------------------------------------------------------------------------------
44--------------------------------------------------------------------------------
45-- | Read a string using pandoc, with the supplied options
45-- | Read a string using pandoc, with the supplied options
46+readPandocWith
46−readPandocWith :: ReaderOptions -- ^ Parser options
47+ :: ReaderOptions -- ^ Parser options
47− -> Item String -- ^ String to read
48+ -> Item String -- ^ String to read
48− -> Item Pandoc -- ^ Resulting document
49+ -> Compiler (Item Pandoc) -- ^ Resulting document
49−readPandocWith ropt item = fmap (reader ropt (itemFileType item)) item
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'
50 where
55 where
51 reader ro t = case t of
56 reader ro t = case t of
57+ DocBook -> readDocBook ro
52 Html -> readHtml ro
58 Html -> readHtml ro
53 LaTeX -> readLaTeX ro
59 LaTeX -> readLaTeX ro
54 LiterateHaskell t' -> reader (addExt ro Ext_literate_haskell) t'
60 LiterateHaskell t' -> reader (addExt ro Ext_literate_haskell) t'
55 Markdown -> readMarkdown ro
61 Markdown -> readMarkdown ro
62+ MediaWiki -> readMediaWiki ro
56 OrgMode -> readOrg ro
63 OrgMode -> readOrg ro
57 Rst -> readRST ro
64 Rst -> readRST ro
58 Textile -> readTextile ro
65 Textile -> readTextile ro
@@ -60,7 +67,7 @@ readPandocWith ropt item = fmap (reader ropt (itemFileType item)) item
60 "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
67 "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
61 "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
68 "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
62
69
63− addExt ro e = ro {readerExtensions = S.insert e $ readerExtensions ro}
70+ addExt ro e = ro {readerExtensions = enableExtension e $ readerExtensions ro}
64
71
65
72
66--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
@@ -75,20 +82,25 @@ writePandoc = writePandocWith defaultHakyllWriterOptions
75writePandocWith :: WriterOptions -- ^ Writer options for pandoc
82writePandocWith :: WriterOptions -- ^ Writer options for pandoc
76 -> Item Pandoc -- ^ Document to write
83 -> Item Pandoc -- ^ Document to write
77 -> Item String -- ^ Resulting HTML
84 -> Item String -- ^ Resulting HTML
78−writePandocWith wopt = fmap $ writeHtmlString wopt
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'
79
89
80
90
81--------------------------------------------------------------------------------
91--------------------------------------------------------------------------------
82-- | Render the resource using pandoc
92-- | Render the resource using pandoc
83−renderPandoc :: Item String -> Item String
93+renderPandoc :: Item String -> Compiler (Item String)
84renderPandoc =
94renderPandoc =
85 renderPandocWith defaultHakyllReaderOptions defaultHakyllWriterOptions
95 renderPandocWith defaultHakyllReaderOptions defaultHakyllWriterOptions
86
96
87
97
88--------------------------------------------------------------------------------
98--------------------------------------------------------------------------------
89-- | Render the resource using pandoc
99-- | Render the resource using pandoc
100+renderPandocWith
90−renderPandocWith :: ReaderOptions -> WriterOptions -> Item String -> Item String
101+ :: ReaderOptions -> WriterOptions -> Item String -> Compiler (Item String)
102+renderPandocWith ropt wopt item =
91−renderPandocWith ropt wopt = writePandocWith wopt . readPandocWith ropt
103+ writePandocWith wopt <$> readPandocWith ropt item
92
104
93
105
94--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
@@ -127,7 +139,7 @@ pandocCompilerWithTransformM :: ReaderOptions -> WriterOptions
127 -> Compiler (Item String)
139 -> Compiler (Item String)
128pandocCompilerWithTransformM ropt wopt f =
140pandocCompilerWithTransformM ropt wopt f =
129 writePandocWith wopt <$>
141 writePandocWith wopt <$>
130− (traverse f =<< readPandocWith ropt <$> getResourceBody)
142+ (traverse f =<< readPandocWith ropt =<< getResourceBody)
131
143
132
144
133--------------------------------------------------------------------------------
145--------------------------------------------------------------------------------
@@ -136,7 +148,7 @@ defaultHakyllReaderOptions :: ReaderOptions
136defaultHakyllReaderOptions = def
148defaultHakyllReaderOptions = def
137 { -- The following option causes pandoc to read smart typography, a nice
149 { -- The following option causes pandoc to read smart typography, a nice
138 -- and free bonus.
150 -- and free bonus.
139− readerSmart = True
151+ readerExtensions = enableExtension Ext_smart pandocExtensions
140 }
152 }
141
153
142
154
@@ -146,8 +158,8 @@ defaultHakyllWriterOptions :: WriterOptions
146defaultHakyllWriterOptions = def
158defaultHakyllWriterOptions = def
147 { -- This option causes literate haskell to be written using '>' marks in
159 { -- This option causes literate haskell to be written using '>' marks in
148 -- html, which I think is a good default.
160 -- html, which I think is a good default.
149− writerExtensions = S.insert Ext_literate_haskell (writerExtensions def)
161+ writerExtensions = enableExtension Ext_smart pandocExtensions
150 , -- We want to have hightlighting by default, to be compatible with earlier
162 , -- We want to have hightlighting by default, to be compatible with earlier
151 -- Hakyll releases
163 -- Hakyll releases
152− writerHighlight = True
164+ writerHighlightStyle = Just pygments
153 }
165 }
src/Hakyll/Web/Pandoc/Biblio.hs → lib/Hakyll/Web/Pandoc/Biblio.hsrenamed+34−15
@@ -4,9 +4,11 @@
4-- In order to add a bibliography, you will need a bibliography file (e.g.
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
5-- @.bib@) and a CSL file (@.csl@). Both need to be compiled with their
6-- respective compilers ('biblioCompiler' and 'cslCompiler'). Then, you can
6-- respective compilers ('biblioCompiler' and 'cslCompiler'). Then, you can
7−-- refer to these files when you use 'pageReadPandocBiblio'. This function also
7+-- refer to these files when you use 'readPandocBiblio'. This function also
8-- takes the reader options for completeness -- you can use
8-- takes the reader options for completeness -- you can use
9-- 'defaultHakyllReaderOptions' if you're unsure.
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.
10{-# LANGUAGE Arrows #-}
12{-# LANGUAGE Arrows #-}
11{-# LANGUAGE DeriveDataTypeable #-}
13{-# LANGUAGE DeriveDataTypeable #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
14{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -16,25 +18,26 @@ module Hakyll.Web.Pandoc.Biblio
16 , Biblio (..)
18 , Biblio (..)
17 , biblioCompiler
19 , biblioCompiler
18 , readPandocBiblio
20 , readPandocBiblio
21+ , pandocBiblioCompiler
19 ) where
22 ) where
20
23
21
24
22--------------------------------------------------------------------------------
25--------------------------------------------------------------------------------
23−import Control.Applicative ((<$>))
26+import Control.Monad (liftM, replicateM)
24−import Control.Monad (replicateM)
27+import Data.Binary (Binary (..))
25−import Data.Binary (Binary (..))
28+import Data.Typeable (Typeable)
26−import Data.Typeable (Typeable)
27−import qualified Text.CSL as CSL
28−import Text.CSL.Pandoc (processCites)
29−import Text.Pandoc (Pandoc, ReaderOptions (..))
30−
31−--------------------------------------------------------------------------------
32import Hakyll.Core.Compiler
29import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
33import Hakyll.Core.Identifier
31import Hakyll.Core.Identifier
34import Hakyll.Core.Item
32import Hakyll.Core.Item
33+import Hakyll.Core.Provider
35import Hakyll.Core.Writable
34import Hakyll.Core.Writable
36import Hakyll.Web.Pandoc
35import Hakyll.Web.Pandoc
37import Hakyll.Web.Pandoc.Binary ()
36import 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 (..))
38
41
39
42
40--------------------------------------------------------------------------------
43--------------------------------------------------------------------------------
@@ -82,8 +85,11 @@ instance Writable Biblio where
82--------------------------------------------------------------------------------
85--------------------------------------------------------------------------------
83biblioCompiler :: Compiler (Item Biblio)
86biblioCompiler :: Compiler (Item Biblio)
84biblioCompiler = do
87biblioCompiler = do
85− filePath <- toFilePath <$> getUnderlying
88+ filePath <- getResourceFilePath
86− makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile filePath)
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
87
93
88
94
89--------------------------------------------------------------------------------
95--------------------------------------------------------------------------------
@@ -94,14 +100,27 @@ readPandocBiblio :: ReaderOptions
94 -> Compiler (Item Pandoc)
100 -> Compiler (Item Pandoc)
95readPandocBiblio ropt csl biblio item = do
101readPandocBiblio ropt csl biblio item = do
96 -- Parse CSL file, if given
102 -- Parse CSL file, if given
103+ provider <- compilerProvider <$> compilerAsk
104+ style <- unsafeCompiler $
97− style <- unsafeCompiler $ CSL.readCSLFile Nothing . toFilePath . itemIdentifier $ csl
105+ CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
98
106
99 -- We need to know the citation keys, add then *before* actually parsing the
107 -- We need to know the citation keys, add then *before* actually parsing the
100 -- actual page. If we don't do this, pandoc won't even consider them
108 -- actual page. If we don't do this, pandoc won't even consider them
101 -- citations!
109 -- citations!
102 let Biblio refs = itemBody biblio
110 let Biblio refs = itemBody biblio
103− pandoc = itemBody $ readPandocWith ropt item
111+ pandoc <- itemBody <$> readPandocWith ropt item
104− pandoc' = processCites style refs pandoc
112+ let pandoc' = processCites style refs pandoc
105
113
106 return $ fmap (const pandoc') item
114 return $ fmap (const pandoc') item
107
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+ }
src/Hakyll/Web/Pandoc/Binary.hs → lib/Hakyll/Web/Pandoc/Binary.hsrenamed+19−17
@@ -1,31 +1,33 @@
1{-# OPTIONS_GHC -fno-warn-orphans #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveGeneric #-}
3module Hakyll.Web.Pandoc.Binary where
3module Hakyll.Web.Pandoc.Binary where
4
4
5−import Data.Binary (Binary (..))
5+import Data.Binary (Binary (..))
6
6
7−import qualified Text.CSL as CSL
7+import qualified Text.CSL as CSL
8−import qualified Text.CSL.Reference as REF
8+import qualified Text.CSL.Reference as REF
9−import qualified Text.CSL.Style as STY
9+import qualified Text.CSL.Style as STY
10−import Text.Pandoc
10+import Text.Pandoc
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
13-- orphans
13-- orphans
14
14
15−instance Binary REF.CNum
15+instance Binary Alignment
16−instance Binary REF.Literal
17−instance Binary REF.RefDate
18−instance Binary REF.RefType
19−instance Binary STY.Agent
20−instance Binary STY.Formatted
21−instance Binary Inline
22instance Binary Block
16instance Binary Block
17+instance Binary CSL.Reference
23instance Binary Citation
18instance Binary Citation
24−instance Binary MathType
25−instance Binary Alignment
26instance Binary CitationMode
19instance Binary CitationMode
27−instance Binary QuoteType
28instance Binary Format
20instance Binary Format
21+instance Binary Inline
29instance Binary ListNumberDelim
22instance Binary ListNumberDelim
30instance Binary ListNumberStyle
23instance Binary ListNumberStyle
31−instance Binary CSL.Reference
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
src/Hakyll/Web/Pandoc/FileType.hs → lib/Hakyll/Web/Pandoc/FileType.hsrenamed+24−19
@@ -22,10 +22,12 @@ import Hakyll.Core.Item
22data FileType
22data FileType
23 = Binary
23 = Binary
24 | Css
24 | Css
25+ | DocBook
25 | Html
26 | Html
26 | LaTeX
27 | LaTeX
27 | LiterateHaskell FileType
28 | LiterateHaskell FileType
28 | Markdown
29 | Markdown
30+ | MediaWiki
29 | OrgMode
31 | OrgMode
30 | PlainText
32 | PlainText
31 | Rst
33 | Rst
@@ -38,29 +40,32 @@ data FileType
38fileType :: FilePath -> FileType
40fileType :: FilePath -> FileType
39fileType = uncurry fileType' . splitExtension
41fileType = uncurry fileType' . splitExtension
40 where
42 where
41− fileType' _ ".css" = Css
43+ fileType' _ ".css" = Css
42− fileType' _ ".htm" = Html
44+ fileType' _ ".dbk" = DocBook
45+ fileType' _ ".htm" = Html
43− fileType' _ ".html" = Html
46+ fileType' _ ".html" = Html
44− fileType' f ".lhs" = LiterateHaskell $ case fileType f of
47+ fileType' f ".lhs" = LiterateHaskell $ case fileType f of
45 -- If no extension is given, default to Markdown + LiterateHaskell
48 -- If no extension is given, default to Markdown + LiterateHaskell
46 Binary -> Markdown
49 Binary -> Markdown
47 -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
50 -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
48 x -> x
51 x -> x
49− fileType' _ ".markdown" = Markdown
52+ fileType' _ ".markdown" = Markdown
53+ fileType' _ ".mediawiki" = MediaWiki
50− fileType' _ ".md" = Markdown
54+ fileType' _ ".md" = Markdown
51− fileType' _ ".mdn" = Markdown
55+ fileType' _ ".mdn" = Markdown
52− fileType' _ ".mdown" = Markdown
56+ fileType' _ ".mdown" = Markdown
53− fileType' _ ".mdwn" = Markdown
57+ fileType' _ ".mdwn" = Markdown
54− fileType' _ ".mkd" = Markdown
58+ fileType' _ ".mkd" = Markdown
55− fileType' _ ".mkdwn" = Markdown
59+ fileType' _ ".mkdwn" = Markdown
56− fileType' _ ".org" = OrgMode
60+ fileType' _ ".org" = OrgMode
57− fileType' _ ".page" = Markdown
61+ fileType' _ ".page" = Markdown
58− fileType' _ ".rst" = Rst
62+ fileType' _ ".rst" = Rst
59− fileType' _ ".tex" = LaTeX
63+ fileType' _ ".tex" = LaTeX
60− fileType' _ ".text" = PlainText
64+ fileType' _ ".text" = PlainText
61− fileType' _ ".textile" = Textile
65+ fileType' _ ".textile" = Textile
62− fileType' _ ".txt" = PlainText
66+ fileType' _ ".txt" = PlainText
67+ fileType' _ ".wiki" = MediaWiki
63− fileType' _ _ = Binary -- Treat unknown files as binary
68+ fileType' _ _ = Binary -- Treat unknown files as binary
64
69
65
70
66--------------------------------------------------------------------------------
71--------------------------------------------------------------------------------
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>"
src/Hakyll/Web/Tags.hs → lib/Hakyll/Web/Tags.hsrenamed+10−7
@@ -43,6 +43,7 @@
43module Hakyll.Web.Tags
43module Hakyll.Web.Tags
44 ( Tags (..)
44 ( Tags (..)
45 , getTags
45 , getTags
46+ , getCategory
46 , buildTagsWith
47 , buildTagsWith
47 , buildTags
48 , buildTags
48 , buildCategories
49 , buildCategories
@@ -63,13 +64,12 @@ module Hakyll.Web.Tags
63
64
64--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
65import Control.Arrow ((&&&))
66import Control.Arrow ((&&&))
66−import Control.Monad (foldM, forM, forM_)
67+import Control.Monad (foldM, forM, forM_, mplus)
67import Data.Char (toLower)
68import Data.Char (toLower)
68import Data.List (intercalate, intersperse,
69import Data.List (intercalate, intersperse,
69 sortBy)
70 sortBy)
70import qualified Data.Map as M
71import qualified Data.Map as M
71import Data.Maybe (catMaybes, fromMaybe)
72import Data.Maybe (catMaybes, fromMaybe)
72−import Data.Monoid (mconcat)
73import Data.Ord (comparing)
73import Data.Ord (comparing)
74import qualified Data.Set as S
74import qualified Data.Set as S
75import System.FilePath (takeBaseName, takeDirectory)
75import System.FilePath (takeBaseName, takeDirectory)
@@ -88,8 +88,8 @@ import Hakyll.Core.Item
88import Hakyll.Core.Metadata
88import Hakyll.Core.Metadata
89import Hakyll.Core.Rules
89import Hakyll.Core.Rules
90import Hakyll.Core.Util.String
90import Hakyll.Core.Util.String
91−import Hakyll.Web.Template.Context
92import Hakyll.Web.Html
91import Hakyll.Web.Html
92+import Hakyll.Web.Template.Context
93
93
94
94
95--------------------------------------------------------------------------------
95--------------------------------------------------------------------------------
@@ -98,19 +98,22 @@ data Tags = Tags
98 { tagsMap :: [(String, [Identifier])]
98 { tagsMap :: [(String, [Identifier])]
99 , tagsMakeId :: String -> Identifier
99 , tagsMakeId :: String -> Identifier
100 , tagsDependency :: Dependency
100 , tagsDependency :: Dependency
101− } deriving (Show)
101+ }
102
102
103
103
104--------------------------------------------------------------------------------
104--------------------------------------------------------------------------------
105-- | Obtain tags from a page in the default way: parse them from the @tags@
105-- | Obtain tags from a page in the default way: parse them from the @tags@
106−-- metadata field.
106+-- metadata field. This can either be a list or a comma-separated string.
107getTags :: MonadMetadata m => Identifier -> m [String]
107getTags :: MonadMetadata m => Identifier -> m [String]
108getTags identifier = do
108getTags identifier = do
109 metadata <- getMetadata identifier
109 metadata <- getMetadata identifier
110+ return $ fromMaybe [] $
111+ (lookupStringList "tags" metadata) `mplus`
110− return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata
112+ (map trim . splitAll "," <$> lookupString "tags" metadata)
113+
111
114
112--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
113−-- | Obtain categories from a page.
116+-- | Obtain category from a page.
114getCategory :: MonadMetadata m => Identifier -> m [String]
117getCategory :: MonadMetadata m => Identifier -> m [String]
115getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118getCategory = return . return . takeBaseName . takeDirectory . toFilePath
116
119
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 |]
src/Hakyll/Web/Template/Context.hs → lib/Hakyll/Web/Template/Context.hsrenamed+201−42
@@ -1,9 +1,32 @@
1+-- | This module provides 'Context's which are used to expand expressions in
2+-- templates and allow for arbitrary customisation.
3+--
4+-- 'Template's define a small expression DSL which consists of strings,
5+-- identifiers and function application. There is no type system, every value is
6+-- a string and on the top level they get substituted verbatim into the page.
7+--
8+-- For example, you can build a context that contains
9+--
10+-- > … <> functionField "concat" (const . concat) <> …
11+--
12+-- which will allow you to use the @concat@ identifier as a function that takes
13+-- arbitrarily many stings and concatenates them to a new string:
14+--
15+-- > $partial(concat("templates/categories/", category))$
16+--
17+-- This will evaluate the @category@ field in the context, then prepend he path,
18+-- and include the referenced file as a template.
19+
20+
1--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
22+{-# LANGUAGE CPP #-}
2{-# LANGUAGE ExistentialQuantification #-}
23{-# LANGUAGE ExistentialQuantification #-}
24+{-# LANGUAGE RankNTypes #-}
3module Hakyll.Web.Template.Context
25module Hakyll.Web.Template.Context
4 ( ContextField (..)
26 ( ContextField (..)
5 , Context (..)
27 , Context (..)
6 , field
28 , field
29+ , boolField
7 , constField
30 , constField
8 , listField
31 , listField
9 , listFieldWith
32 , listFieldWith
@@ -16,43 +39,49 @@ module Hakyll.Web.Template.Context
16 , urlField
39 , urlField
17 , pathField
40 , pathField
18 , titleField
41 , titleField
42+ , snippetField
19 , dateField
43 , dateField
20 , dateFieldWith
44 , dateFieldWith
21 , getItemUTC
45 , getItemUTC
46+ , getItemModificationTime
22 , modificationTimeField
47 , modificationTimeField
23 , modificationTimeFieldWith
48 , modificationTimeFieldWith
24 , teaserField
49 , teaserField
50+ , teaserFieldWithSeparator
25 , missingField
51 , missingField
52+ , bindItem
26 ) where
53 ) where
27
54
28
55
29--------------------------------------------------------------------------------
56--------------------------------------------------------------------------------
30−import Control.Applicative (Alternative (..), (<$>))
57+import Control.Applicative (Alternative (..))
31import Control.Monad (msum)
58import Control.Monad (msum)
32−import Data.List (intercalate)
59+import Data.List (intercalate, tails)
33−import qualified Data.Map as M
60+#if MIN_VERSION_base(4,9,0)
34−import Data.Monoid (Monoid (..))
61+import Data.Semigroup (Semigroup (..))
62+#endif
35import Data.Time.Clock (UTCTime (..))
63import Data.Time.Clock (UTCTime (..))
36−import Data.Time.Format (formatTime, parseTime)
64+import Data.Time.Format (formatTime)
37−import System.FilePath (takeBaseName, splitDirectories)
65+import qualified Data.Time.Format as TF
38−import System.Locale (TimeLocale, defaultTimeLocale)
66+import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale)
39−
40−
41−--------------------------------------------------------------------------------
42import Hakyll.Core.Compiler
67import Hakyll.Core.Compiler
43import Hakyll.Core.Compiler.Internal
68import Hakyll.Core.Compiler.Internal
44import Hakyll.Core.Identifier
69import Hakyll.Core.Identifier
45import Hakyll.Core.Item
70import Hakyll.Core.Item
46import Hakyll.Core.Metadata
71import Hakyll.Core.Metadata
47import Hakyll.Core.Provider
72import Hakyll.Core.Provider
48−import Hakyll.Core.Util.String (splitAll, needlePrefix)
73+import Hakyll.Core.Util.String (needlePrefix, splitAll)
49import Hakyll.Web.Html
74import Hakyll.Web.Html
75+import Prelude hiding (id)
76+import System.FilePath (dropExtension, splitDirectories,
77+ takeBaseName)
50
78
51
79
52--------------------------------------------------------------------------------
80--------------------------------------------------------------------------------
53-- | Mostly for internal usage
81-- | Mostly for internal usage
54data ContextField
82data ContextField
83+ = EmptyField
55− = StringField String
84+ | StringField String
56 | forall a. ListField (Context a) [Item a]
85 | forall a. ListField (Context a) [Item a]
57
86
58
87
@@ -77,61 +106,140 @@ newtype Context a = Context
77
106
78
107
79--------------------------------------------------------------------------------
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
80instance Monoid (Context a) where
119instance Monoid (Context a) where
81 mempty = missingField
120 mempty = missingField
82 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
121 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122+#endif
83
123
84
124
85--------------------------------------------------------------------------------
125--------------------------------------------------------------------------------
86field' :: String -> (Item a -> Compiler ContextField) -> Context a
126field' :: String -> (Item a -> Compiler ContextField) -> Context a
87−field' key value = Context $ \k _ i -> if k == key then value i else empty
127+field' key value = Context $ \k _ i ->
128+ if k == key
129+ then value i
130+ else noResult $ "Tried field " ++ key
88
131
89
132
90--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
91−-- | Constructs a new field in the 'Context.'
134+-- | Constructs a new field for a 'Context'.
135+-- If the key matches, the compiler is run and its result is substituted in the
136+-- template.
137+--
138+-- If the compiler fails, the field will be considered non-existent
139+-- in an @$if()$@ macro or ultimately break the template application
140+-- (unless the key is found in another context when using '<>').
141+-- Use 'empty' or 'noResult' for intentional failures of fields used in
142+-- @$if()$@, to distinguish them from exceptions thrown with 'fail'.
92field
143field
93 :: String -- ^ Key
144 :: String -- ^ Key
94 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
145 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
95− -- on the item
146+ -- on the item (e.g. accessing metadata)
96 -> Context a
147 -> Context a
97field key value = field' key (fmap StringField . value)
148field key value = field' key (fmap StringField . value)
98
149
99
150
100--------------------------------------------------------------------------------
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+--------------------------------------------------------------------------------
101−-- | Creates a 'field' that does not depend on the 'Item'
164+-- | Creates a 'field' that does not depend on the 'Item' but always yields
165+-- the same string
102−constField :: String -> String -> Context a
166+constField :: String -- ^ Key
167+ -> String -- ^ Value
168+ -> Context a
103constField key = field key . const . return
169constField key = field key . const . return
104
170
105
171
106--------------------------------------------------------------------------------
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.
107listField :: String -> Context a -> Compiler [Item a] -> Context b
176listField :: String -> Context a -> Compiler [Item a] -> Context b
108listField key c xs = listFieldWith key c (const xs)
177listField key c xs = listFieldWith key c (const xs)
109
178
110
179
111--------------------------------------------------------------------------------
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.
112listFieldWith
187listFieldWith
113 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
188 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
114listFieldWith key c f = field' key $ fmap (ListField c) . f
189listFieldWith key c f = field' key $ fmap (ListField c) . f
115
190
116
191
117--------------------------------------------------------------------------------
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
118−functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
198+ -> ([String] -> Item a -> Compiler String) -- ^ Function
199+ -> Context a
119functionField name value = Context $ \k args i ->
200functionField name value = Context $ \k args i ->
120 if k == name
201 if k == name
121 then StringField <$> value args i
202 then StringField <$> value args i
122− else empty
203+ else noResult $ "Tried function field " ++ name
123
204
124
205
125--------------------------------------------------------------------------------
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+--
126mapContext :: (String -> String) -> Context a -> Context a
216mapContext :: (String -> String) -> Context a -> Context a
127mapContext f (Context c) = Context $ \k a i -> do
217mapContext f (Context c) = Context $ \k a i -> do
128 fld <- c k a i
218 fld <- c k a i
129 case fld of
219 case fld of
220+ EmptyField -> wrongType "boolField"
130 StringField str -> return $ StringField (f str)
221 StringField str -> return $ StringField (f str)
131− ListField _ _ -> fail $
222+ _ -> wrongType "ListField"
223+ where
132− "Hakyll.Web.Template.Context.mapContext: " ++
224+ wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
133− "can't map over a ListField!"
225+ "can't map over a " ++ typ ++ "!"
134
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()'"
135
243
136--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
137-- | A context that contains (in that order)
245-- | A context that contains (in that order)
@@ -151,8 +259,7 @@ defaultContext =
151 metadataField `mappend`
259 metadataField `mappend`
152 urlField "url" `mappend`
260 urlField "url" `mappend`
153 pathField "path" `mappend`
261 pathField "path" `mappend`
154− titleField "title" `mappend`
262+ titleField "title"
155− missingField
156
263
157
264
158--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
@@ -170,15 +277,20 @@ bodyField key = field key $ return . itemBody
170-- | Map any field to its metadata value, if present
277-- | Map any field to its metadata value, if present
171metadataField :: Context a
278metadataField :: Context a
172metadataField = Context $ \k _ i -> do
279metadataField = Context $ \k _ i -> do
173− value <- getMetadataField (itemIdentifier i) k
280+ let id = itemIdentifier i
281+ empty' = noResult $ "No '" ++ k ++ "' field in metadata " ++
282+ "of item " ++ show id
283+ value <- getMetadataField id k
174− maybe empty (return . StringField) value
284+ maybe empty' (return . StringField) value
175
285
176
286
177--------------------------------------------------------------------------------
287--------------------------------------------------------------------------------
178-- | Absolute url to the resulting item
288-- | Absolute url to the resulting item
179urlField :: String -> Context a
289urlField :: String -> Context a
180−urlField key = field key $
290+urlField key = field key $ \i -> do
291+ let id = itemIdentifier i
292+ empty' = fail $ "No route url found for item " ++ show id
181− fmap (maybe empty toUrl) . getRoute . itemIdentifier
293+ fmap (maybe empty' toUrl) $ getRoute id
182
294
183
295
184--------------------------------------------------------------------------------
296--------------------------------------------------------------------------------
@@ -225,10 +337,16 @@ titleField = mapContext takeBaseName . pathField
225-- Alternatively, when the metadata has a field called @path@ in a
337-- Alternatively, when the metadata has a field called @path@ in a
226-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
338-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
227-- and no @published@ metadata field set, this function can render
339-- and no @published@ metadata field set, this function can render
228−-- the date. This pattern matches the file name or directory names
340+-- the date. This pattern matches the file name or directory names
229-- that begins with @yyyy-mm-dd@ . For example:
341-- that begins with @yyyy-mm-dd@ . For example:
230-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
342-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
231-- In case of multiple matches, the rightmost one is used.
343-- In case of multiple matches, the rightmost one is used.
344+--
345+-- As another alternative, if none of the above matches, and the file has a
346+-- path which contains nested directories specifying a date, then that date
347+-- will be used. In other words, if the path is of the form
348+-- @**//yyyy//mm//dd//**//main.extension@ .
349+-- As above, in case of multiple matches, the rightmost one is used.
232
350
233dateField :: String -- ^ Key in which the rendered date should be placed
351dateField :: String -- ^ Key in which the rendered date should be placed
234 -> String -- ^ Format to use on the date
352 -> String -- ^ Format to use on the date
@@ -239,7 +357,7 @@ dateField = dateFieldWith defaultTimeLocale
239--------------------------------------------------------------------------------
357--------------------------------------------------------------------------------
240-- | This is an extended version of 'dateField' that allows you to
358-- | This is an extended version of 'dateField' that allows you to
241-- specify a time locale that is used for outputting the date. For more
359-- specify a time locale that is used for outputting the date. For more
242−-- details, see 'dateField'.
360+-- details, see 'dateField' and 'formatTime'.
243dateFieldWith :: TimeLocale -- ^ Output time locale
361dateFieldWith :: TimeLocale -- ^ Output time locale
244 -> String -- ^ Destination key
362 -> String -- ^ Destination key
245 -> String -- ^ Format to use on the date
363 -> String -- ^ Format to use on the date
@@ -259,17 +377,18 @@ getItemUTC :: MonadMetadata m
259 -> m UTCTime -- ^ Parsed UTCTime
377 -> m UTCTime -- ^ Parsed UTCTime
260getItemUTC locale id' = do
378getItemUTC locale id' = do
261 metadata <- getMetadata id'
379 metadata <- getMetadata id'
262− let tryField k fmt = M.lookup k metadata >>= parseTime' fmt
380+ let tryField k fmt = lookupString k metadata >>= parseTime' fmt
263− paths = splitDirectories $ toFilePath id'
381+ paths = splitDirectories $ (dropExtension . toFilePath) id'
264
382
265 maybe empty' return $ msum $
383 maybe empty' return $ msum $
266 [tryField "published" fmt | fmt <- formats] ++
384 [tryField "published" fmt | fmt <- formats] ++
267 [tryField "date" fmt | fmt <- formats] ++
385 [tryField "date" fmt | fmt <- formats] ++
268− [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths]
386+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths] ++
387+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ fnCand | fnCand <- map (take 3) $ reverse . tails $ paths]
269 where
388 where
270 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
389 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
271 "could not parse time for " ++ show id'
390 "could not parse time for " ++ show id'
272− parseTime' = parseTime locale
391+ parseTime' = parseTimeM True locale
273 formats =
392 formats =
274 [ "%a, %d %b %Y %H:%M:%S %Z"
393 [ "%a, %d %b %Y %H:%M:%S %Z"
275 , "%Y-%m-%dT%H:%M:%S%Z"
394 , "%Y-%m-%dT%H:%M:%S%Z"
@@ -282,20 +401,33 @@ getItemUTC locale id' = do
282
401
283
402
284--------------------------------------------------------------------------------
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.
285modificationTimeField :: String -- ^ Key
416modificationTimeField :: String -- ^ Key
286 -> String -- ^ Format
417 -> String -- ^ Format
287− -> Context a -- ^ Resuting context
418+ -> Context a -- ^ Resulting context
288modificationTimeField = modificationTimeFieldWith defaultTimeLocale
419modificationTimeField = modificationTimeFieldWith defaultTimeLocale
289
420
290
421
291--------------------------------------------------------------------------------
422--------------------------------------------------------------------------------
423+-- | Creates a field with the last modification date of the underlying item
424+-- in a custom localisation format (see 'formatTime').
292modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
425modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
293 -> String -- ^ Key
426 -> String -- ^ Key
294 -> String -- ^ Format
427 -> String -- ^ Format
295 -> Context a -- ^ Resulting context
428 -> Context a -- ^ Resulting context
296modificationTimeFieldWith locale key fmt = field key $ \i -> do
429modificationTimeFieldWith locale key fmt = field key $ \i -> do
297− provider <- compilerProvider <$> compilerAsk
298− let mtime = resourceModificationTime provider $ itemIdentifier i
430+ mtime <- getItemModificationTime $ itemIdentifier i
299 return $ formatTime locale fmt mtime
431 return $ formatTime locale fmt mtime
300
432
301
433
@@ -306,9 +438,21 @@ modificationTimeFieldWith locale key fmt = field key $ \i -> do
306teaserField :: String -- ^ Key to use
438teaserField :: String -- ^ Key to use
307 -> Snapshot -- ^ Snapshot to load
439 -> Snapshot -- ^ Snapshot to load
308 -> Context String -- ^ Resulting context
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
309−teaserField key snapshot = field key $ \item -> do
453+teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
310 body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
454 body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
311− case needlePrefix teaserSeparator body of
455+ case needlePrefix separator body of
312 Nothing -> fail $
456 Nothing -> fail $
313 "Hakyll.Web.Template.Context: no teaser defined for " ++
457 "Hakyll.Web.Template.Context: no teaser defined for " ++
314 show (itemIdentifier item)
458 show (itemIdentifier item)
@@ -316,7 +460,22 @@ teaserField key snapshot = field key $ \item -> do
316
460
317
461
318--------------------------------------------------------------------------------
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.
319missingField :: Context a
465missingField :: Context a
320−missingField = Context $ \k _ i -> fail $
466+missingField = Context $ \k _ _ -> noResult $
321− "Missing field $" ++ k ++ "$ in context for item " ++
467+ "Missing field '" ++ k ++ "' in context"
322− show (itemIdentifier i)
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" #-}
src/Hakyll/Web/Template/Internal.hs → lib/Hakyll/Web/Template/Internal/Element.hsrenamed+138−68
@@ -1,22 +1,24 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2−-- | Module containing the template data structure
2+-- | Module containing the elements used in a template. A template is generally
3−{-# LANGUAGE DeriveDataTypeable #-}
3+-- just a list of these elements.
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−module Hakyll.Web.Template.Internal
5+module Hakyll.Web.Template.Internal.Element
6− ( Template (..)
7− , TemplateKey (..)
6+ ( TemplateKey (..)
8 , TemplateExpr (..)
7 , TemplateExpr (..)
9 , TemplateElement (..)
8 , TemplateElement (..)
10− , readTemplate
9+ , templateElems
10+ , parseTemplateElemsFile
11 ) where
11 ) where
12
12
13
13
14--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
15−import Control.Applicative (pure, (<$), (<$>), (<*>), (<|>))
15+import Control.Applicative ((<|>), (<*))
16import Control.Monad (void)
16import Control.Monad (void)
17+import Control.Arrow (left)
17import Data.Binary (Binary, get, getWord8, put, putWord8)
18import Data.Binary (Binary, get, getWord8, put, putWord8)
19+import Data.List (intercalate)
20+import Data.Maybe (isJust)
18import Data.Typeable (Typeable)
21import Data.Typeable (Typeable)
19−import Data.List (intercalate)
20import GHC.Exts (IsString (..))
22import GHC.Exts (IsString (..))
21import qualified Text.Parsec as P
23import qualified Text.Parsec as P
22import qualified Text.Parsec.String as P
24import qualified Text.Parsec.String as P
@@ -24,25 +26,6 @@ import qualified Text.Parsec.String as P
24
26
25--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
26import Hakyll.Core.Util.Parser
28import Hakyll.Core.Util.Parser
27−import Hakyll.Core.Writable
28−
29−
30−--------------------------------------------------------------------------------
31−-- | Datatype used for template substitutions.
32−newtype Template = Template
33− { unTemplate :: [TemplateElement]
34− } deriving (Show, Eq, Binary, Typeable)
35−
36−
37−--------------------------------------------------------------------------------
38−instance Writable Template where
39− -- Writing a template is impossible
40− write _ _ = return ()
41−
42−
43−--------------------------------------------------------------------------------
44−instance IsString Template where
45− fromString = readTemplate
46
29
47
30
48--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
@@ -61,9 +44,14 @@ data TemplateElement
61 = Chunk String
44 = Chunk String
62 | Expr TemplateExpr
45 | Expr TemplateExpr
63 | Escaped
46 | Escaped
64− | If TemplateExpr Template (Maybe Template) -- expr, then, else
47+ -- expr, then, else
48+ | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
65− | For TemplateExpr Template (Maybe Template) -- expr, body, separator
49+ -- expr, body, separator
50+ | For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
66− | Partial TemplateExpr -- filename
51+ -- filename
52+ | Partial TemplateExpr
53+ | TrimL
54+ | TrimR
67 deriving (Show, Eq, Typeable)
55 deriving (Show, Eq, Typeable)
68
56
69
57
@@ -71,10 +59,12 @@ data TemplateElement
71instance Binary TemplateElement where
59instance Binary TemplateElement where
72 put (Chunk string) = putWord8 0 >> put string
60 put (Chunk string) = putWord8 0 >> put string
73 put (Expr e) = putWord8 1 >> put e
61 put (Expr e) = putWord8 1 >> put e
74− put (Escaped) = putWord8 2
62+ put Escaped = putWord8 2
75− put (If e t f ) = putWord8 3 >> put e >> put t >> put f
63+ put (If e t f) = putWord8 3 >> put e >> put t >> put f
76 put (For e b s) = putWord8 4 >> put e >> put b >> put s
64 put (For e b s) = putWord8 4 >> put e >> put b >> put s
77 put (Partial e) = putWord8 5 >> put e
65 put (Partial e) = putWord8 5 >> put e
66+ put TrimL = putWord8 6
67+ put TrimR = putWord8 7
78
68
79 get = getWord8 >>= \tag -> case tag of
69 get = getWord8 >>= \tag -> case tag of
80 0 -> Chunk <$> get
70 0 -> Chunk <$> get
@@ -83,8 +73,9 @@ instance Binary TemplateElement where
83 3 -> If <$> get <*> get <*> get
73 3 -> If <$> get <*> get <*> get
84 4 -> For <$> get <*> get <*> get
74 4 -> For <$> get <*> get <*> get
85 5 -> Partial <$> get
75 5 -> Partial <$> get
86− _ -> error $
76+ 6 -> pure TrimL
77+ 7 -> pure TrimR
87− "Hakyll.Web.Template.Internal: Error reading cached template"
78+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
88
79
89
80
90--------------------------------------------------------------------------------
81--------------------------------------------------------------------------------
@@ -114,35 +105,38 @@ instance Binary TemplateExpr where
114 0 -> Ident <$> get
105 0 -> Ident <$> get
115 1 -> Call <$> get <*> get
106 1 -> Call <$> get <*> get
116 2 -> StringLiteral <$> get
107 2 -> StringLiteral <$> get
117− _ -> error $
118− "Hakyll.Web.Tamplte.Internal: Error reading cached template"
108+ _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
119−
120
109
121--------------------------------------------------------------------------------
110--------------------------------------------------------------------------------
122−readTemplate :: String -> Template
111+parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
123−readTemplate input = case P.parse template "" input of
124− Left err -> error $ "Cannot parse template: " ++ show err
112+parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
125− Right t -> t
113+ . P.parse (templateElems <* P.eof) file
126
114
127
115
128--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
129−template :: P.Parser Template
117+templateElems :: P.Parser [TemplateElement]
130−template = Template <$>
118+templateElems = mconcat <$> P.many (P.choice [ lift chunk
119+ , lift escaped
131− (P.many1 $ chunk <|> escaped <|> conditional <|> for <|> partial <|> expr)
120+ , conditional
121+ , for
122+ , partial
123+ , expr
124+ ])
125+ where lift = fmap (:[])
132
126
133
127
134--------------------------------------------------------------------------------
128--------------------------------------------------------------------------------
135chunk :: P.Parser TemplateElement
129chunk :: P.Parser TemplateElement
136−chunk = Chunk <$> (P.many1 $ P.noneOf "$")
130+chunk = Chunk <$> P.many1 (P.noneOf "$")
137
131
138
132
139--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
140−expr :: P.Parser TemplateElement
134+expr :: P.Parser [TemplateElement]
141expr = P.try $ do
135expr = P.try $ do
142− void $ P.char '$'
136+ trimLExpr <- trimOpen
143 e <- expr'
137 e <- expr'
144− void $ P.char '$'
138+ trimRExpr <- trimClose
145− return $ Expr e
139+ return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
146
140
147
141
148--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
@@ -152,40 +146,105 @@ expr' = stringLiteral <|> call <|> ident
152
146
153--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
154escaped :: P.Parser TemplateElement
148escaped :: P.Parser TemplateElement
155−escaped = Escaped <$ (P.try $ P.string "$$")
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
156
166
157
167
158--------------------------------------------------------------------------------
168--------------------------------------------------------------------------------
159−conditional :: P.Parser TemplateElement
169+conditional :: P.Parser [TemplateElement]
160conditional = P.try $ do
170conditional = P.try $ do
171+ -- if
172+ trimLIf <- trimOpen
161− void $ P.string "$if("
173+ void $ P.string "if("
162 e <- expr'
174 e <- expr'
163− void $ P.string ")$"
175+ void $ P.char ')'
164− thenBranch <- template
176+ trimRIf <- trimClose
165− elseBranch <- P.optionMaybe $ P.try (P.string "$else$") >> template
177+ -- then
178+ thenBranch <- templateElems
179+ -- else
180+ elseParse <- opt "else"
181+ -- endif
182+ trimLEnd <- trimOpen
166− void $ P.string "$endif$"
183+ void $ P.string "endif"
167− return $ If e thenBranch elseBranch
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]
168
201
169
202
170--------------------------------------------------------------------------------
203--------------------------------------------------------------------------------
171−for :: P.Parser TemplateElement
204+for :: P.Parser [TemplateElement]
172for = P.try $ do
205for = P.try $ do
206+ -- for
207+ trimLFor <- trimOpen
173− void $ P.string "$for("
208+ void $ P.string "for("
174 e <- expr'
209 e <- expr'
175− void $ P.string ")$"
210+ void $ P.char ')'
176− body <- template
211+ trimRFor <- trimClose
177− sep <- P.optionMaybe $ P.try (P.string "$sep$") >> template
212+ -- body
213+ bodyBranch <- templateElems
214+ -- sep
215+ sepParse <- opt "sep"
216+ -- endfor
217+ trimLEnd <- trimOpen
178− void $ P.string "$endfor$"
218+ void $ P.string "endfor"
179− return $ For e body sep
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]
180
236
181
237
182--------------------------------------------------------------------------------
238--------------------------------------------------------------------------------
183−partial :: P.Parser TemplateElement
239+partial :: P.Parser [TemplateElement]
184partial = P.try $ do
240partial = P.try $ do
241+ trimLPart <- trimOpen
185− void $ P.string "$partial("
242+ void $ P.string "partial("
186 e <- expr'
243 e <- expr'
187− void $ P.string ")$"
244+ void $ P.char ')'
188− return $ Partial e
245+ trimRPart <- trimClose
246+
247+ pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
189
248
190
249
191--------------------------------------------------------------------------------
250--------------------------------------------------------------------------------
@@ -219,3 +278,14 @@ stringLiteral = do
219--------------------------------------------------------------------------------
278--------------------------------------------------------------------------------
220key :: P.Parser TemplateKey
279key :: P.Parser TemplateKey
221key = TemplateKey <$> metadataKey
280key = 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+
src/Hakyll/Web/Template/List.hs → lib/Hakyll/Web/Template/List.hsrenamed+1−1
@@ -22,7 +22,7 @@ module Hakyll.Web.Template.List
22import Control.Monad (liftM)
22import Control.Monad (liftM)
23import Data.List (intersperse, sortBy)
23import Data.List (intersperse, sortBy)
24import Data.Ord (comparing)
24import Data.Ord (comparing)
25−import System.Locale (defaultTimeLocale)
25+import Data.Time.Locale.Compat (defaultTimeLocale)
26
26
27
27
28--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
src/Hakyll/Check.hsdeleted−251
@@ -1,251 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Check
5− ( Check (..)
6− , check
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Control.Applicative ((<$>))
12−import Control.Monad (forM_)
13−import Control.Monad.Reader (ask)
14−import Control.Monad.RWS (RWST, runRWST)
15−import Control.Monad.Trans (liftIO)
16−import Control.Monad.Writer (tell)
17−import Data.List (isPrefixOf)
18−import Data.Monoid (Monoid (..))
19−import Data.Set (Set)
20−import qualified Data.Set as S
21−import Network.URI (unEscapeString)
22−import System.Directory (doesDirectoryExist, doesFileExist)
23−import System.Exit (ExitCode (..))
24−import System.FilePath (takeDirectory, takeExtension, (</>))
25−import qualified Text.HTML.TagSoup as TS
26−
27−
28−--------------------------------------------------------------------------------
29−#ifdef CHECK_EXTERNAL
30−import Control.Exception (AsyncException (..),
31− SomeException (..), handle, throw)
32−import Control.Monad.State (get, modify)
33−import Data.List (intercalate)
34−import Data.Typeable (cast)
35−import Data.Version (versionBranch)
36−import GHC.Exts (fromString)
37−import qualified Network.HTTP.Conduit as Http
38−import qualified Network.HTTP.Types as Http
39−import qualified Paths_hakyll as Paths_hakyll
40−#endif
41−
42−
43−--------------------------------------------------------------------------------
44−import Hakyll.Core.Configuration
45−import Hakyll.Core.Logger (Logger, Verbosity)
46−import qualified Hakyll.Core.Logger as Logger
47−import Hakyll.Core.Util.File
48−import Hakyll.Web.Html
49−
50−
51−--------------------------------------------------------------------------------
52−data Check = All | InternalLinks
53− deriving (Eq, Ord, Show)
54−
55−
56−--------------------------------------------------------------------------------
57−check :: Configuration -> Verbosity -> Check -> IO ExitCode
58−check config verbosity check' = do
59− ((), write) <- runChecker checkDestination config verbosity check'
60− return $ if checkerFaulty write > 0 then ExitFailure 1 else ExitSuccess
61−
62−
63−--------------------------------------------------------------------------------
64−data CheckerRead = CheckerRead
65− { checkerConfig :: Configuration
66− , checkerLogger :: Logger
67− , checkerCheck :: Check
68− }
69−
70−
71−--------------------------------------------------------------------------------
72−data CheckerWrite = CheckerWrite
73− { checkerFaulty :: Int
74− , checkerOk :: Int
75− } deriving (Show)
76−
77−
78−--------------------------------------------------------------------------------
79−instance Monoid CheckerWrite where
80− mempty = CheckerWrite 0 0
81− mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
82− CheckerWrite (f1 + f2) (o1 + o2)
83−
84−
85−--------------------------------------------------------------------------------
86−type CheckerState = Set String
87−
88−
89−--------------------------------------------------------------------------------
90−type Checker a = RWST CheckerRead CheckerWrite CheckerState IO a
91−
92−
93−--------------------------------------------------------------------------------
94−runChecker :: Checker a -> Configuration -> Verbosity -> Check
95− -> IO (a, CheckerWrite)
96−runChecker checker config verbosity check' = do
97− logger <- Logger.new verbosity
98− let read' = CheckerRead
99− { checkerConfig = config
100− , checkerLogger = logger
101− , checkerCheck = check'
102− }
103−
104− (x, _, write) <- runRWST checker read' S.empty
105− Logger.flush logger
106− return (x, write)
107−
108−
109−--------------------------------------------------------------------------------
110−checkDestination :: Checker ()
111−checkDestination = do
112− config <- checkerConfig <$> ask
113− files <- liftIO $ getRecursiveContents
114− (const $ return False) (destinationDirectory config)
115−
116− let htmls =
117− [ destinationDirectory config </> file
118− | file <- files
119− , takeExtension file == ".html"
120− ]
121−
122− forM_ htmls checkFile
123−
124−
125−--------------------------------------------------------------------------------
126−checkFile :: FilePath -> Checker ()
127−checkFile filePath = do
128− logger <- checkerLogger <$> ask
129− contents <- liftIO $ readFile filePath
130− Logger.header logger $ "Checking file " ++ filePath
131−
132− let urls = getUrls $ TS.parseTags contents
133− forM_ urls $ \url -> do
134− Logger.debug logger $ "Checking link " ++ url
135− checkUrl filePath url
136−
137−
138−--------------------------------------------------------------------------------
139−checkUrl :: FilePath -> String -> Checker ()
140−checkUrl filePath url
141− | isExternal url = checkExternalUrl url
142− | hasProtocol url = skip "Unknown protocol, skipping"
143− | otherwise = checkInternalUrl filePath url
144− where
145− validProtoChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "+-."
146− hasProtocol str = case break (== ':') str of
147− (proto, ':' : _) -> all (`elem` validProtoChars) proto
148− _ -> False
149−
150−
151−--------------------------------------------------------------------------------
152−ok :: String -> Checker ()
153−ok _ = tell $ mempty {checkerOk = 1}
154−
155−
156−--------------------------------------------------------------------------------
157−skip :: String -> Checker ()
158−skip reason = do
159− logger <- checkerLogger <$> ask
160− Logger.debug logger $ reason
161− tell $ mempty {checkerOk = 1}
162−
163−--------------------------------------------------------------------------------
164−faulty :: String -> Checker ()
165−faulty url = do
166− logger <- checkerLogger <$> ask
167− Logger.error logger $ "Broken link to " ++ show url
168− tell $ mempty {checkerFaulty = 1}
169−
170−
171−--------------------------------------------------------------------------------
172−checkInternalUrl :: FilePath -> String -> Checker ()
173−checkInternalUrl base url = case url' of
174− "" -> ok url
175− _ -> do
176− config <- checkerConfig <$> ask
177− let dest = destinationDirectory config
178− dir = takeDirectory base
179− filePath
180− | "/" `isPrefixOf` url' = dest ++ url'
181− | otherwise = dir </> url'
182−
183− exists <- checkFileExists filePath
184− if exists then ok url else faulty url
185− where
186− url' = stripFragments $ unEscapeString url
187−
188−
189−--------------------------------------------------------------------------------
190−checkExternalUrl :: String -> Checker ()
191−#ifdef CHECK_EXTERNAL
192−checkExternalUrl url = do
193− logger <- checkerLogger <$> ask
194− needsCheck <- (== All) . checkerCheck <$> ask
195− checked <- (url `S.member`) <$> get
196−
197− if not needsCheck || checked
198− then Logger.debug logger "Already checked, skipping"
199− else do
200− isOk <- liftIO $ handle (failure logger) $
201− Http.withManager $ \mgr -> do
202− request <- Http.parseUrl urlToCheck
203− response <- Http.http (settings request) mgr
204− let code = Http.statusCode (Http.responseStatus response)
205− return $ code >= 200 && code < 300
206−
207− modify $ if schemeRelative url
208− then S.insert urlToCheck . S.insert url
209− else S.insert url
210− if isOk then ok url else faulty url
211− where
212− -- Add additional request info
213− settings r = r
214− { Http.method = "HEAD"
215− , Http.redirectCount = 10
216− , Http.requestHeaders = ("User-Agent", ua) : Http.requestHeaders r
217− }
218−
219− -- Nice user agent info
220− ua = fromString $ "hakyll-check/" ++
221− (intercalate "." $ map show $ versionBranch $ Paths_hakyll.version)
222−
223− -- Catch all the things except UserInterrupt
224− failure logger (SomeException e) = case cast e of
225− Just UserInterrupt -> throw UserInterrupt
226− _ -> Logger.error logger (show e) >> return False
227−
228− -- Check scheme-relative links
229− schemeRelative = isPrefixOf "//"
230− urlToCheck = if schemeRelative url then "http:" ++ url else url
231−#else
232−checkExternalUrl _ = return ()
233−#endif
234−
235−
236−
237−--------------------------------------------------------------------------------
238−-- | Wraps doesFileExist, also checks for index.html
239−checkFileExists :: FilePath -> Checker Bool
240−checkFileExists filePath = liftIO $ do
241− file <- doesFileExist filePath
242− dir <- doesDirectoryExist filePath
243− case (file, dir) of
244− (True, _) -> return True
245− (_, True) -> doesFileExist $ filePath </> "index.html"
246− _ -> return False
247−
248−
249−--------------------------------------------------------------------------------
250−stripFragments :: String -> String
251−stripFragments = takeWhile (not . flip elem ['?', '#'])
src/Hakyll/Core/Metadata.hsdeleted−64
@@ -1,64 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Core.Metadata
3− ( Metadata
4− , MonadMetadata (..)
5− , getMetadataField
6− , getMetadataField'
7− , makePatternDependency
8− ) where
9−
10−
11−--------------------------------------------------------------------------------
12−import Control.Monad (forM)
13−import Data.Map (Map)
14−import qualified Data.Map as M
15−import qualified Data.Set as S
16−
17−
18−--------------------------------------------------------------------------------
19−import Hakyll.Core.Dependencies
20−import Hakyll.Core.Identifier
21−import Hakyll.Core.Identifier.Pattern
22−
23−
24−--------------------------------------------------------------------------------
25−type Metadata = Map String String
26−
27−
28−--------------------------------------------------------------------------------
29−class Monad m => MonadMetadata m where
30− getMetadata :: Identifier -> m Metadata
31− getMatches :: Pattern -> m [Identifier]
32−
33− getAllMetadata :: Pattern -> m [(Identifier, Metadata)]
34− getAllMetadata pattern = do
35− matches' <- getMatches pattern
36− forM matches' $ \id' -> do
37− metadata <- getMetadata id'
38− return (id', metadata)
39−
40−
41−--------------------------------------------------------------------------------
42−getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
43−getMetadataField identifier key = do
44− metadata <- getMetadata identifier
45− return $ M.lookup key metadata
46−
47−
48−--------------------------------------------------------------------------------
49−-- | Version of 'getMetadataField' which throws an error if the field does not
50−-- exist.
51−getMetadataField' :: MonadMetadata m => Identifier -> String -> m String
52−getMetadataField' identifier key = do
53− field <- getMetadataField identifier key
54− case field of
55− Just v -> return v
56− Nothing -> fail $ "Hakyll.Core.Metadata.getMetadataField': " ++
57− "Item " ++ show identifier ++ " has no metadata field " ++ show key
58−
59−
60−--------------------------------------------------------------------------------
61−makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
62−makePatternDependency pattern = do
63− matches' <- getMatches pattern
64− return $ PatternDependency pattern (S.fromList matches')
src/Hakyll/Core/Provider/Metadata.hsdeleted−135
@@ -1,135 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Internal module to parse metadata
3−module Hakyll.Core.Provider.Metadata
4− ( loadMetadata
5− , metadata
6− , page
7−
8− -- This parser can be reused in some places
9− , metadataKey
10− ) where
11−
12−
13−--------------------------------------------------------------------------------
14−import Control.Applicative
15−import Control.Arrow (second)
16−import qualified Data.ByteString.Char8 as BC
17−import Data.List (intercalate)
18−import qualified Data.Map as M
19−import System.IO as IO
20−import Text.Parsec ((<?>))
21−import qualified Text.Parsec as P
22−import Text.Parsec.String (Parser)
23−
24−
25−--------------------------------------------------------------------------------
26−import Hakyll.Core.Identifier
27−import Hakyll.Core.Metadata
28−import Hakyll.Core.Provider.Internal
29−import Hakyll.Core.Util.Parser
30−import Hakyll.Core.Util.String
31−
32−
33−--------------------------------------------------------------------------------
34−loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
35−loadMetadata p identifier = do
36− hasHeader <- probablyHasMetadataHeader fp
37− (md, body) <- if hasHeader
38− then second Just <$> loadMetadataHeader fp
39− else return (M.empty, Nothing)
40−
41− emd <- case mi of
42− Nothing -> return M.empty
43− Just mi' -> loadMetadataFile $ resourceFilePath p mi'
44−
45− return (M.union md emd, body)
46− where
47− normal = setVersion Nothing identifier
48− fp = resourceFilePath p identifier
49− mi = M.lookup normal (providerFiles p) >>= resourceInfoMetadata
50−
51−
52−--------------------------------------------------------------------------------
53−loadMetadataHeader :: FilePath -> IO (Metadata, String)
54−loadMetadataHeader fp = do
55− contents <- readFile fp
56− case P.parse page fp contents of
57− Left err -> error (show err)
58− Right (md, b) -> return (M.fromList md, b)
59−
60−
61−--------------------------------------------------------------------------------
62−loadMetadataFile :: FilePath -> IO Metadata
63−loadMetadataFile fp = do
64− contents <- readFile fp
65− case P.parse metadata fp contents of
66− Left err -> error (show err)
67− Right md -> return $ M.fromList md
68−
69−
70−--------------------------------------------------------------------------------
71−-- | Check if a file "probably" has a metadata header. The main goal of this is
72−-- to exclude binary files (which are unlikely to start with "---").
73−probablyHasMetadataHeader :: FilePath -> IO Bool
74−probablyHasMetadataHeader fp = do
75− handle <- IO.openFile fp IO.ReadMode
76− bs <- BC.hGet handle 1024
77− IO.hClose handle
78− return $ isMetadataHeader bs
79− where
80− isMetadataHeader bs =
81− let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
82− in BC.length pre >= 3 && BC.all (== '-') pre
83−
84−
85−--------------------------------------------------------------------------------
86−-- | Space or tab, no newline
87−inlineSpace :: Parser Char
88−inlineSpace = P.oneOf ['\t', ' '] <?> "space"
89−
90−
91−--------------------------------------------------------------------------------
92−-- | Parse Windows newlines as well (i.e. "\n" or "\r\n")
93−newline :: Parser String
94−newline = P.string "\n" <|> P.string "\r\n"
95−
96−
97−--------------------------------------------------------------------------------
98−-- | Parse a single metadata field
99−metadataField :: Parser (String, String)
100−metadataField = do
101− key <- metadataKey
102− _ <- P.char ':'
103− P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
104− value <- P.manyTill P.anyChar newline
105− trailing' <- P.many trailing
106− return (key, trim $ intercalate " " $ value : trailing')
107− where
108− trailing = P.many1 inlineSpace *> P.manyTill P.anyChar newline
109−
110−
111−--------------------------------------------------------------------------------
112−-- | Parse a metadata block
113−metadata :: Parser [(String, String)]
114−metadata = P.many metadataField
115−
116−
117−--------------------------------------------------------------------------------
118−-- | Parse a metadata block, including delimiters and trailing newlines
119−metadataBlock :: Parser [(String, String)]
120−metadataBlock = do
121− open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline
122− metadata' <- metadata
123− _ <- P.choice $ map (P.string . replicate (length open)) ['-', '.']
124− P.skipMany inlineSpace
125− P.skipMany1 newline
126− return metadata'
127−
128−
129−--------------------------------------------------------------------------------
130−-- | Parse a page consisting of a metadata header and a body
131−page :: Parser ([(String, String)], String)
132−page = do
133− metadata' <- P.option [] metadataBlock
134− body <- P.many P.anyChar
135− return (metadata', body)
src/Hakyll/Init.hsdeleted−81
@@ -1,81 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Main
3− ( main
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import Control.Arrow (first)
9−import Control.Monad (forM_)
10−import Data.Char (isAlphaNum, isNumber)
11−import Data.List (intercalate)
12−import Data.Version (Version(..))
13−import System.Directory (copyFile, canonicalizePath)
14−import System.Environment (getArgs, getProgName)
15−import System.Exit (exitFailure)
16−import System.FilePath ((</>), splitDirectories)
17−
18−
19−--------------------------------------------------------------------------------
20−import Hakyll.Core.Util.File
21−import Paths_hakyll
22−
23−
24−--------------------------------------------------------------------------------
25−main :: IO ()
26−main = do
27− progName <- getProgName
28− args <- getArgs
29− srcDir <- getDataFileName "example"
30− files <- getRecursiveContents (const $ return False) srcDir
31−
32− case args of
33− [dstDir] -> do
34− forM_ files $ \file -> do
35− let dst = dstDir </> file
36− src = srcDir </> file
37− putStrLn $ "Creating " ++ dst
38− makeDirectories dst
39− copyFile src dst
40− -- canonicalizePath is safe because the destination
41− -- directory should exist at this point
42− canonicalizePath dstDir >>= createCabal
43− _ -> do
44− putStrLn $ "Usage: " ++ progName ++ " <directory>"
45− exitFailure
46−
47−
48−createCabal :: FilePath -> IO ()
49−createCabal dstDir = do
50− putStrLn $ "Creating " ++ name ++ ".cabal"
51− writeFile (dstDir </> name ++ ".cabal") $ unlines [
52− "name: " ++ name
53− , "version: 0.1.0.0"
54− , "build-type: Simple"
55− , "cabal-version: >= 1.10"
56− , ""
57− , "executable site"
58− , " main-is: site.hs"
59− , " build-depends: base == 4.*"
60− , " , hakyll == " ++ version' ++ ".*"
61− , " ghc-options: -threaded"
62− , " default-language: Haskell2010"
63− ]
64− where
65− -- Major hakyll version
66− version' = intercalate "." . take 2 . map show $ versionBranch version
67− -- last is safe here as the path is canonicalised and "/" is just
68− -- a very rare but possible corner case
69− name = case last (splitDirectories dstDir) of
70− "/" -> fallbackName
71− x -> repair (fallbackName ++) id x
72− -- Package name repair code comes from
73− -- cabal-install.Distribution.Client.Init.Heuristics
74− repair invalid valid x = case dropWhile (not . isAlphaNum) x of
75− "" -> repairComponent ""
76− x' -> let (c, r) = first repairComponent $ break (not . isAlphaNum) x'
77− in c ++ repairRest r
78− where repairComponent c | all isNumber c = invalid c
79− | otherwise = valid c
80− repairRest = repair id ('-' :)
81− fallbackName = "site"
src/Hakyll/Main.hsdeleted−122
@@ -1,122 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module providing the main hakyll function and command-line argument parsing
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveDataTypeable #-}
5−module Hakyll.Main
6− ( hakyll
7− , hakyllWith
8− ) where
9−
10−
11−--------------------------------------------------------------------------------
12−import System.Console.CmdArgs
13−import qualified System.Console.CmdArgs.Explicit as CA
14−import System.Environment (getProgName)
15−import System.IO.Unsafe (unsafePerformIO)
16−import System.Exit (exitWith)
17−
18−--------------------------------------------------------------------------------
19−import qualified Hakyll.Check as Check
20−import qualified Hakyll.Commands as Commands
21−import qualified Hakyll.Core.Configuration as Config
22−import qualified Hakyll.Core.Logger as Logger
23−import Hakyll.Core.Rules
24−
25−
26−--------------------------------------------------------------------------------
27−-- | This usualy is the function with which the user runs the hakyll compiler
28−hakyll :: Rules a -> IO ()
29−hakyll = hakyllWith Config.defaultConfiguration
30−
31−
32−--------------------------------------------------------------------------------
33−-- | A variant of 'hakyll' which allows the user to specify a custom
34−-- configuration
35−hakyllWith :: Config.Configuration -> Rules a -> IO ()
36−hakyllWith conf rules = do
37− args' <- cmdArgs (hakyllArgs conf)
38−
39− let verbosity' = if verbose args' then Logger.Debug else Logger.Message
40− check' =
41− if internal_links args' then Check.InternalLinks else Check.All
42−
43− case args' of
44− Build _ -> Commands.build conf verbosity' rules >>= exitWith
45− Check _ _ -> Commands.check conf verbosity' check'
46− Clean _ -> Commands.clean conf
47− Deploy _ -> Commands.deploy conf >>= exitWith
48− Help _ -> showHelp
49− Preview _ p -> Commands.preview conf verbosity' rules p
50− Rebuild _ -> Commands.rebuild conf verbosity' rules >>= exitWith
51− Server _ _ _ -> Commands.server conf (host args') (port args')
52− Watch _ _ p s -> Commands.watch conf verbosity' (host args') p (not s) rules
53−
54−
55−--------------------------------------------------------------------------------
56−-- | Show usage information.
57−showHelp :: IO ()
58−showHelp = print $ CA.helpText [] CA.HelpFormatOne $ cmdArgsMode (hakyllArgs Config.defaultConfiguration)
59−
60−
61−--------------------------------------------------------------------------------
62−data HakyllArgs
63− = Build {verbose :: Bool}
64− | Check {verbose :: Bool, internal_links :: Bool}
65− | Clean {verbose :: Bool}
66− | Deploy {verbose :: Bool}
67− | Help {verbose :: Bool}
68− | Preview {verbose :: Bool, port :: Int}
69− | Rebuild {verbose :: Bool}
70− | Server {verbose :: Bool, host :: String, port :: Int}
71− | Watch {verbose :: Bool, host :: String, port :: Int, no_server :: Bool }
72− deriving (Data, Typeable, Show)
73−
74−
75−--------------------------------------------------------------------------------
76−hakyllArgs :: Config.Configuration -> HakyllArgs
77−hakyllArgs conf = modes
78− [ (Build $ verboseFlag def) &= help "Generate the site"
79− , (Check (verboseFlag def) (False &= help "Check internal links only")) &=
80− help "Validate the site output"
81− , (Clean $ verboseFlag def) &= help "Clean up and remove cache"
82− , (Deploy $ verboseFlag def) &= help "Upload/deploy your site"
83− , (Help $ verboseFlag def) &= help "Show this message" &= auto
84− , (Preview (verboseFlag def) (portFlag defaultPort)) &=
85− help "[Deprecated] Please use the watch command"
86− , (Rebuild $ verboseFlag def) &= help "Clean and build again"
87− , (Server (verboseFlag def) (hostFlag defaultHost) (portFlag defaultPort)) &=
88− help "Start a preview server"
89− , (Watch (verboseFlag def) (hostFlag defaultHost) (portFlag defaultPort) (noServerFlag False) &=
90− help "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server.")
91− ] &= help "Hakyll static site compiler" &= program progName
92− where
93− defaultHost = Config.previewHost conf
94− defaultPort = Config.previewPort conf
95−
96−--------------------------------------------------------------------------------
97−verboseFlag :: Data a => a -> a
98−verboseFlag x = x &= help "Run in verbose mode"
99−{-# INLINE verboseFlag #-}
100−
101−
102−--------------------------------------------------------------------------------
103−noServerFlag :: Data a => a -> a
104−noServerFlag x = x &= help "Disable the built-in web server"
105−{-# INLINE noServerFlag #-}
106−
107−--------------------------------------------------------------------------------
108−hostFlag :: Data a => a -> a
109−hostFlag x = x &= help "Host to bind on"
110−{-# INLINE hostFlag #-}
111−
112−--------------------------------------------------------------------------------
113−portFlag :: Data a => a -> a
114−portFlag x = x &= help "Port to listen on"
115−{-# INLINE portFlag #-}
116−
117−
118−--------------------------------------------------------------------------------
119−-- | This is necessary because not everyone calls their program the same...
120−progName :: String
121−progName = unsafePerformIO getProgName
122−{-# NOINLINE progName #-}
src/Hakyll/Preview/Server.hsdeleted−46
@@ -1,46 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Implements a basic static file server for previewing options
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Preview.Server
5− ( staticServer
6− ) where
7−
8−
9−--------------------------------------------------------------------------------
10−import Control.Monad.Trans (liftIO)
11−import qualified Data.ByteString.Char8 as B
12−import qualified Snap.Core as Snap
13−import qualified Snap.Http.Server as Snap
14−import qualified Snap.Util.FileServe as Snap
15−
16−
17−--------------------------------------------------------------------------------
18−-- | Serve a given directory
19−static :: FilePath -- ^ Directory to serve
20− -> (FilePath -> IO ()) -- ^ Pre-serve hook
21− -> Snap.Snap ()
22−static directory preServe =
23− Snap.serveDirectoryWith directoryConfig directory
24− where
25− directoryConfig :: Snap.DirectoryConfig Snap.Snap
26− directoryConfig = Snap.fancyDirectoryConfig
27− { Snap.preServeHook = liftIO . preServe
28− }
29−
30−
31−--------------------------------------------------------------------------------
32−-- | Main method, runs a static server in the given directory
33−staticServer :: FilePath -- ^ Directory to serve
34− -> (FilePath -> IO ()) -- ^ Pre-serve hook
35− -> String -- ^ Host to bind on
36− -> Int -- ^ Port to listen on
37− -> IO () -- ^ Blocks forever
38−staticServer directory preServe host port =
39− Snap.httpServe config $ static directory preServe
40− where
41− -- Snap server config
42− config = Snap.setBind (B.pack host)
43− $ Snap.setPort port
44− $ Snap.setAccessLog Snap.ConfigNoLog
45− $ Snap.setErrorLog Snap.ConfigNoLog
46− $ Snap.emptyConfig
src/Hakyll/Web/CompressCss.hsdeleted−60
@@ -1,60 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module used for CSS compression. The compression is currently in a simple
3−-- state, but would typically reduce the number of bytes by about 25%.
4−module Hakyll.Web.CompressCss
5− ( compressCssCompiler
6− , compressCss
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Control.Applicative ((<$>))
12−import Data.Char (isSpace)
13−import Data.List (isPrefixOf)
14−
15−
16−--------------------------------------------------------------------------------
17−import Hakyll.Core.Compiler
18−import Hakyll.Core.Item
19−import Hakyll.Core.Util.String
20−
21−
22−--------------------------------------------------------------------------------
23−-- | Compiler form of 'compressCss'
24−compressCssCompiler :: Compiler (Item String)
25−compressCssCompiler = fmap compressCss <$> getResourceString
26−
27−
28−--------------------------------------------------------------------------------
29−-- | Compress CSS to speed up your site.
30−compressCss :: String -> String
31−compressCss = compressSeparators . stripComments . compressWhitespace
32−
33−
34−--------------------------------------------------------------------------------
35−-- | Compresses certain forms of separators.
36−compressSeparators :: String -> String
37−compressSeparators =
38− replaceAll "; *}" (const "}") .
39− replaceAll " *([{};:]) *" (take 1 . dropWhile isSpace) .
40− replaceAll ";+" (const ";")
41−
42−
43−--------------------------------------------------------------------------------
44−-- | Compresses all whitespace.
45−compressWhitespace :: String -> String
46−compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
47−
48−
49−--------------------------------------------------------------------------------
50−-- | Function that strips CSS comments away.
51−stripComments :: String -> String
52−stripComments [] = []
53−stripComments str
54− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
55− | otherwise = head str : stripComments (drop 1 str)
56− where
57− eatComments str'
58− | null str' = []
59− | isPrefixOf "*/" str' = drop 2 str'
60− | otherwise = eatComments $ drop 1 str'
src/Hakyll/Web/Template.hsdeleted−256
@@ -1,256 +0,0 @@
1−-- | This module provides means for reading and applying 'Template's.
2−--
3−-- Templates are tools to convert items into a string. They are perfectly suited
4−-- for laying out your site.
5−--
6−-- Let's look at an example template:
7−--
8−-- > <html>
9−-- > <head>
10−-- > <title>My crazy homepage - $title$</title>
11−-- > </head>
12−-- > <body>
13−-- > <div id="header">
14−-- > <h1>My crazy homepage - $title$</h1>
15−-- > </div>
16−-- > <div id="content">
17−-- > $body$
18−-- > </div>
19−-- > <div id="footer">
20−-- > By reading this you agree that I now own your soul
21−-- > </div>
22−-- > </body>
23−-- > </html>
24−--
25−-- As you can see, the format is very simple -- @$key$@ is used to render the
26−-- @$key$@ field from the page, everything else is literally copied. If you want
27−-- to literally insert @\"$key$\"@ into your page (for example, when you're
28−-- writing a Hakyll tutorial) you can use
29−--
30−-- > <p>
31−-- > A literal $$key$$.
32−-- > </p>
33−--
34−-- Because of it's simplicity, these templates can be used for more than HTML:
35−-- you could make, for example, CSS or JS templates as well.
36−--
37−-- Apart from interpolating @$key$@s from the 'Context' you can also
38−-- use the following macros:
39−--
40−-- * @$if(key)$@
41−--
42−-- > $if(key)$
43−-- > <b> Defined </b>
44−-- > $else$
45−-- > <b> Non-defined </b>
46−-- > $endif$
47−--
48−-- This example will print @Defined@ if @key@ is defined in the
49−-- context and @Non-defined@ otherwise. The @$else$@ clause is
50−-- optional.
51−--
52−-- * @$for(key)$@
53−--
54−-- The @for@ macro is used for enumerating 'Context' elements that are
55−-- lists, i.e. constructed using the 'listField' function. Assume that
56−-- in a context we have an element @listField \"key\" c itms@. Then
57−-- the snippet
58−--
59−-- > $for(key)$
60−-- > $x$
61−-- > $sep$,
62−-- > $endfor$
63−--
64−-- would, for each item @i@ in 'itms', lookup @$x$@ in the context @c@
65−-- with item @i@, interpolate it, and join the resulting list with
66−-- @,@.
67−--
68−-- Another concrete example one may consider is the following. Given the
69−-- context
70−--
71−-- > listField "things" (field "thing" (return . itemBody))
72−-- > (sequence [makeItem "fruits", makeItem "vegetables"])
73−--
74−-- and a template
75−--
76−-- > I like
77−-- > $for(things)$
78−-- > fresh $thing$$sep$, and
79−-- > $endfor$
80−--
81−-- the resulting page would look like
82−--
83−-- > <p>
84−-- > I like
85−-- >
86−-- > fresh fruits, and
87−-- >
88−-- > fresh vegetables
89−-- > </p>
90−--
91−-- The @$sep$@ part can be omitted. Usually, you can get by using the
92−-- 'applyListTemplate' and 'applyJoinListTemplate' functions.
93−--
94−-- * @$partial(path)$@
95−--
96−-- Loads a template located in a separate file and interpolates it
97−-- under the current context.
98−--
99−-- Assuming that the file @test.html@ contains
100−--
101−-- > <b>$key$</b>
102−--
103−-- The result of rendering
104−--
105−-- > <p>
106−-- > $partial("test.html")$
107−-- > </p>
108−--
109−-- is the same as the result of rendering
110−--
111−-- > <p>
112−-- > <b>$key$</b>
113−-- > </p>
114−--
115−-- That is, calling @$partial$@ is equivalent to just copying and pasting
116−-- template code.
117−--
118−{-# LANGUAGE ScopedTypeVariables #-}
119−module Hakyll.Web.Template
120− ( Template
121− , templateCompiler
122− , applyTemplate
123− , loadAndApplyTemplate
124− , applyAsTemplate
125− , readTemplate
126− ) where
127−
128−
129−--------------------------------------------------------------------------------
130−import Control.Monad (liftM)
131−import Control.Monad.Error (MonadError (..))
132−import Data.List (intercalate)
133−import Data.Monoid (mappend)
134−import Prelude hiding (id)
135−
136−
137−--------------------------------------------------------------------------------
138−import Hakyll.Core.Compiler
139−import Hakyll.Core.Identifier
140−import Hakyll.Core.Item
141−import Hakyll.Web.Template.Context
142−import Hakyll.Web.Template.Internal
143−
144−
145−--------------------------------------------------------------------------------
146−-- | Read a template.
147−templateCompiler :: Compiler (Item Template)
148−templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
149− item <- getResourceString
150− return $ fmap readTemplate item
151−
152−
153−--------------------------------------------------------------------------------
154−applyTemplate :: Template -- ^ Template
155− -> Context a -- ^ Context
156− -> Item a -- ^ Page
157− -> Compiler (Item String) -- ^ Resulting item
158−applyTemplate tpl context item = do
159− body <- applyTemplate' tpl context item
160− return $ itemSetBody body item
161−
162−
163−--------------------------------------------------------------------------------
164−applyTemplate'
165− :: forall a.
166− Template -- ^ Template
167− -> Context a -- ^ Context
168− -> Item a -- ^ Page
169− -> Compiler String -- ^ Resulting item
170−applyTemplate' tpl context x = go tpl
171− where
172− context' :: String -> [String] -> Item a -> Compiler ContextField
173− context' = unContext (context `mappend` missingField)
174−
175− go = liftM concat . mapM applyElem . unTemplate
176−
177− ---------------------------------------------------------------------------
178−
179− applyElem :: TemplateElement -> Compiler String
180−
181− applyElem (Chunk c) = return c
182−
183− applyElem (Expr e) = applyExpr e >>= getString e
184−
185− applyElem Escaped = return "$"
186−
187− applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
188− where
189− handler _ = case mf of
190− Nothing -> return ""
191− Just f -> go f
192−
193− applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
194− StringField _ -> fail $
195− "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
196− "got StringField for expr " ++ show e
197− ListField c xs -> do
198− sep <- maybe (return "") go s
199− bs <- mapM (applyTemplate' b c) xs
200− return $ intercalate sep bs
201−
202− applyElem (Partial e) = do
203− p <- applyExpr e >>= getString e
204− tpl' <- loadBody (fromFilePath p)
205− applyTemplate' tpl' context x
206−
207− ---------------------------------------------------------------------------
208−
209− applyExpr :: TemplateExpr -> Compiler ContextField
210−
211− applyExpr (Ident (TemplateKey k)) = context' k [] x
212−
213− applyExpr (Call (TemplateKey k) args) = do
214− args' <- mapM (\e -> applyExpr e >>= getString e) args
215− context' k args' x
216−
217− applyExpr (StringLiteral s) = return (StringField s)
218−
219− ----------------------------------------------------------------------------
220−
221− getString _ (StringField s) = return s
222− getString e (ListField _ _) = fail $
223− "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
224− "got ListField for expr " ++ show e
225−
226−
227−--------------------------------------------------------------------------------
228−-- | The following pattern is so common:
229−--
230−-- > tpl <- loadBody "templates/foo.html"
231−-- > someCompiler
232−-- > >>= applyTemplate tpl context
233−--
234−-- That we have a single function which does this:
235−--
236−-- > someCompiler
237−-- > >>= loadAndApplyTemplate "templates/foo.html" context
238−loadAndApplyTemplate :: Identifier -- ^ Template identifier
239− -> Context a -- ^ Context
240− -> Item a -- ^ Page
241− -> Compiler (Item String) -- ^ Resulting item
242−loadAndApplyTemplate identifier context item = do
243− tpl <- loadBody identifier
244− applyTemplate tpl context item
245−
246−
247−--------------------------------------------------------------------------------
248−-- | It is also possible that you want to substitute @$key$@s within the body of
249−-- an item. This function does that by interpreting the item body as a template,
250−-- and then applying it to itself.
251−applyAsTemplate :: Context String -- ^ Context
252− -> Item String -- ^ Item and template
253− -> Compiler (Item String) -- ^ Resulting item
254−applyAsTemplate context item =
255− let tpl = readTemplate $ itemBody item
256− in applyTemplate tpl context item
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.hs+3−3
@@ -9,8 +9,8 @@ module Hakyll.Core.Dependencies.Tests
9import Data.List (delete)
9import Data.List (delete)
10import qualified Data.Map as M
10import qualified Data.Map as M
11import qualified Data.Set as S
11import qualified Data.Set as S
12−import Test.Framework (Test, testGroup)
12+import Test.Tasty (TestTree, testGroup)
13−import Test.HUnit (Assertion, (@=?))
13+import Test.Tasty.HUnit (Assertion, (@=?))
14
14
15
15
16--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ import TestSuite.Util
20
20
21
21
22--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23−tests :: Test
23+tests :: TestTree
24tests = testGroup "Hakyll.Core.Dependencies.Tests" $
24tests = testGroup "Hakyll.Core.Dependencies.Tests" $
25 fromAssertions "analyze" [case01, case02, case03]
25 fromAssertions "analyze" [case01, case02, case03]
26
26
tests/Hakyll/Core/Identifier/Tests.hs+6−5
@@ -6,8 +6,8 @@ module Hakyll.Core.Identifier.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
10−import Test.HUnit ((@=?))
10+import Test.Tasty.HUnit ((@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ import TestSuite.Util
17
17
18
18
19--------------------------------------------------------------------------------
19--------------------------------------------------------------------------------
20−tests :: Test
20+tests :: TestTree
21tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
21tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
22 [ captureTests
22 [ captureTests
23 , matchesTests
23 , matchesTests
@@ -25,7 +25,7 @@ tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
25
25
26
26
27--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
28−captureTests :: [Test]
28+captureTests :: [TestTree]
29captureTests = fromAssertions "capture"
29captureTests = fromAssertions "capture"
30 [ Just ["bar"] @=? capture "foo/**" "foo/bar"
30 [ Just ["bar"] @=? capture "foo/**" "foo/bar"
31 , Just ["foo/bar"] @=? capture "**" "foo/bar"
31 , Just ["foo/bar"] @=? capture "**" "foo/bar"
@@ -42,11 +42,12 @@ captureTests = fromAssertions "capture"
42 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
42 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
43 , Just [] @=? capture "\\*.jpg" "*.jpg"
43 , Just [] @=? capture "\\*.jpg" "*.jpg"
44 , Nothing @=? capture "\\*.jpg" "foo.jpg"
44 , Nothing @=? capture "\\*.jpg" "foo.jpg"
45+ , Just ["xyz","42"] @=? capture (fromRegex "cat-([a-z]+)/foo([0-9]+).jpg") "cat-xyz/foo42.jpg"
45 ]
46 ]
46
47
47
48
48--------------------------------------------------------------------------------
49--------------------------------------------------------------------------------
49−matchesTests :: [Test]
50+matchesTests :: [TestTree]
50matchesTests = fromAssertions "matches"
51matchesTests = fromAssertions "matches"
51 [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
52 [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
52 , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
53 , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
tests/Hakyll/Core/Provider/Metadata/Tests.hs+24−15
@@ -5,26 +5,27 @@ module Hakyll.Core.Provider.Metadata.Tests
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Test.Framework (Test, testGroup)
8+import qualified Data.HashMap.Strict as HMS
9−import Test.HUnit (Assertion, (@=?))
9+import qualified Data.Text as T
10−import Text.Parsec as P
10+import qualified Data.Yaml as Yaml
11−import Text.Parsec.String (Parser)
11+import Hakyll.Core.Metadata
12−
13−
14−--------------------------------------------------------------------------------
15import Hakyll.Core.Provider.Metadata
12import Hakyll.Core.Provider.Metadata
13+import Test.Tasty (TestTree, testGroup)
14+import Test.Tasty.HUnit (Assertion, assertFailure, (@=?))
16import TestSuite.Util
15import TestSuite.Util
17
16
18
17
19--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
20−tests :: Test
19+tests :: TestTree
21tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
20tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
22 fromAssertions "page" [testPage01, testPage02]
21 fromAssertions "page" [testPage01, testPage02]
23
22
24
23
24+
25--------------------------------------------------------------------------------
25--------------------------------------------------------------------------------
26testPage01 :: Assertion
26testPage01 :: Assertion
27−testPage01 = testParse page ([("foo", "bar")], "qux\n")
27+testPage01 =
28+ (meta [("foo", "bar")], "qux\n") `expectRight` parsePage
28 "---\n\
29 "---\n\
29 \foo: bar\n\
30 \foo: bar\n\
30 \---\n\
31 \---\n\
@@ -33,21 +34,29 @@ testPage01 = testParse page ([("foo", "bar")], "qux\n")
33
34
34--------------------------------------------------------------------------------
35--------------------------------------------------------------------------------
35testPage02 :: Assertion
36testPage02 :: Assertion
36−testPage02 = testParse page
37+testPage02 =
37− ([("description", descr)], "Hello I am dog\n")
38+ (meta [("description", descr)], "Hello I am dog\n") `expectRight`
39+ parsePage
38 "---\n\
40 "---\n\
39 \description: A long description that would look better if it\n\
41 \description: A long description that would look better if it\n\
40 \ spanned multiple lines and was indented\n\
42 \ spanned multiple lines and was indented\n\
41 \---\n\
43 \---\n\
42 \Hello I am dog\n"
44 \Hello I am dog\n"
43 where
45 where
46+ descr :: String
44 descr =
47 descr =
45 "A long description that would look better if it \
48 "A long description that would look better if it \
46 \spanned multiple lines and was indented"
49 \spanned multiple lines and was indented"
47
50
48
51
49--------------------------------------------------------------------------------
52--------------------------------------------------------------------------------
50−testParse :: (Eq a, Show a) => Parser a -> a -> String -> Assertion
53+meta :: Yaml.ToJSON a => [(String, a)] -> Metadata
51−testParse parser expected input = case P.parse parser "<inline>" input of
54+meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
52− Left err -> error $ show err
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)
53− Right x -> expected @=? x
62+expectRight expected (Right res) = expected @=? res
tests/Hakyll/Core/Provider/Tests.hs+8−12
@@ -6,19 +6,15 @@ module Hakyll.Core.Provider.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import qualified Data.Map as M
9+import Hakyll.Core.Metadata
10−import Test.Framework (Test, testGroup)
11−import Test.Framework.Providers.HUnit (testCase)
12−import Test.HUnit (Assertion, assert, (@=?))
13−
14−
15−--------------------------------------------------------------------------------
16import Hakyll.Core.Provider
10import Hakyll.Core.Provider
11+import Test.Tasty (TestTree, testGroup)
12+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
17import TestSuite.Util
13import TestSuite.Util
18
14
19
15
20--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
21−tests :: Test
17+tests :: TestTree
22tests = testGroup "Hakyll.Core.Provider.Tests"
18tests = testGroup "Hakyll.Core.Provider.Tests"
23 [ testCase "case01" case01
19 [ testCase "case01" case01
24 ]
20 ]
@@ -29,12 +25,12 @@ case01 :: Assertion
29case01 = do
25case01 = do
30 store <- newTestStore
26 store <- newTestStore
31 provider <- newTestProvider store
27 provider <- newTestProvider store
32− assert $ resourceExists provider "example.md"
28+ True @=? resourceExists provider "example.md"
33
29
34 metadata <- resourceMetadata provider "example.md"
30 metadata <- resourceMetadata provider "example.md"
35− Just "An example" @=? M.lookup "title" metadata
31+ Just "An example" @=? lookupString "title" metadata
36− Just "External data" @=? M.lookup "external" metadata
32+ Just "External data" @=? lookupString "external" metadata
37
33
38 doesntExist <- resourceMetadata provider "doesntexist.md"
34 doesntExist <- resourceMetadata provider "doesntexist.md"
39− M.empty @=? doesntExist
35+ mempty @=? doesntExist
40 cleanTestEnv
36 cleanTestEnv
tests/Hakyll/Core/Routes/Tests.hs+7−9
@@ -6,20 +6,18 @@ module Hakyll.Core.Routes.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import qualified Data.Map as M
9+import Data.Maybe (fromMaybe)
10−import System.FilePath ((</>))
11−import Test.Framework (Test, testGroup)
12−import Test.HUnit (Assertion, (@=?))
13−
14−
15−--------------------------------------------------------------------------------
16import Hakyll.Core.Identifier
10import Hakyll.Core.Identifier
11+import Hakyll.Core.Metadata
17import Hakyll.Core.Routes
12import Hakyll.Core.Routes
13+import System.FilePath ((</>))
14+import Test.Tasty (TestTree, testGroup)
15+import Test.Tasty.HUnit (Assertion, (@=?))
18import TestSuite.Util
16import TestSuite.Util
19
17
20
18
21--------------------------------------------------------------------------------
19--------------------------------------------------------------------------------
22−tests :: Test
20+tests :: TestTree
23tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
21tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
24 [ testRoutes "foo.html" (setExtension "html") "foo"
22 [ testRoutes "foo.html" (setExtension "html") "foo"
25 , testRoutes "foo.html" (setExtension ".html") "foo"
23 , testRoutes "foo.html" (setExtension ".html") "foo"
@@ -37,7 +35,7 @@ tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
37 "tags/rss/bar"
35 "tags/rss/bar"
38
36
39 , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
37 , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
40− M.findWithDefault "?" "subblog" md </> toFilePath id')
38+ fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
41 "example.md"
39 "example.md"
42 ]
40 ]
43
41
tests/Hakyll/Core/Rules/Tests.hs+29−19
@@ -9,24 +9,22 @@ module Hakyll.Core.Rules.Tests
9import Data.IORef (IORef, newIORef, readIORef,
9import Data.IORef (IORef, newIORef, readIORef,
10 writeIORef)
10 writeIORef)
11import qualified Data.Set as S
11import qualified Data.Set as S
12−import Test.Framework (Test, testGroup)
13−import Test.HUnit (Assertion, assert, (@=?))
14−
15−
16−--------------------------------------------------------------------------------
17import Hakyll.Core.Compiler
12import Hakyll.Core.Compiler
18import Hakyll.Core.File
13import Hakyll.Core.File
19import Hakyll.Core.Identifier
14import Hakyll.Core.Identifier
20import Hakyll.Core.Identifier.Pattern
15import Hakyll.Core.Identifier.Pattern
16+import Hakyll.Core.Metadata
21import Hakyll.Core.Routes
17import Hakyll.Core.Routes
22import Hakyll.Core.Rules
18import Hakyll.Core.Rules
23import Hakyll.Core.Rules.Internal
19import Hakyll.Core.Rules.Internal
24−import Hakyll.Web.Pandoc
20+import System.FilePath ((</>))
21+import Test.Tasty (TestTree, testGroup)
22+import Test.Tasty.HUnit (Assertion, (@=?))
25import TestSuite.Util
23import TestSuite.Util
26
24
27
25
28--------------------------------------------------------------------------------
26--------------------------------------------------------------------------------
29−tests :: Test
27+tests :: TestTree
30tests = testGroup "Hakyll.Core.Rules.Tests" $ fromAssertions "runRules"
28tests = testGroup "Hakyll.Core.Rules.Tests" $ fromAssertions "runRules"
31 [case01]
29 [case01]
32
30
@@ -44,22 +42,29 @@ case01 = do
44 runRoutes routes provider i >>= \(r, _) -> Just ex @=? r
42 runRoutes routes provider i >>= \(r, _) -> Just ex @=? r
45
43
46 -- Test that we have some identifiers and that the routes work out
44 -- Test that we have some identifiers and that the routes work out
47− assert $ all (`S.member` identifiers) expected
45+ S.fromList expected @=? identifiers
48− checkRoute "example.html" "example.md"
46+ checkRoute "example.html" "example.md"
49− checkRoute "example.md" (sv "raw" "example.md")
47+ checkRoute "example.md" (sv "raw" "example.md")
50− checkRoute "example.md" (sv "nav" "example.md")
48+ checkRoute "example.md" (sv "nav" "example.md")
51− checkRoute "example.mv1" (sv "mv1" "example.md")
49+ checkRoute "example.mv1" (sv "mv1" "example.md")
52− checkRoute "example.mv2" (sv "mv2" "example.md")
50+ checkRoute "example.mv2" (sv "mv2" "example.md")
51+ checkRoute "food/example.md" (sv "metadataMatch" "example.md")
53− readIORef ioref >>= assert
52+ readIORef ioref >>= (True @=?)
54 cleanTestEnv
53 cleanTestEnv
55 where
54 where
56 sv g = setVersion (Just g)
55 sv g = setVersion (Just g)
57 expected =
56 expected =
58− [ "example.md"
57+ [ "example.md"
59− , "russian.md"
58+ , sv "raw" "example.md"
59+ , sv "metadataMatch" "example.md"
60+ , sv "nav" "example.md"
60− , sv "raw" "example.md"
61+ , sv "mv1" "example.md"
62+ , sv "mv2" "example.md"
63+
64+ , "russian.md"
61 , sv "raw" "russian.md"
65 , sv "raw" "russian.md"
62− , sv "nav" "example.md"
66+ , sv "mv1" "russian.md"
67+ , sv "mv2" "russian.md"
63 ]
68 ]
64
69
65
70
@@ -69,7 +74,7 @@ rules01 ioref = do
69 -- Compile some posts
74 -- Compile some posts
70 match "*.md" $ do
75 match "*.md" $ do
71 route $ setExtension "html"
76 route $ setExtension "html"
72− compile pandocCompiler
77+ compile copyFileCompiler
73
78
74 -- Yeah. I don't know how else to test this stuff?
79 -- Yeah. I don't know how else to test this stuff?
75 preprocess $ writeIORef ioref True
80 preprocess $ writeIORef ioref True
@@ -79,6 +84,11 @@ rules01 ioref = do
79 route idRoute
84 route idRoute
80 compile getResourceString
85 compile getResourceString
81
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+
82 -- Regression test
92 -- Regression test
83 version "nav" $ match (fromList ["example.md"]) $ do
93 version "nav" $ match (fromList ["example.md"]) $ do
84 route idRoute
94 route idRoute
tests/Hakyll/Core/Runtime/Tests.hs+21−6
@@ -8,8 +8,8 @@ module Hakyll.Core.Runtime.Tests
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9import qualified Data.ByteString as B
9import qualified Data.ByteString as B
10import System.FilePath ((</>))
10import System.FilePath ((</>))
11−import Test.Framework (Test, testGroup)
11+import Test.Tasty (TestTree, testGroup)
12−import Test.HUnit (Assertion, (@?=))
12+import Test.Tasty.HUnit (Assertion, (@?=))
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ import TestSuite.Util
20
20
21
21
22--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23−tests :: Test
23+tests :: TestTree
24tests = testGroup "Hakyll.Core.Runtime.Tests" $
24tests = testGroup "Hakyll.Core.Runtime.Tests" $
25 fromAssertions "run" [case01, case02]
25 fromAssertions "run" [case01, case02]
26
26
@@ -28,7 +28,8 @@ tests = testGroup "Hakyll.Core.Runtime.Tests" $
28--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
29case01 :: Assertion
29case01 :: Assertion
30case01 = do
30case01 = do
31+ logger <- Logger.new Logger.Error
31− _ <- run testConfiguration Logger.Error $ do
32+ _ <- run testConfiguration logger $ do
32 match "images/*" $ do
33 match "images/*" $ do
33 route idRoute
34 route idRoute
34 compile copyFileCompiler
35 compile copyFileCompiler
@@ -38,7 +39,16 @@ case01 = do
38 compile $ do
39 compile $ do
39 getResourceBody
40 getResourceBody
40 >>= saveSnapshot "raw"
41 >>= saveSnapshot "raw"
41− >>= return . renderPandoc
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
42
52
43 create ["bodies.txt"] $ do
53 create ["bodies.txt"] $ do
44 route idRoute
54 route idRoute
@@ -59,13 +69,18 @@ case01 = do
59 bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
69 bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
60 head (lines bodies) @?= "This is an example."
70 head (lines bodies) @?= "This is an example."
61
71
72+ partial <- readFile $ providerDirectory testConfiguration </> "partial.html.out"
73+ partial' <- readFile $ destinationDirectory testConfiguration </> "partial.html.out"
74+ partial @?= partial'
75+
62 cleanTestEnv
76 cleanTestEnv
63
77
64
78
65--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
66case02 :: Assertion
80case02 :: Assertion
67case02 = do
81case02 = do
82+ logger <- Logger.new Logger.Error
68− _ <- run testConfiguration Logger.Error $ do
83+ _ <- run testConfiguration logger $ do
69 match "images/favicon.ico" $ do
84 match "images/favicon.ico" $ do
70 route $ gsubRoute "images/" (const "")
85 route $ gsubRoute "images/" (const "")
71 compile $ makeItem ("Test" :: String)
86 compile $ makeItem ("Test" :: String)
tests/Hakyll/Core/Store/Tests.hs+16−16
@@ -6,22 +6,22 @@ module Hakyll.Core.Store.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Data.Typeable (typeOf)
9+import Data.Typeable (typeOf)
10+import qualified Test.QuickCheck as Q
11+import qualified Test.QuickCheck.Monadic as Q
10−import Test.Framework (Test, testGroup)
12+import Test.Tasty (TestTree, testGroup)
11−import Test.Framework.Providers.HUnit (testCase)
13+import Test.Tasty.HUnit (testCase)
12−import Test.Framework.Providers.QuickCheck2 (testProperty)
13−import qualified Test.HUnit as H
14+import qualified Test.Tasty.HUnit as H
14−import qualified Test.QuickCheck as Q
15+import Test.Tasty.QuickCheck (testProperty)
15−import qualified Test.QuickCheck.Monadic as Q
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−import qualified Hakyll.Core.Store as Store
19+import qualified Hakyll.Core.Store as Store
20import TestSuite.Util
20import TestSuite.Util
21
21
22
22
23--------------------------------------------------------------------------------
23--------------------------------------------------------------------------------
24−tests :: Test
24+tests :: TestTree
25tests = testGroup "Hakyll.Core.Store.Tests"
25tests = testGroup "Hakyll.Core.Store.Tests"
26 [ testProperty "simple get . set" simpleSetGet
26 [ testProperty "simple get . set" simpleSetGet
27 , testProperty "persistent get . set" persistentSetGet
27 , testProperty "persistent get . set" persistentSetGet
@@ -63,11 +63,11 @@ wrongType = do
63 -- Store a string and try to fetch an int
63 -- Store a string and try to fetch an int
64 Store.set store ["foo", "bar"] ("qux" :: String)
64 Store.set store ["foo", "bar"] ("qux" :: String)
65 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
65 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
66− H.assert $ case value of
66+ case value of
67− Store.WrongType e t ->
67+ Store.WrongType e t -> do
68− e == typeOf (undefined :: Int) &&
68+ typeOf (undefined :: Int) H.@=? e
69− t == typeOf (undefined :: String)
69+ typeOf (undefined :: String) H.@=? t
70− _ -> False
70+ _ -> H.assertFailure "Expecting WrongType"
71 cleanTestEnv
71 cleanTestEnv
72
72
73
73
@@ -78,6 +78,6 @@ isMembertest = do
78 Store.set store ["foo", "bar"] ("qux" :: String)
78 Store.set store ["foo", "bar"] ("qux" :: String)
79 good <- Store.isMember store ["foo", "bar"]
79 good <- Store.isMember store ["foo", "bar"]
80 bad <- Store.isMember store ["foo", "baz"]
80 bad <- Store.isMember store ["foo", "baz"]
81− H.assert good
81+ True H.@=? good
82− H.assert (not bad)
82+ False H.@=? bad
83 cleanTestEnv
83 cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hs+23−13
@@ -6,36 +6,38 @@ module Hakyll.Core.UnixFilter.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Data.List (isInfixOf)
10−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
11−import Test.Framework.Providers.HUnit (testCase)
10+import Test.Tasty.HUnit (testCase)
12−import qualified Test.HUnit as H
11+import qualified Test.Tasty.HUnit as H
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16import Hakyll.Core.Compiler
15import Hakyll.Core.Compiler
17−import Hakyll.Core.Compiler.Internal
16+import Hakyll.Core.Identifier
18import Hakyll.Core.Item
17import Hakyll.Core.Item
19import Hakyll.Core.UnixFilter
18import Hakyll.Core.UnixFilter
20import TestSuite.Util
19import TestSuite.Util
21
20
22
21
23--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
24−tests :: Test
23+tests :: TestTree
25tests = testGroup "Hakyll.Core.UnixFilter.Tests"
24tests = testGroup "Hakyll.Core.UnixFilter.Tests"
26 [ testCase "unixFilter rev" unixFilterRev
25 [ testCase "unixFilter rev" unixFilterRev
27 , testCase "unixFilter false" unixFilterFalse
26 , testCase "unixFilter false" unixFilterFalse
27+ , testCase "unixFilter error" unixFilterError
28 ]
28 ]
29
29
30+testMarkdown :: Identifier
31+testMarkdown = "russian.md"
30
32
31--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
32unixFilterRev :: H.Assertion
34unixFilterRev :: H.Assertion
33unixFilterRev = do
35unixFilterRev = do
34 store <- newTestStore
36 store <- newTestStore
35 provider <- newTestProvider store
37 provider <- newTestProvider store
36− output <- testCompilerDone store provider "russian.md" compiler
38+ output <- testCompilerDone store provider testMarkdown compiler
37− expected <- testCompilerDone store provider "russian.md" getResourceString
39+ expected <- testCompilerDone store provider testMarkdown getResourceString
38− H.assert $ rev (itemBody expected) == lines (itemBody output)
40+ rev (itemBody expected) H.@=? lines (itemBody output)
39 cleanTestEnv
41 cleanTestEnv
40 where
42 where
41 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
43 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
@@ -47,10 +49,18 @@ unixFilterFalse :: H.Assertion
47unixFilterFalse = do
49unixFilterFalse = do
48 store <- newTestStore
50 store <- newTestStore
49 provider <- newTestProvider store
51 provider <- newTestProvider store
50− result <- testCompiler store provider "russian.md" compiler
52+ testCompilerError store provider testMarkdown compiler "exit code"
51− H.assert $ case result of
52− CompilerError es -> any ("exit code" `isInfixOf`) es
53− _ -> False
54 cleanTestEnv
53 cleanTestEnv
55 where
54 where
56 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
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.hs+3−3
@@ -5,8 +5,8 @@ module Hakyll.Core.Util.String.Tests
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Test.Framework (Test, testGroup)
8+import Test.Tasty (TestTree, testGroup)
9−import Test.HUnit ((@=?))
9+import Test.Tasty.HUnit ((@=?))
10
10
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
@@ -15,7 +15,7 @@ import TestSuite.Util
15
15
16
16
17--------------------------------------------------------------------------------
17--------------------------------------------------------------------------------
18−tests :: Test
18+tests :: TestTree
19tests = testGroup "Hakyll.Core.Util.String.Tests" $ concat
19tests = testGroup "Hakyll.Core.Util.String.Tests" $ concat
20 [ fromAssertions "trim"
20 [ fromAssertions "trim"
21 [ "foo" @=? trim " foo\n\t "
21 [ "foo" @=? trim " foo\n\t "
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.hs+7−3
@@ -6,8 +6,9 @@ module Hakyll.Web.Html.RelativizeUrls.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
10−import Test.HUnit ((@=?))
10+import Test.Tasty.HUnit ((@=?))
11+
11
12
12--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
13import Hakyll.Web.Html.RelativizeUrls
14import Hakyll.Web.Html.RelativizeUrls
@@ -15,13 +16,16 @@ import TestSuite.Util
15
16
16
17
17--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
18−tests :: Test
19+tests :: TestTree
19tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
20tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
20 fromAssertions "relativizeUrls"
21 fromAssertions "relativizeUrls"
21 [ "<a href=\"../foo\">bar</a>" @=?
22 [ "<a href=\"../foo\">bar</a>" @=?
22 relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
23 relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
23 , "<img src=\"../../images/lolcat.png\" />" @=?
24 , "<img src=\"../../images/lolcat.png\" />" @=?
24 relativizeUrlsWith "../.." "<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>"
25 , "<a href=\"http://haskell.org\">Haskell</a>" @=?
29 , "<a href=\"http://haskell.org\">Haskell</a>" @=?
26 relativizeUrlsWith "../.."
30 relativizeUrlsWith "../.."
27 "<a href=\"http://haskell.org\">Haskell</a>"
31 "<a href=\"http://haskell.org\">Haskell</a>"
tests/Hakyll/Web/Html/Tests.hs+9−9
@@ -5,9 +5,9 @@ module Hakyll.Web.Html.Tests
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Data.Char (toUpper)
8+import Data.Char (toUpper)
9−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
10−import Test.HUnit (assert, (@=?))
10+import Test.Tasty.HUnit ((@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import TestSuite.Util
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−tests :: Test
19+tests :: TestTree
20tests = testGroup "Hakyll.Web.Html.Tests" $ concat
20tests = testGroup "Hakyll.Web.Html.Tests" $ concat
21 [ fromAssertions "demoteHeaders"
21 [ fromAssertions "demoteHeaders"
22 [ "<h2>A h1 title</h2>" @=?
22 [ "<h2>A h1 title</h2>" @=?
@@ -64,11 +64,11 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
64 ]
64 ]
65
65
66 , fromAssertions "isExternal"
66 , fromAssertions "isExternal"
67− [ assert (isExternal "http://reddit.com")
67+ [ True @=? isExternal "http://reddit.com"
68− , assert (isExternal "https://mail.google.com")
68+ , True @=? isExternal "https://mail.google.com"
69− , assert (isExternal "//ajax.googleapis.com")
69+ , True @=? isExternal "//ajax.googleapis.com"
70− , assert (not (isExternal "../header.png"))
70+ , False @=? isExternal "../header.png"
71− , assert (not (isExternal "/foo/index.html"))
71+ , False @=? isExternal "/foo/index.html"
72 ]
72 ]
73
73
74 , fromAssertions "stripTags"
74 , fromAssertions "stripTags"
tests/Hakyll/Web/Pandoc/FileType/Tests.hs+3−3
@@ -6,8 +6,8 @@ module Hakyll.Web.Pandoc.FileType.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
10−import Test.HUnit ((@=?))
10+import Test.Tasty.HUnit ((@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import TestSuite.Util
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−tests :: Test
19+tests :: TestTree
20tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
20tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
21 fromAssertions "fileType"
21 fromAssertions "fileType"
22 [ Markdown @=? fileType "index.md"
22 [ Markdown @=? fileType "index.md"
tests/Hakyll/Web/Template/Context/Tests.hs+44−8
@@ -6,24 +6,26 @@ module Hakyll.Web.Template.Context.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Framework (Test, testGroup)
9+import Test.Tasty (TestTree, testGroup)
10−import Test.Framework.Providers.HUnit (testCase)
10+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
11−import Test.HUnit (Assertion, (@=?))
12
11
13
12
14--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
15import Hakyll.Core.Compiler
14import Hakyll.Core.Compiler
16import Hakyll.Core.Identifier
15import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
17import Hakyll.Core.Provider
17import Hakyll.Core.Provider
18−import Hakyll.Core.Store (Store)
18+import Hakyll.Core.Store (Store)
19import Hakyll.Web.Template.Context
19import Hakyll.Web.Template.Context
20+import Hakyll.Web.Template.Internal
20import TestSuite.Util
21import TestSuite.Util
21
22
22
23
23--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
24−tests :: Test
25+tests :: TestTree
25−tests = testGroup "Hakyll.Core.Template.Context.Tests"
26+tests = testGroup "Hakyll.Web.Template.Context.Tests"
26 [ testCase "testDateField" testDateField
27 [ testCase "testDateField" testDateField
28+ , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
27 ]
29 ]
28
30
29
31
@@ -42,6 +44,15 @@ testDateField = do
42 dateField "date" "%B %e, %Y"
44 dateField "date" "%B %e, %Y"
43 date2 @=? "August 26, 2010"
45 date2 @=? "August 26, 2010"
44
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"
45 cleanTestEnv
56 cleanTestEnv
46
57
47
58
@@ -54,6 +65,31 @@ testContextDone store provider identifier key context =
54 cf <- unContext context key [] item
65 cf <- unContext context key [] item
55 case cf of
66 case cf of
56 StringField str -> return str
67 StringField str -> return str
57− ListField _ _ -> error $
68+ _ -> error $
58 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
69 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
59− "Didn't expect ListField"
70+ "expected StringField"
71+
72+--------------------------------------------------------------------------------
73+
74+testOuerLoopContextAccess :: Assertion
75+testOuerLoopContextAccess = do
76+ store <- newTestStore
77+ provider <- newTestProvider store
78+ test store provider ctx "baz"
79+ test store provider (ctx' <> ctx) "not baz"
80+ test store provider (ctx <> ctx') "baz"
81+
82+ cleanTestEnv
83+ where
84+ tpl = readTemplate "$for(foo)$$for(bar)$$qux$$endfor$$endfor$"
85+ ctx = mconcat [
86+ field "qux" $ const $ return "baz"
87+ , listField "foo" (listField "bar" mempty $ return [mockItem])
88+ $ return [mockItem]
89+ ]
90+ ctx' = field "qux" $ const $ return "not baz"
91+ mockItem = Item "" ()
92+ test store provider context str = do
93+ str' <- testCompilerDone store provider ""
94+ $ applyTemplate tpl context mockItem
95+ str @=? itemBody str'
tests/Hakyll/Web/Template/Tests.hs+93−27
@@ -1,22 +1,23 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
3module Hakyll.Web.Template.Tests
4module Hakyll.Web.Template.Tests
4 ( tests
5 ( tests
5 ) where
6 ) where
6
7
7
8
8--------------------------------------------------------------------------------
9--------------------------------------------------------------------------------
9−import Data.Monoid (mconcat)
10−import Test.Framework (Test, testGroup)
10+import Test.Tasty (TestTree, testGroup)
11−import Test.Framework.Providers.HUnit (testCase)
11+import Test.Tasty.HUnit (Assertion, assertBool, testCase,
12−import Test.HUnit (Assertion, (@=?), (@?=))
12+ (@=?), (@?=))
13
13
14+import Data.Either (isLeft)
14
15
15--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
16import Hakyll.Core.Compiler
17import Hakyll.Core.Compiler
18+import Hakyll.Core.Identifier
17import Hakyll.Core.Item
19import Hakyll.Core.Item
18import Hakyll.Core.Provider
20import Hakyll.Core.Provider
19−import Hakyll.Web.Pandoc
20import Hakyll.Web.Template
21import Hakyll.Web.Template
21import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Internal
23import Hakyll.Web.Template.Internal
@@ -25,37 +26,83 @@ import TestSuite.Util
25
26
26
27
27--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
28−tests :: Test
29+tests :: TestTree
29−tests = testGroup "Hakyll.Core.Template.Tests" $ concat
30+tests = testGroup "Hakyll.Web.Template.Tests" $ concat
30− [ [ testCase "case01" case01
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")
31 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
34 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
32 ]
35 ]
33
36
34− , fromAssertions "readTemplate"
37+ , fromAssertions "parseTemplate"
35− [ Template [Chunk "Hello ", Expr (Call "guest" [])]
38+ [ Right [Chunk "Hello ", Expr (Call "guest" [])]
36− @=? readTemplate "Hello $guest()$"
39+ @=? parse "Hello $guest()$"
37− , Template
38− [If (Call "a" [StringLiteral "bar"])
40+ , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
39− (Template [Chunk "foo"])
40− Nothing]
41− @=? readTemplate "$if(a(\"bar\"))$foo$endif$"
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$"
42 ]
86 ]
87+
88+ , [testCase "embeddedTemplate" testEmbeddedTemplate]
43 ]
89 ]
90+ where
91+ parse = parseTemplateElemsFile ""
44
92
45
93
46--------------------------------------------------------------------------------
94--------------------------------------------------------------------------------
47−case01 :: Assertion
95+test :: (Identifier, Identifier, Identifier) -> Assertion
48−case01 = do
96+test (outf, tplf, itemf) = do
49 store <- newTestStore
97 store <- newTestStore
50 provider <- newTestProvider store
98 provider <- newTestProvider store
51
99
52− out <- resourceString provider "template.html.out"
100+ out <- resourceString provider outf
53− tpl <- testCompilerDone store provider "template.html" $
101+ tpl <- testCompilerDone store provider tplf templateBodyCompiler
54− templateCompiler
55− item <- testCompilerDone store provider "example.md" $
102+ item <- testCompilerDone store provider itemf $
103+ getResourceBody
104+ >>= renderParagraphs
56− pandocCompiler >>= applyTemplate (itemBody tpl) testContext
105+ >>= applyTemplate (itemBody tpl) testContext
57−
58− writeFile "foo" (itemBody item)
59
106
60 out @=? itemBody item
107 out @=? itemBody item
61 cleanTestEnv
108 cleanTestEnv
@@ -71,7 +118,6 @@ testContext = mconcat
71 return [n1, n2]
118 return [n1, n2]
72 , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
119 , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
73 ]
120 ]
74− where
75
121
76
122
77--------------------------------------------------------------------------------
123--------------------------------------------------------------------------------
@@ -79,6 +125,8 @@ testApplyJoinTemplateList :: Assertion
79testApplyJoinTemplateList = do
125testApplyJoinTemplateList = do
80 store <- newTestStore
126 store <- newTestStore
81 provider <- newTestProvider store
127 provider <- newTestProvider store
128+ tpl <- testCompilerDone store provider "tpl" $
129+ compileTemplateItem (Item "tpl" "<b>$body$</b>")
82 str <- testCompilerDone store provider "item3" $
130 str <- testCompilerDone store provider "item3" $
83 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
131 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
84
132
@@ -87,4 +135,22 @@ testApplyJoinTemplateList = do
87 where
135 where
88 i1 = Item "item1" "Hello"
136 i1 = Item "item1" "Hello"
89 i2 = Item "item2" "World"
137 i2 = Item "item2" "World"
90− tpl = Template [Chunk "<b>", Expr (Ident "body"), Chunk "</b>"]
138+
139+
140+--------------------------------------------------------------------------------
141+embeddedTemplate :: Template
142+embeddedTemplate = $(embedTemplate "tests/data/embed.html")
143+
144+--------------------------------------------------------------------------------
145+testEmbeddedTemplate :: Assertion
146+testEmbeddedTemplate = do
147+ store <- newTestStore
148+ provider <- newTestProvider store
149+ str <- testCompilerDone store provider "item3" $
150+ applyTemplate embeddedTemplate defaultContext item
151+
152+ itemBody str @?= "<p>Hello, world</p>\n"
153+ cleanTestEnv
154+ where
155+ item = Item "item1" "Hello, world"
156+
tests/TestSuite.hs+11−2
@@ -1,11 +1,12 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2module Main
3module Main
3 ( main
4 ( main
4 ) where
5 ) where
5
6
6
7
7--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
8−import Test.Framework (defaultMain)
9+import Test.Tasty (defaultMain, testGroup)
9
10
10
11
11--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
@@ -19,16 +20,20 @@ import qualified Hakyll.Core.Runtime.Tests
19import qualified Hakyll.Core.Store.Tests
20import qualified Hakyll.Core.Store.Tests
20import qualified Hakyll.Core.UnixFilter.Tests
21import qualified Hakyll.Core.UnixFilter.Tests
21import qualified Hakyll.Core.Util.String.Tests
22import qualified Hakyll.Core.Util.String.Tests
23+import qualified Hakyll.Web.CompressCss.Tests
22import qualified Hakyll.Web.Html.RelativizeUrls.Tests
24import qualified Hakyll.Web.Html.RelativizeUrls.Tests
23import qualified Hakyll.Web.Html.Tests
25import qualified Hakyll.Web.Html.Tests
26+#ifdef USE_PANDOC
24import qualified Hakyll.Web.Pandoc.FileType.Tests
27import qualified Hakyll.Web.Pandoc.FileType.Tests
28+#endif
25import qualified Hakyll.Web.Template.Context.Tests
29import qualified Hakyll.Web.Template.Context.Tests
26import qualified Hakyll.Web.Template.Tests
30import qualified Hakyll.Web.Template.Tests
31+import qualified Hakyll.Web.Tags.Tests
27
32
28
33
29--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
30main :: IO ()
35main :: IO ()
31−main = defaultMain
36+main = defaultMain $ testGroup "Hakyll"
32 [ Hakyll.Core.Dependencies.Tests.tests
37 [ Hakyll.Core.Dependencies.Tests.tests
33 , Hakyll.Core.Identifier.Tests.tests
38 , Hakyll.Core.Identifier.Tests.tests
34 , Hakyll.Core.Provider.Metadata.Tests.tests
39 , Hakyll.Core.Provider.Metadata.Tests.tests
@@ -39,9 +44,13 @@ main = defaultMain
39 , Hakyll.Core.Store.Tests.tests
44 , Hakyll.Core.Store.Tests.tests
40 , Hakyll.Core.UnixFilter.Tests.tests
45 , Hakyll.Core.UnixFilter.Tests.tests
41 , Hakyll.Core.Util.String.Tests.tests
46 , Hakyll.Core.Util.String.Tests.tests
47+ , Hakyll.Web.CompressCss.Tests.tests
42 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
48 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
43 , Hakyll.Web.Html.Tests.tests
49 , Hakyll.Web.Html.Tests.tests
50+#ifdef USE_PANDOC
44 , Hakyll.Web.Pandoc.FileType.Tests.tests
51 , Hakyll.Web.Pandoc.FileType.Tests.tests
52+#endif
53+ , Hakyll.Web.Tags.Tests.tests
45 , Hakyll.Web.Template.Context.Tests.tests
54 , Hakyll.Web.Template.Context.Tests.tests
46 , Hakyll.Web.Template.Tests.tests
55 , Hakyll.Web.Template.Tests.tests
47 ]
56 ]
tests/TestSuite/Util.hs+36−15
@@ -6,36 +6,38 @@ module TestSuite.Util
6 , newTestProvider
6 , newTestProvider
7 , testCompiler
7 , testCompiler
8 , testCompilerDone
8 , testCompilerDone
9+ , testCompilerError
9 , testConfiguration
10 , testConfiguration
10 , cleanTestEnv
11 , cleanTestEnv
12+ , renderParagraphs
11 ) where
13 ) where
12
14
13
15
14--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
15−import Data.List (intercalate)
17+import Data.List (intercalate, isInfixOf)
16−import Data.Monoid (mempty)
18+import Data.Monoid (mempty)
17−import qualified Data.Set as S
19+import qualified Data.Set as S
18−import Test.Framework
20+import Test.Tasty
19−import Test.Framework.Providers.HUnit
21+import Test.Tasty.HUnit
20−import Test.HUnit hiding (Test)
21−import Text.Printf (printf)
22+import Text.Printf (printf)
22
23
23
24
24--------------------------------------------------------------------------------
25--------------------------------------------------------------------------------
25import Hakyll.Core.Compiler.Internal
26import Hakyll.Core.Compiler.Internal
26import Hakyll.Core.Configuration
27import Hakyll.Core.Configuration
27import Hakyll.Core.Identifier
28import Hakyll.Core.Identifier
28−import qualified Hakyll.Core.Logger as Logger
29+import qualified Hakyll.Core.Logger as Logger
29import Hakyll.Core.Provider
30import Hakyll.Core.Provider
30−import Hakyll.Core.Store (Store)
31+import Hakyll.Core.Store (Store)
31−import qualified Hakyll.Core.Store as Store
32+import qualified Hakyll.Core.Store as Store
32import Hakyll.Core.Util.File
33import Hakyll.Core.Util.File
34+import Hakyll.Core.Item
33
35
34
36
35--------------------------------------------------------------------------------
37--------------------------------------------------------------------------------
36fromAssertions :: String -- ^ Name
38fromAssertions :: String -- ^ Name
37 -> [Assertion] -- ^ Cases
39 -> [Assertion] -- ^ Cases
38− -> [Test] -- ^ Result tests
40+ -> [TestTree] -- ^ Result tests
39fromAssertions name =
41fromAssertions name =
40 zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
42 zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
41
43
@@ -77,14 +79,23 @@ testCompilerDone store provider underlying compiler = do
77 result <- testCompiler store provider underlying compiler
79 result <- testCompiler store provider underlying compiler
78 case result of
80 case result of
79 CompilerDone x _ -> return x
81 CompilerDone x _ -> return x
80− CompilerError e -> error $
82+ CompilerError e -> fail $
81 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
82− " threw: " ++ intercalate "; " e
84+ " threw: " ++ intercalate "; " (compilerErrorMessages e)
83− CompilerRequire i _ -> error $
85+ CompilerRequire i _ -> fail $
84 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
86 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
85 " requires: " ++ show i
87 " requires: " ++ show i
88+ CompilerSnapshot _ _ -> fail
89+ "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
86
90
87−
91+testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()
92+testCompilerError store provider underlying compiler expectedMessage = do
93+ result <- testCompiler store provider underlying compiler
94+ case result of
95+ CompilerError e ->
96+ any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?
97+ "Expecting '" ++ expectedMessage ++ "' error"
98+ _ -> assertFailure "Expecting CompilerError"
88
99
89--------------------------------------------------------------------------------
100--------------------------------------------------------------------------------
90testConfiguration :: Configuration
101testConfiguration :: Configuration
@@ -102,3 +113,13 @@ cleanTestEnv = do
102 removeDirectory $ destinationDirectory testConfiguration
113 removeDirectory $ destinationDirectory testConfiguration
103 removeDirectory $ storeDirectory testConfiguration
114 removeDirectory $ storeDirectory testConfiguration
104 removeDirectory $ tmpDirectory testConfiguration
115 removeDirectory $ tmpDirectory testConfiguration
116+
117+
118+--------------------------------------------------------------------------------
119+-- | like 'Hakyll.Web.Pandoc.renderPandoc'
120+-- | but allowing to test without the @usePandoc@ flag
121+renderParagraphs :: Item String -> Compiler (Item String)
122+renderParagraphs = withItemBody (return
123+ . intercalate "\n" -- no trailing line
124+ . map (("<p>"++) . (++"</p>"))
125+ . lines)
tests/data/embed.htmlnew file+1
@@ -0,0 +1 @@
1+<p>$body$</p>
tests/data/example.md.metadata+2
@@ -1,3 +1,5 @@
1external: External data
1external: External data
2date: 2012-10-22 14:35:24
2date: 2012-10-22 14:35:24
3subblog: food
3subblog: food
4+intfield: 42
5+numfield: 3.14
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/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/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+
web/css/syntax.css+61−18
@@ -1,18 +1,61 @@
1−/* Generated by pandoc. */
1+a.sourceLine { display: inline-block; line-height: 1.25; }
2−table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
2+a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
3− { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
3+a.sourceLine:empty { height: 1.2em; }
4−td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
4+.sourceCode { overflow: visible; }
5−td.sourceCode { padding-left: 5px; }
5+code.sourceCode { white-space: pre; position: relative; }
6−.sourceCode span.kw { color: #007020; font-weight: bold; }
6+div.sourceCode { margin: 1em 0; }
7+pre.sourceCode { margin: 0; }
8+@media screen {
9+div.sourceCode { overflow: auto; }
10+}
11+@media print {
12+code.sourceCode { white-space: pre-wrap; }
13+a.sourceLine { text-indent: -1em; padding-left: 1em; }
14+}
15+pre.numberSource a.sourceLine
16+ { position: relative; left: -4em; }
17+pre.numberSource a.sourceLine::before
18+ { content: attr(title);
19+ position: relative; left: -1em; text-align: right; vertical-align: baseline;
20+ border: none; pointer-events: all; display: inline-block;
21+ -webkit-touch-callout: none; -webkit-user-select: none;
22+ -khtml-user-select: none; -moz-user-select: none;
23+ -ms-user-select: none; user-select: none;
24+ padding: 0 4px; width: 4em;
25+ color: #aaaaaa;
26+ }
27+pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
28+div.sourceCode
29+ { }
30+@media screen {
31+a.sourceLine::before { text-decoration: underline; }
32+}
33+code span.al { color: #ff0000; font-weight: bold; } /* Alert */
34+code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
35+code span.at { color: #7d9029; } /* Attribute */
36+code span.bn { color: #40a070; } /* BaseN */
37+code span.bu { } /* BuiltIn */
38+code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
39+code span.ch { color: #4070a0; } /* Char */
40+code span.cn { color: #880000; } /* Constant */
41+code span.co { color: #60a0b0; font-style: italic; } /* Comment */
42+code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
43+code span.do { color: #ba2121; font-style: italic; } /* Documentation */
7−.sourceCode span.dt { color: #902000; }
44+code span.dt { color: #902000; } /* DataType */
8−.sourceCode span.dv { color: #40a070; }
45+code span.dv { color: #40a070; } /* DecVal */
9−.sourceCode span.bn { color: #40a070; }
46+code span.er { color: #ff0000; font-weight: bold; } /* Error */
47+code span.ex { } /* Extension */
10−.sourceCode span.fl { color: #40a070; }
48+code span.fl { color: #40a070; } /* Float */
11−.sourceCode span.ch { color: #4070a0; }
12−.sourceCode span.st { color: #4070a0; }
13−.sourceCode span.co { color: #60a0b0; font-style: italic; }
14−.sourceCode span.ot { color: #007020; }
15−.sourceCode span.al { color: red; font-weight: bold; }
16−.sourceCode span.fu { color: #06287e; }
49+code span.fu { color: #06287e; } /* Function */
17−.sourceCode span.re { }
50+code span.im { } /* Import */
18−.sourceCode span.er { color: red; font-weight: bold; }
51+code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
52+code span.kw { color: #007020; font-weight: bold; } /* Keyword */
53+code span.op { color: #666666; } /* Operator */
54+code span.ot { color: #007020; } /* Other */
55+code span.pp { color: #bc7a00; } /* Preprocessor */
56+code span.sc { color: #4070a0; } /* SpecialChar */
57+code span.ss { color: #bb6688; } /* SpecialString */
58+code span.st { color: #4070a0; } /* String */
59+code span.va { color: #19177c; } /* Variable */
60+code span.vs { color: #4070a0; } /* VerbatimString */
61+code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
web/examples.markdown+97−28
@@ -9,12 +9,19 @@ interesting resource to learn from as well. If you're using Hakyll for your
9site, and the source code is available, please notify me so I can add you to
9site, and the source code is available, please notify me so I can add you to
10this list. This list has no particular ordering.
10this list. This list has no particular ordering.
11
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+
12## Hakyll 4
17## Hakyll 4
13
18
14- <http://jaspervdj.be/>,
19- <http://jaspervdj.be/>,
15 [source](https://github.com/jaspervdj/jaspervdj)
20 [source](https://github.com/jaspervdj/jaspervdj)
16- <http://www.imagination-land.org/>,
21- <http://www.imagination-land.org/>,
17 [source](https://github.com/Keruspe/blog/)
22 [source](https://github.com/Keruspe/blog/)
23+- <https://bananasandlenses.net/>,
24+ [source](https://github.com/denisshevchenko/bananasandlenses)
18- <http://blog.clement.delafargue.name/>,
25- <http://blog.clement.delafargue.name/>,
19 [source](https://github.com/divarvel/blog)
26 [source](https://github.com/divarvel/blog)
20- <http://bneijt.nl>,
27- <http://bneijt.nl>,
@@ -29,8 +36,6 @@ this list. This list has no particular ordering.
29 [source](https://github.com/dannysu/hakyll-blog)
36 [source](https://github.com/dannysu/hakyll-blog)
30- <http://meta.plasm.us/>,
37- <http://meta.plasm.us/>,
31 [source](https://github.com/travisbrown/metaplasm)
38 [source](https://github.com/travisbrown/metaplasm)
32−- <http://www.web2day-nantes.org/>,
33− [source](https://github.com/CompanyCampus/web2day2013)
34- <http://workforpizza.com/>,
39- <http://workforpizza.com/>,
35 [source](https://github.com/irneh/workforpizza)
40 [source](https://github.com/irneh/workforpizza)
36- <http://poleiro.info/>,
41- <http://poleiro.info/>,
@@ -40,9 +45,7 @@ this list. This list has no particular ordering.
40- <http://www.regisfoucault.com/>,
45- <http://www.regisfoucault.com/>,
41 [source](https://github.com/regisfoucault/blog)
46 [source](https://github.com/regisfoucault/blog)
42- <http://www.chaoxuprime.com/>,
47- <http://www.chaoxuprime.com/>,
43− [source](https://github.com/Mgccl/blog)
48+ [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
44−- <https://benjeffrey.com/>,
45− [source](https://github.com/jeffbr13/benjeffrey.com)
46- <http://www.skybluetrades.net/>,
49- <http://www.skybluetrades.net/>,
47 [source](https://github.com/ian-ross/blog)
50 [source](https://github.com/ian-ross/blog)
48- <http://chromaticleaves.com/>,
51- <http://chromaticleaves.com/>,
@@ -53,32 +56,22 @@ this list. This list has no particular ordering.
53 [source](https://github.com/blaenk/blaenk.github.io)
56 [source](https://github.com/blaenk/blaenk.github.io)
54- <https://xinitrc.de/>,
57- <https://xinitrc.de/>,
55 [source](https://github.com/xinitrc/xinitrc.de)
58 [source](https://github.com/xinitrc/xinitrc.de)
56−- <http://blog.darkfox.id.au/>,
57− [source](http://hub.darcs.net/DarkFox/DarkFox-blog)
58- <http://nickcharlton.net/>,
59- <http://nickcharlton.net/>,
59 [source](https://github.com/nickcharlton/nickcharlton.net)
60 [source](https://github.com/nickcharlton/nickcharlton.net)
60- <http://scr.stunts.hu/>,
61- <http://scr.stunts.hu/>,
61 [literate source](http://scr.stunts.hu/hakyll.html)
62 [literate source](http://scr.stunts.hu/hakyll.html)
62- <http://www.eanalytica.com/>,
63- <http://www.eanalytica.com/>,
63 [literate source](http://www.eanalytica.com/site/)
64 [literate source](http://www.eanalytica.com/site/)
64−- <http://dikmax.name/>,
65− [source](https://github.com/dikmax/dikmax.name)
66- <http://cse.iitk.ac.in/users/ppk>,
65- <http://cse.iitk.ac.in/users/ppk>,
67 [source](https://github.com/piyush-kurur-pages/website)
66 [source](https://github.com/piyush-kurur-pages/website)
68- <http://web.engr.oregonstate.edu/~walkiner/>,
67- <http://web.engr.oregonstate.edu/~walkiner/>,
69 [source](https://github.com/walkie/WebPage)
68 [source](https://github.com/walkie/WebPage)
70- <http://techblog.rosedu.org/>,
69- <http://techblog.rosedu.org/>,
71 [source](https://github.com/rosedu/techblog)
70 [source](https://github.com/rosedu/techblog)
72−- <http://abizern.org>,
73− [source](https://github.com/Abizern/hblog)
74- <http://freizl.github.io>,
71- <http://freizl.github.io>,
75 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
72 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
76−- <http://covariant.me>,
77− [source](http://hub.darcs.net/co-dan/website)
78- <http://www.gwern.net/>,
73- <http://www.gwern.net/>,
79 [source](https://github.com/gwern/gwern.net)
74 [source](https://github.com/gwern/gwern.net)
80−- <http://www.rohanjain.in/>,
81− [source](https://github.com/crodjer/rohanjain.in)
82- <http://www.corentindupont.info/>,
75- <http://www.corentindupont.info/>,
83 [source](https://github.com/cdupont/CorentinDupont-WebPage)
76 [source](https://github.com/cdupont/CorentinDupont-WebPage)
84- <http://jelv.is>,
77- <http://jelv.is>,
@@ -87,8 +80,8 @@ this list. This list has no particular ordering.
87 [source](https://github.com/plaimi/www)
80 [source](https://github.com/plaimi/www)
88- <http://duplode.github.io/>,
81- <http://duplode.github.io/>,
89 [source](https://github.com/duplode/duplode.github.io/tree/sources)
82 [source](https://github.com/duplode/duplode.github.io/tree/sources)
90−- <http://maseek.codes>,
83+- <http://tzemanovic.github.io/>,
91− [source](https://github.com/maseek/maseek-codes)
84+ [source](https://github.com/tzemanovic/tzemanovic)
92- <http://listx.github.io>,
85- <http://listx.github.io>,
93 [source](https://github.com/listx/listx_blog)
86 [source](https://github.com/listx/listx_blog)
94- <http://kyle.marek-spartz.org>,
87- <http://kyle.marek-spartz.org>,
@@ -99,18 +92,98 @@ this list. This list has no particular ordering.
99 [source](https://github.com/rbros/reichertbrothers.com)
92 [source](https://github.com/rbros/reichertbrothers.com)
100- <http://alemedeiros.sdf.org>,
93- <http://alemedeiros.sdf.org>,
101 [source](https://github.com/alemedeiros/homepage)
94 [source](https://github.com/alemedeiros/homepage)
102−- <http://blog.ssanj.net/>,
103− [source](https://github.com/ssanj/babyloncandle)
104−- <http://ruudvanasseldonk.com/>,
105− [source](https://github.com/ruud-v-a/ruudvanasseldonk.com)
106- <http://www.haskell.mn>,
95- <http://www.haskell.mn>,
107 [source](https://github.com/HaskellMN/www.haskell.mn)
96 [source](https://github.com/HaskellMN/www.haskell.mn)
108- <https://axiomatic.neophilus.net/>,
97- <https://axiomatic.neophilus.net/>,
109 [source](https://github.com/Libbum/AxiomaticSemantics)
98 [source](https://github.com/Libbum/AxiomaticSemantics)
110−- <https://tesser.org/>,
99+- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
111 [source](https://github.com/fractalcat/tesser.org)
100 [source](https://github.com/fractalcat/tesser.org)
112−- <https://wei2912.github.io/>,
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/>,
113− [source](https://github.com/wei2912/blog_src)
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)
114
187
115## Hakyll 3.X
188## Hakyll 3.X
116
189
@@ -130,8 +203,6 @@ this list. This list has no particular ordering.
130 [source](https://github.com/marcmo/blog.coldflake)
203 [source](https://github.com/marcmo/blog.coldflake)
131- <http://deepak.jois.name>,
204- <http://deepak.jois.name>,
132 [source](https://github.com/deepakjois/website)
205 [source](https://github.com/deepakjois/website)
133−- <http://sigkill.dk/>,
134− [literate source](http://sigkill.dk/programs/sigkill.html)
135- <http://orftz.com>,
206- <http://orftz.com>,
136 [source](https://github.com/orftz/orftz.com)
207 [source](https://github.com/orftz/orftz.com)
137- <http://citationneeded.me/>,
208- <http://citationneeded.me/>,
@@ -140,7 +211,5 @@ this list. This list has no particular ordering.
140 [source](http://gitorious.org/shakthimaan-blog)
211 [source](http://gitorious.org/shakthimaan-blog)
141- <http://www.alfredodinapoli.com/>,
212- <http://www.alfredodinapoli.com/>,
142 [source](https://github.com/CharlesStain/alfredodinapoli.com)
213 [source](https://github.com/CharlesStain/alfredodinapoli.com)
143−- <http://datahackermd.com>,
144− [source](http://github.com/akshayjshah/datahackermd)
145- <http://michaelxavier.net>,
214- <http://michaelxavier.net>,
146 [source](https://github.com/michaelxavier/michaelxavier.net)
215 [source](https://github.com/michaelxavier/michaelxavier.net)
web/index.markdown+4−2
@@ -31,8 +31,10 @@ and TeX support, including syntax highlighting and other goodies.
31
31
32# Getting Started
32# Getting Started
33
33
34−You can get the latest version from hackage using `cabal install hakyll`. Then,
34+You can get the latest version from hackage using `cabal install hakyll`, or
35−you can:
35+using [stack] by using `stack install hakyll`. Then, you can:
36+
37+[stack]: http://www.haskellstack.org/
36
38
37- read the [tutorials](/tutorials.html);
39- read the [tutorials](/tutorials.html);
38- mail the [google discussion group](http://groups.google.com/group/hakyll);
40- mail the [google discussion group](http://groups.google.com/group/hakyll);
web/libraries.markdown+1
@@ -18,3 +18,4 @@ Here is a list of the ones I know of (I would be happy to hear about others).
18 a conversion tool from other blog engines to Hakyll.
18 a conversion tool from other blog engines to Hakyll.
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
20 Hakyll wrapper for the Elm compiler.
20 Hakyll wrapper for the Elm compiler.
21+- [hakyll-sass](http://hackage.haskell.org/package/hakyll-sass), a compiler for SASS templates.
web/releases.markdowndeleted−234
@@ -1,234 +0,0 @@
1−---
2−title: Releases
3−---
4−
5−# Releases
6−
7−## Hakyll 4.6.2.0
8−
9−- Loosen `binary` dependency
10−- Make dependency handling more granular so you can depend on specific snapshots
11− of an item
12−
13−## Hakyll 4.6.1.0
14−
15−- Bump `fsnotify` and `pandoc-citeproc` dependencies
16−- Rewrite polling code a bit
17−
18−## Hakyll 4.6.0.0
19−
20−- Added `listFieldWith` function
21−- Improved `rulesExtraDependencies` behaviour
22−- Changed function syntax in templates from `$foo arg1 arg2$` to
23− `$foo("arg1", "arg2")$`
24−- Support parsing date from directory names in addition to file names
25−
26−## Hakyll 4.5.5.0
27−
28−- Fix Binary instances for `pandoc` and `pandoc-citeproc`
29−- Fix `network-uri` dependency issue
30−
31−## Hakyll 4.5.4.0
32−
33−- Fix issue with HTML entities when running `withUrls` and `demoteHeaders`.
34−- Generate a cabal file for the initialised site.
35−- Add pagination support.
36−
37−## Hakyll 4.5.3.0
38−
39−- Bump Pandoc to 1.12.4 to include the org-mode reader.
40−
41−## Hakyll 4.5.2.0
42−
43−- Fix rebuilding everything issue with latest directory (contribution by Jorge
44− Israel Peña)
45−- Fix issue with `toSiteRoot` (contribution by Izzy Cecil)
46−- Fix issue with tag dependencies, slightly improve caching
47−
48−## Hakyll 4.5.0.0
49−
50−- Fix issue with syntax highlighting and line numbers (contribution by Adelbert
51− Chang)
52−- Improve documentation for `Context` (contribution by Daniil Frumin)
53−- Added `IsString` instance for `Template`
54−- Added the `pandocCompilerWithTransformM` function (contribution by Daniil
55− Frumin)
56−- Make `./site check` return the right exit code (contribution by Andres Loeh)
57−- Use OS threads to make `./site watch` work nicely on Windows (contribution by
58− Simonas Kazlauskas)
59−- Make the `unixFilter` function work better on windows by calling `shell`
60− (contribution by Collin J. Doering)
61−- Add a command-line flag to bind on a user-specified host (contribution by
62− chrisdotcode)
63−
64−## Hakyll 4.4.3.0
65−
66−- Fix issue when using `metadataRoute` after other custom routes
67−
68−## Hakyll 4.4.2.0
69−
70−- Fix issue where Hakyll would not detect a change if a `.metadata` file was
71− deleted
72−
73−## Hakyll 4.4.1.0
74−
75−- Use Pandoc 1.12 highlighting by default
76−
77−## Hakyll 4.4.0.0
78−
79−- Update to work with Pandoc 1.12. This changes the type of `readPandocBibilio`:
80− the `CSL` argument is no longer optional (contribution by Jorge Israel Peña)
81−
82−- Fix incorrect output of `toSiteRoot` on windows (contribution by Saeid
83− Al-Wazzan)
84−
85−- Add a preview port option to `Configuration` (contribution by Jorge Israel
86− Peña)
87−
88−- Add `watch` command that polls for changes but does not necessarily launch a
89− server (contribution by Eric Stolten)
90−
91−- Generalise type of `metadataField`
92−
93−- Fix issue where metadata was not correctly loaded when using versions
94−
95−## Hakyll 4.3.3.0
96−
97−- Re-add the `functionField` function
98−
99−## Hakyll 4.3.2.0
100−
101−- Re-add the `mapContext` function
102−
103−- Unescape internal URLs when using `./site check` (contribution by Marc-Antoine
104− Perennou)
105−
106−## Hakyll 4.3.1.0
107−
108−- Make teasers undefined if no `<!--more-->` comment is found
109−
110−- Sanitize tag URLs (contribution by Simonas Kazlauskas)
111−
112−## Hakyll 4.3.0.0
113−
114−- Add conditionals, partials and for loops to the template system (includes a
115− contribution by Ivan N. Veselov)
116−
117−- Improvements to the preview functionality on windows (contribution by Jorge
118− Israel Peña)
119−
120−- Add pagination support (contribution by Anton Dubovik)
121−
122−- Slight speedup for the Hakyll cache (contribution by justnoxx)
123−
124−- Add teaser functionality (contribution by Ivan N. Veselov)
125−
126−- Make `./site check` work with scheme-relative URLs (contribution by Simonas
127− Kazlauskas)
128−
129−- The `./site deploy` command can now be customized with Haskell code
130− (contribution by Samuel Tardieu)
131−
132−- Use `hsnotify` for proper polling instead of sleep loop on all platforms
133− (contribution by Simonas Kazlauskas)
134−
135−- More useful debug info available
136−
137−## Hakyll 4.2.2.0
138−
139−- Fix issue with `Alternative` instance of `Compiler`
140−
141−## Hakyll 4.2.1.1
142−
143−*March 9, 2013*
144−
145−- Make `http-conduit` dependency optional by adding a `checkExternal` cabal flag
146−
147−## Hakyll 4.2.1.0
148−
149−*March 7, 2013*
150−
151−- Fix issue where `copyFileCompiler` ignored `providerDirectory`
152−
153−## Hakyll 4.2.0.0
154−
155−*March 7, 2013*
156−
157−- Read second extension for `.lhs`, e.g. `.md.lhs` or `.tex.lhs` (contribution
158− by Alexander Vershilov)
159−
160−- Speedup initialization by using modification times instead of hashing files
161−
162−- Speedup initialization with a rewritten resource provider
163−
164−- Fix `./site check` not working with sites that require a user agent (e.g.
165− <http://www.wikipedia.org/>)
166−
167−- Change `chronological` and `recentFirst` to actually look at the dates of
168− items. This changes their types from:
169−
170− chronological, recentFirst :: [Item a] -> [Item a]
171−
172− to:
173−
174− chronological, recentFirst
175− :: MonadMetadata m => [Item a] -> m [Item a]
176−
177− (contribution by Simonas Kazlauskas)
178−
179−- Add `metadataRoute`, so it is now possible to use metadata when determining
180− routes
181−
182−- Improve metadata parser for multiline metadata fields (contribution by Peter
183− Jones)
184−
185−- Add the `getMetadataField` utility
186−
187−## Hakyll 4.1.4.0
188−
189−*January 26, 2013*
190−
191−- Export the flexible `renderTags` function
192−
193−## Hakyll 4.1.3.0
194−
195−*January 26, 2013*
196−
197−- Export the constructor of the `Tags` datatype
198−
199−## Hakyll 4.1.2.0
200−
201−*January 20, 2013*
202−
203−- Fix an issue where a dependency cycle would lead to infinite recursion/stack
204− overflow
205−
206−## Hakyll 4.1.1.0
207−
208−*January 20, 2013*
209−
210−- Fix an issue regarding `relativizeUrls` expanding `<meta />` to
211− `<meta></meta>`
212−
213−## Hakyll 4.1.0.0
214−
215−*January 20, 2013*
216−
217−Update to use Pandoc 1.10, this requires changes to your `site.hs` if you're
218−using custom Pandoc options or the `Hakyll.Web.Pandoc.Biblio` module.
219−
220−- `defaultHakyllParserState` renamed to `defaultHakyllReaderOptions`
221−
222−- The type of `readPandocBiblio` changed
223−
224−Because of the many changes, this release is no longer compatible with Pandoc
225−1.9.
226−
227−## Hakyll 4.0.0.0
228−
229−*January 16, 2013*
230−
231−The Initial release of Hakyll 4, see
232−[this blogpost](http://jaspervdj.be/posts/2013-01-16-hakyll-4.0.html) and
233−[the migration guide](/tutorials/hakyll-3-to-hakyll4-migration-guide.html) for
234−an overview of changes.
web/site.hs+29−32
@@ -1,20 +1,23 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3−import Control.Applicative ((<$>))
3+import Control.Arrow (second)
4−import Control.Arrow (second)
5−import Control.Monad (forM_)
4+import Control.Monad (filterM, forM_)
6−import Data.Char (isDigit)
7−import Data.List (isPrefixOf, partition, sortBy)
5+import Data.List (isPrefixOf, sortBy)
8−import Data.Monoid (mappend)
6+import Data.Monoid ((<>))
9−import Data.Ord (comparing)
7+import Data.Ord (comparing)
10import Hakyll
8import Hakyll
9+import System.Directory (copyFile)
11−import System.FilePath (dropTrailingPathSeparator, splitPath)
10+import System.FilePath (dropTrailingPathSeparator, splitPath)
12import Text.Pandoc
11import Text.Pandoc
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16main :: IO ()
15main :: IO ()
17main = hakyllWith config $ do
16main = hakyllWith config $ do
17+ -- Copy CHANGELOG.md here.
18+ preprocess $ copyFile "../CHANGELOG.md" "releases.markdown"
19+
20+ -- CSS
18 match "css/*" $ do
21 match "css/*" $ do
19 route idRoute
22 route idRoute
20 compile compressCssCompiler
23 compile compressCssCompiler
@@ -53,23 +56,11 @@ main = hakyllWith config $ do
53 create ["tutorials.html"] $ do
56 create ["tutorials.html"] $ do
54 route idRoute
57 route idRoute
55 compile $ do
58 compile $ do
56− tutorials <- loadAll "tutorials/*"
59+ ctx <- tutorialsCtx <$>
57− itemTpl <- loadBody "templates/tutorial-item.html"
58− let (series, articles) = partitionTutorials $
59− sortBy (comparing itemIdentifier) tutorials
60+ sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
60−
61− series' <- applyTemplateList itemTpl defaultContext series
62− articles' <- applyTemplateList itemTpl defaultContext articles
63−
64− let tutorialsCtx =
65− constField "title" "Tutorials" `mappend`
66− constField "series" series' `mappend`
67− constField "articles" articles' `mappend`
68− defaultContext
69−
70 makeItem ""
61 makeItem ""
71− >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
62+ >>= loadAndApplyTemplate "templates/tutorials.html" ctx
72− >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
63+ >>= loadAndApplyTemplate "templates/default.html" ctx
73 >>= relativizeUrls
64 >>= relativizeUrls
74
65
75 -- Templates
66 -- Templates
@@ -77,8 +68,7 @@ main = hakyllWith config $ do
77 where
68 where
78 withToc = defaultHakyllWriterOptions
69 withToc = defaultHakyllWriterOptions
79 { writerTableOfContents = True
70 { writerTableOfContents = True
80− , writerTemplate = "$toc$\n$body$"
71+ , writerTemplate = Just "$toc$\n$body$"
81− , writerStandalone = True
82 }
72 }
83
73
84
74
@@ -112,9 +102,16 @@ hackage url
112
102
113
103
114--------------------------------------------------------------------------------
104--------------------------------------------------------------------------------
115−-- | Partition tutorials into tutorial series & other articles
105+-- | Partition tutorials into tutorial series, other articles, external articles
116−partitionTutorials :: [Item a] -> ([Item a], [Item a])
106+tutorialsCtx :: [Item String] -> Context String
117−partitionTutorials = partition $ \i ->
107+tutorialsCtx tuts =
118− case splitPath (toFilePath $ itemIdentifier i) of
108+ constField "title" "Tutorials" <>
119− [_, (x : _)] -> isDigit x
109+ listField "main" defaultContext (ofType "main") <>
120− _ -> False
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
web/templates/default.html+1−7
@@ -35,13 +35,7 @@
35 <a href="/tutorials/faq.html">faq</a>
35 <a href="/tutorials/faq.html">faq</a>
36 <a href="/examples.html">examples</a>
36 <a href="/examples.html">examples</a>
37 <a href="/releases.html">releases</a>
37 <a href="/releases.html">releases</a>
38− <a href="/hakyll3">Hakyll 3.X</a>
38+ <a href="https://jaspervdj.be/contact.html#donate" target="_blank">donate</a>
39−
40− <!-- Flattr button -->
41− <h1>Donate</h1>
42− <a href="http://flattr.com/thing/291889/Hakyll" target="_blank">
43− <img src="http://api.flattr.com/button/flattr-badge-large.png"
44− alt="Flattr this" title="Flattr this" border="0" /></a>
45 </div>
39 </div>
46
40
47 <div id="content">
41 <div id="content">
web/templates/tutorial-item.htmldeleted−1
@@ -1 +0,0 @@
1−<li><a href="$url$">$title$</a> by <em>$author$</em></li>
web/templates/tutorials.html+47−4
@@ -1,9 +1,47 @@
1<h1>Tutorials about Hakyll</h1>
1<h1>Tutorials about Hakyll</h1>
2−<h2>Tutorial series</h2>
2+<h2 id="series">Tutorial series</h2>
3−<ul>$series$</ul>
3+<ul>
4+ $for(main)$
5+ <li>
6+ <a href="$url$">$title$</a>
7+ </li>
8+ $endfor$
9+</ul>
4−<h2>Other articles</h2>
10+<h2 id="articles">Other articles</h2>
5<p>In no particular order:</p>
11<p>In no particular order:</p>
12+<ul>
6−<ul>$articles$</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>
7<p>
45<p>
8 All these tutorials assume you are using the latest stable version of
46 All these tutorials assume you are using the latest stable version of
9 Hakyll. If this is not the case, you might want to update using:
47 Hakyll. If this is not the case, you might want to update using:
@@ -11,3 +49,8 @@
11<pre><code>$$ ghc-pkg unregister hakyll
49<pre><code>$$ ghc-pkg unregister hakyll
12$$ cabal update
50$$ cabal update
13$$ cabal install hakyll</code></pre>
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.markdown+16−23
@@ -1,27 +1,25 @@
1---
1---
2title: Installation
2title: Installation
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Installation
7Installation
7------------
8------------
8
9
9−Installation is provided using [cabal], and some packages are available for
10+Installation is provided via Hackage, and some packages are available for
10−different distributions.
11+different distributions. For installation from source (i.e. via Hackage),
12+[stack] is recommended:
11
13
12− $ cabal install hakyll
14+ $ stack install hakyll
13
15
14−[cabal]: http://www.haskell.org/cabal/
16+[stack]: http://www.haskellstack.org/
15−
16−If you have a recent installation of `cabal` and your time is somewhat valuable,
17−use:
18−
19− $ cabal install -j hakyll
20
17
21Linux distro packages:
18Linux distro packages:
22
19
23−- [Archlinux (AUR)](https://aur.archlinux.org/packages/haskell-hakyll/)
24- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
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`
25
23
26Building the example site
24Building the example site
27-------------------------
25-------------------------
@@ -30,32 +28,27 @@ Apart from the main Hakyll library, the cabal package also provides you with an
30executable `hakyll-init` to create an example site. This is an easy way to get
28executable `hakyll-init` to create an example site. This is an easy way to get
31started:
29started:
32
30
33− $ hakyll-init my-site
31+ $ stack exec hakyll-init my-site
34
32
35This creates a folder `my-site` in the current directory, with some example
33This creates a folder `my-site` in the current directory, with some example
36content and a generic configuration.
34content and a generic configuration.
37
35
38−If `hakyll-init` is not found, you should make sure `$HOME/.cabal/bin` is in
36+If `hakyll-init` is not found, you should make sure your stack bin path
39−your `$PATH`.
37+(usually `$HOME/.local/bin`) is in your `$PATH`. You can check your stack local
40−
38+bin path by running `stack path --local-bin`.
41−(If you're on OS X you may not have a bin directory in `$HOME/.cabal`. In this
42−case, check `$HOME/Library/Haskell/bin` and put it on your path if you find
43−`hakyll-init` there. See [here] for more information on installation paths on
44−OS X.)
45−
46−[here]: http://www.haskell.org/haskellwiki/Mac_OS_X_Common_Installation_Paths
47
39
48The file `site.hs` holds the configuration of your site, as an executable
40The file `site.hs` holds the configuration of your site, as an executable
49haskell program. We can compile and run it like this:
41haskell program. We can compile and run it like this:
50
42
51 $ cd my-site
43 $ cd my-site
52− $ ghc --make -threaded site.hs
44+ $ stack init # creates stack.yaml file based on my-site.cabal
45+ $ stack build
53− $ ./site build
46+ $ stack exec site build
54
47
55If you installed `hakyll` with a preview server (this is the default), you can
48If you installed `hakyll` with a preview server (this is the default), you can
56now use
49now use
57
50
58− $ ./site watch
51+ $ stack exec site watch
59
52
60and have a look at your site at
53and have a look at your site at
61[http://localhost:8000/](http://localhost:8000/).
54[http://localhost:8000/](http://localhost:8000/).
web/tutorials/02-basics.markdown+12−11
@@ -1,30 +1,31 @@
1---
1---
2title: The basics
2title: The basics
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Building and cleaning
7Building and cleaning
7---------------------
8---------------------
8
9
9If you followed along with the previous tutorial, you should now have the
10If you followed along with the previous tutorial, you should now have the
10−example site up and running. By running `./site build`, you created two
11+example site up and running. By running `stack exec site build`, you created
11−directories:
12+two directories:
12
13
13- `_site`, with your site as HTML files, ready to be deployed;
14- `_site`, with your site as HTML files, ready to be deployed;
14- `_cache`, which Hakyll uses internally.
15- `_cache`, which Hakyll uses internally.
15
16
16−`./site clean` removes these directories, and `./site rebuild` performs a
17+`stack exec site clean` removes these directories, and `stack exec site
17−`clean` and then a `build`.
18+rebuild` performs a `clean` and then a `build`.
18
19
19−In general, you want to use `./site build` when you just made changes to the
20+In general, you want to use `stack exec site build` when you just made changes
20−contents of your website. If you made important changes to `site.hs`, you need
21+to the contents of your website. If you made changes to `site.hs`, you need to
21−to recompile `site.hs` followed by a rebuild:
22+recompile `site.hs` followed by a rebuild:
22
23
23− ghc --make site.hs
24+ stack build
24− ./site rebuild
25+ stack exec site rebuild
25
26
26−At this point, feel free to change some files, `./site build` and see what
27+At this point, feel free to change some files, `stack exec site build` and see
27−happens!
28+what happens!
28
29
29Pages and metadata
30Pages and metadata
30------------------
31------------------
web/tutorials/03-rules-routes-compilers.markdown+2−1
@@ -1,6 +1,7 @@
1---
1---
2title: Rules, routes and compilers
2title: Rules, routes and compilers
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic rules
7Basic rules
@@ -36,7 +37,7 @@ match "css/*" $ do
36
37
37```
38```
38
39
39−This is a declarative DSL: the order in which you write the rules make little
40+This is a declarative DSL: the order in which you write the rules makes little
40difference, Hakyll will use dependency tracking to determine the correct order.
41difference, Hakyll will use dependency tracking to determine the correct order.
41
42
42We group the different rules using `match`. The first argument for `match` is a
43We group the different rules using `match`. The first argument for `match` is a
web/tutorials/04-compilers.markdown+25−8
@@ -1,6 +1,7 @@
1---
1---
2−title: More on compilers: load, and templates
2+title: 'More on compilers: load, and templates'
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Loading items
7Loading items
@@ -53,10 +54,10 @@ when the template is applied. If you want an actual dollar sign in the output,
53use `$$`.
54use `$$`.
54
55
55You usually compile the templates from disk using the aptly named
56You usually compile the templates from disk using the aptly named
56−`templateCompiler`:
57+`templateBodyCompiler`:
57
58
58```haskell
59```haskell
59−match "templates/*" $ compile templateCompiler
60+match "templates/*" $ compile templateBodyCompiler
60```
61```
61
62
62Notice the lack of `route` here: this is because we don't need to write the
63Notice the lack of `route` here: this is because we don't need to write the
@@ -92,7 +93,7 @@ And `$title$` like this:
92titleContext :: Context a
93titleContext :: Context a
93titleContext = field "title" $ \item -> do
94titleContext = field "title" $ \item -> do
94 metadata <- getMetadata (itemIdentifier item)
95 metadata <- getMetadata (itemIdentifier item)
95− return $ fromMaybe "No title" $ M.lookup "title" metadata
96+ return $ fromMaybe "No title" $ lookupString "title" metadata
96```
97```
97
98
98And compose them using the `Monoid` instance:
99And compose them using the `Monoid` instance:
@@ -115,8 +116,10 @@ you to use:
115- `$path$` for the original filepath of the page;
116- `$path$` for the original filepath of the page;
116- `$foo$` where foo is specified in the metadata.
117- `$foo$` where foo is specified in the metadata.
117
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
118−`$date$` is not provided by default, you can see how we add it in the definition
121+it begins with a date. You can see how we add it in the definition of `postCtx`
119−of `postCtx` in `site.hs`:
122+in `site.hs`:
120
123
121```haskell
124```haskell
122postCtx :: Context String
125postCtx :: Context String
@@ -128,7 +131,7 @@ postCtx =
128### Loading and applying templates
131### Loading and applying templates
129
132
130Now we know about templates, context and how to load arbitrary items. This gives
133Now we know about templates, context and how to load arbitrary items. This gives
131−us enough background information in order to understand you can apply a
134+us enough background information in order to understand how you can apply a
132template:
135template:
133
136
134```haskell
137```haskell
@@ -149,7 +152,7 @@ Control flow in templates
149-------------------------
152-------------------------
150
153
151Sometimes string interpolation does not suffice, and you want a little more
154Sometimes string interpolation does not suffice, and you want a little more
152−control over how your templates are layed out. Hakyll provides a few control
155+control over how your templates are laid out. Hakyll provides a few control
153structures for this. The syntax for these structures was based on the syntax
156structures for this. The syntax for these structures was based on the syntax
154used in pandoc templates, since Hakyll already has tight integration with
157used in pandoc templates, since Hakyll already has tight integration with
155pandoc.
158pandoc.
@@ -234,6 +237,20 @@ This uses the `$for(foo)$` construct. This construct allows you loop over a
234list, in this case, `$posts$`. Inside the body of this for loop, all fields
237list, in this case, `$posts$`. Inside the body of this for loop, all fields
235refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
238refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
236
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+
237Of course, $posts$ does not magically appear. We have to specify this in
254Of course, $posts$ does not magically appear. We have to specify this in
238`site.hs`. Let's look at how `archive.html` is generated:
255`site.hs`. Let's look at how `archive.html` is generated:
239
256
web/tutorials/05-snapshots-feeds.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Snapshots, and how to produce an RSS/Atom feed
2title: Snapshots, and how to produce an RSS/Atom feed
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic feed configuration
7Basic feed configuration
web/tutorials/06-versions.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Producing multiple versions of a single file
2title: Producing multiple versions of a single file
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basics
7Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: A Guide to the Hakyll Module Zoo
2title: A Guide to the Hakyll Module Zoo
3author: Brent Yorgey
3author: Brent Yorgey
4+type: article
4---
5---
5
6
6The hakyll package [contains a bewildering array](/reference/) of modules, and
7The hakyll package [contains a bewildering array](/reference/) of modules, and
web/tutorials/external-add-tags-to-your-hakyll-blog.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.markdown+13
@@ -1,6 +1,7 @@
1---
1---
2title: FAQ
2title: FAQ
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
7## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
@@ -27,6 +28,18 @@ You should also add this to your `.profile`, or whatever config file you use.
27On Windows, running `chcp 65001` before running your Hakyll executable has been
28On Windows, running `chcp 65001` before running your Hakyll executable has been
28reported to work.
29reported to work.
29
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+
30## "File name does not match module name" on Mac OS
43## "File name does not match module name" on Mac OS
31
44
32 Hakyll.hs:1:1:
45 Hakyll.hs:1:1:
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.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Hakyll 3 to Hakyll 4 migration guide
2title: Hakyll 3 to Hakyll 4 migration guide
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6Introduction
7Introduction
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 1: Setup & Initial Customization'
3+url: 'https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 2: Generating a Sitemap XML File'
3+url: 'https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 3: Generating RSS and Atom XML Feeds'
3+url: 'https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-04-copying-static-files-for-your-build.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 4: Copying Static Files For Your Build'
3+url: 'https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 5 – Generating Custom Post Filenames From a Title Slug'
3+url: 'https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html'
4+type: robertwpearce
5+---
web/tutorials/using-clay-with-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using Clay with Hakyll
2title: Using Clay with Hakyll
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
7[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
web/tutorials/using-teasers-in-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using teasers in Hakyll
2title: Using teasers in Hakyll
3author: Ivan Veselov
3author: Ivan Veselov
4+type: article
4---
5---
5
6
6## Introduction
7## Introduction