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

The content is the same.

data/example/posts/2015-10-07-rosa-rosa-rosam.markdown → data/example/posts/2012-10-07-rosa-rosa-rosam.markdownrenamed−1
@@ -1,6 +1,5 @@
1---
1---
2title: Rosa Rosa Rosam
2title: Rosa Rosa Rosam
3−author: Ovidius
4---
3---
5
4
6Suspendisse pharetra ullamcorper sem et auctor. Suspendisse vitae tellus eu
5Suspendisse pharetra ullamcorper sem et auctor. Suspendisse vitae tellus eu
data/example/posts/2015-11-28-carpe-diem.markdown → data/example/posts/2012-11-28-carpe-diem.markdownrenamed

The content is the same.

data/example/posts/2015-12-07-tu-quoque.markdown → data/example/posts/2012-12-07-tu-quoque.markdownrenamed−1
@@ -1,6 +1,5 @@
1---
1---
2title: Tu Quoque
2title: Tu Quoque
3−author: Julius
4---
3---
5
4
6Vestibulum leo turpis, dignissim quis ultrices sit amet, iaculis ac ligula.
5Vestibulum leo turpis, dignissim quis ultrices sit amet, iaculis ac ligula.
data/example/site.hs+16−11
@@ -1,6 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+import Control.Applicative ((<$>))
3−import Data.Monoid (mappend)
4+import Data.Monoid (mappend)
4import Hakyll
5import Hakyll
5
6
6
7
@@ -31,10 +32,9 @@ main = hakyll $ do
31 create ["archive.html"] $ do
32 create ["archive.html"] $ do
32 route idRoute
33 route idRoute
33 compile $ do
34 compile $ do
34− posts <- recentFirst =<< loadAll "posts/*"
35 let archiveCtx =
35 let archiveCtx =
36− listField "posts" postCtx (return posts) `mappend`
36+ field "posts" (\_ -> postList recentFirst) `mappend`
37− constField "title" "Archives" `mappend`
37+ constField "title" "Archives" `mappend`
38 defaultContext
38 defaultContext
39
39
40 makeItem ""
40 makeItem ""
@@ -46,18 +46,14 @@ main = hakyll $ do
46 match "index.html" $ do
46 match "index.html" $ do
47 route idRoute
47 route idRoute
48 compile $ do
48 compile $ do
49− posts <- recentFirst =<< loadAll "posts/*"
49+ let indexCtx = field "posts" $ \_ -> postList (take 3 . recentFirst)
50− let indexCtx =
51− listField "posts" postCtx (return posts) `mappend`
52− constField "title" "Home" `mappend`
53− defaultContext
54
50
55 getResourceBody
51 getResourceBody
56 >>= applyAsTemplate indexCtx
52 >>= applyAsTemplate indexCtx
57− >>= loadAndApplyTemplate "templates/default.html" indexCtx
53+ >>= loadAndApplyTemplate "templates/default.html" postCtx
58 >>= relativizeUrls
54 >>= relativizeUrls
59
55
60− match "templates/*" $ compile templateBodyCompiler
56+ match "templates/*" $ compile templateCompiler
61
57
62
58
63--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
@@ -65,3 +61,12 @@ postCtx :: Context String
65postCtx =
61postCtx =
66 dateField "date" "%B %e, %Y" `mappend`
62 dateField "date" "%B %e, %Y" `mappend`
67 defaultContext
63 defaultContext
64+
65+
66+--------------------------------------------------------------------------------
67+postList :: ([Item String] -> [Item String]) -> Compiler String
68+postList sortFilter = do
69+ posts <- sortFilter <$> loadAll "posts/*"
70+ itemTpl <- loadBody "templates/post-item.html"
71+ list <- applyTemplateList itemTpl postCtx posts
72+ return list
data/example/templates/archive.html+3−2
@@ -1,2 +1,3 @@
1−Here you can find all my previous posts:
1+<ul>
2−$partial("templates/post-list.html")$
2+ $posts$
3+</ul>
data/example/templates/default.html+16−16
@@ -1,33 +1,33 @@
1−<!doctype html>
1+<?xml version="1.0" encoding="UTF-8"?>
2−<html lang="en">
2+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3 <head>
5 <head>
4− <meta charset="utf-8">
6+ <meta http-equiv="Content-Type" content="text/html; 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" href="/css/default.css" />
8+ <link rel="stylesheet" type="text/css" href="/css/default.css" />
9 </head>
9 </head>
10 <body>
10 <body>
11− <header>
11+ <div id="header">
12− <div class="logo">
12+ <div id="logo">
13 <a href="/">My Hakyll Blog</a>
13 <a href="/">My Hakyll Blog</a>
14 </div>
14 </div>
15− <nav>
15+ <div id="navigation">
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− </nav>
20+ </div>
21− </header>
21+ </div>
22
22
23− <main role="main">
23+ <div id="content">
24 <h1>$title$</h1>
24 <h1>$title$</h1>
25− $body$
26− </main>
27
25
26+ $body$
27+ </div>
28− <footer>
28+ <div id="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− </footer>
31+ </div>
32 </body>
32 </body>
33</html>
33</html>
data/example/templates/post-item.htmlnew file+3
@@ -0,0 +1,3 @@
1+<li>
2+ <a href="$url$">$title$</a> - $date$
3+</li>
data/example/templates/post-list.htmldeleted−7
@@ -1,7 +0,0 @@
1−<ul>
2− $for(posts)$
3− <li>
4− <a href="$url$">$title$</a> - $date$
5− </li>
6− $endfor$
7−</ul>
data/example/templates/post.html+3−11
@@ -1,11 +1,3 @@
1−<article>
2− <section class="header">
3− Posted on $date$
1+<div class="info">Posted on $date$</div>
4− $if(author)$
2+
5− by $author$
6− $endif$
7− </section>
8− <section>
9− $body$
3+$body$
10− </section>
11−</article>
data/templates/rss-item.xml−1
@@ -4,5 +4,4 @@
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>
8</item>
7</item>
data/templates/rss.xml+2−3
@@ -1,6 +1,5 @@
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/">
4 <channel>
3 <channel>
5 <title>$title$</title>
4 <title>$title$</title>
6 <link>$root$</link>
5 <link>$root$</link>
@@ -9,5 +8,5 @@
9 type="application/rss+xml" />
8 type="application/rss+xml" />
10 <lastBuildDate>$updated$</lastBuildDate>
9 <lastBuildDate>$updated$</lastBuildDate>
11 $body$
10 $body$
12− </channel>
11+ </channel>
13</rss>
12</rss>
hakyll.cabal+106−198
@@ -1,5 +1,5 @@
1Name: hakyll
1Name: hakyll
2−Version: 4.13.0.0
2+Version: 4.0.0.0
3
3
4Synopsis: A static website compiler library
4Synopsis: A static website compiler library
5Description:
5Description:
@@ -37,18 +37,18 @@ License: BSD3
37License-File: LICENSE
37License-File: LICENSE
38Category: Web
38Category: Web
39
39
40−Cabal-Version: >= 1.10
40+Cabal-Version: >= 1.8
41Build-Type: Simple
41Build-Type: Simple
42Data-Dir: data
42Data-Dir: data
43
43
44Data-files:
44Data-files:
45− example/posts/2015-11-28-carpe-diem.markdown
45+ example/posts/2012-11-28-carpe-diem.markdown
46− example/posts/2015-10-07-rosa-rosa-rosam.markdown
46+ example/posts/2012-10-07-rosa-rosa-rosam.markdown
47− example/posts/2015-12-07-tu-quoque.markdown
47+ example/posts/2012-12-07-tu-quoque.markdown
48− example/posts/2015-08-12-spqr.markdown
48+ example/posts/2012-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-item.html
52 example/templates/default.html
52 example/templates/default.html
53 example/templates/archive.html
53 example/templates/archive.html
54 example/templates/post.html
54 example/templates/post.html
@@ -56,28 +56,16 @@ 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
59
63
60Extra-source-files:
64Extra-source-files:
61− CHANGELOG.md
62 tests/data/example.md
65 tests/data/example.md
63 tests/data/example.md.metadata
66 tests/data/example.md.metadata
64− tests/data/images/favicon.ico
65− tests/data/just-meta.html
66− tests/data/just-meta.html.out
67− tests/data/partial-helper.html
68− tests/data/partial.html
69− tests/data/partial.html.out
70− tests/data/posts/2010-08-26-birthday.md
71− tests/data/posts/2018-09-26.md
72− tests/data/russian.md
73− tests/data/strip.html
74− tests/data/strip.html.out
75 tests/data/template.html
67 tests/data/template.html
76 tests/data/template.html.out
68 tests/data/template.html.out
77− data/templates/atom-item.xml
78− data/templates/atom.xml
79− data/templates/rss-item.xml
80− data/templates/rss.xml
81
69
82Source-Repository head
70Source-Repository head
83 Type: git
71 Type: git
@@ -87,51 +75,53 @@ Flag previewServer
87 Description: Include the preview server
75 Description: Include the preview server
88 Default: True
76 Default: True
89
77
90−Flag watchServer
91− Description: Include the watch server
92− Default: True
93−
94−Flag checkExternal
95− Description: Include external link checking
96− Default: True
97−
98−Flag buildWebsite
99− Description: Build the hakyll website
100− Default: False
101− Manual: True
102−
103−Flag usePandoc
104− Description: Include Pandoc support
105− Default: True
106− Manual: True
107−
108Library
78Library
109− Ghc-Options: -Wall
79+ Ghc-Options: -Wall
80+ Ghc-Prof-Options: -auto-all -caf-all
110− Hs-Source-Dirs: lib
81+ Hs-Source-Dirs: src
111− Default-language: Haskell2010
82+
83+ Build-Depends:
84+ base >= 4 && < 5,
85+ binary >= 0.5 && < 0.7,
86+ blaze-html >= 0.5 && < 0.6,
87+ blaze-markup >= 0.5.1 && < 0.6,
88+ bytestring >= 0.9 && < 0.11,
89+ citeproc-hs >= 0.3.2 && < 0.4,
90+ containers >= 0.3 && < 0.6,
91+ cryptohash >= 0.7 && < 0.9,
92+ deepseq >= 1.3 && < 1.4,
93+ directory >= 1.0 && < 1.3,
94+ filepath >= 1.0 && < 1.4,
95+ http-conduit >= 1.8 && < 1.9,
96+ http-types >= 0.7 && < 0.8,
97+ lrucache >= 1.1.1 && < 1.2,
98+ mtl >= 1 && < 2.2,
99+ old-locale >= 1.0 && < 1.1,
100+ old-time >= 1.0 && < 1.2,
101+ cmdargs >= 0.10 && < 0.11,
102+ pandoc >= 1.9.3 && < 1.10,
103+ parsec >= 3.0 && < 3.2,
104+ process >= 1.0 && < 1.2,
105+ random >= 1.0 && < 1.1,
106+ regex-base >= 0.93 && < 0.94,
107+ regex-tdfa >= 1.1 && < 1.2,
108+ tagsoup >= 0.12.6 && < 0.13,
109+ text >= 0.11 && < 1.12,
110+ time >= 1.1 && < 1.5
112
111
113 Exposed-Modules:
112 Exposed-Modules:
114 Hakyll
113 Hakyll
115− Hakyll.Commands
116 Hakyll.Core.Compiler
114 Hakyll.Core.Compiler
117− Hakyll.Core.Compiler.Internal
118 Hakyll.Core.Configuration
115 Hakyll.Core.Configuration
119 Hakyll.Core.Dependencies
116 Hakyll.Core.Dependencies
120 Hakyll.Core.File
117 Hakyll.Core.File
121 Hakyll.Core.Identifier
118 Hakyll.Core.Identifier
122 Hakyll.Core.Identifier.Pattern
119 Hakyll.Core.Identifier.Pattern
123 Hakyll.Core.Item
120 Hakyll.Core.Item
124− Hakyll.Core.Logger
125 Hakyll.Core.Metadata
121 Hakyll.Core.Metadata
126− Hakyll.Core.Provider
127− Hakyll.Core.Provider.Metadata
128 Hakyll.Core.Routes
122 Hakyll.Core.Routes
129 Hakyll.Core.Rules
123 Hakyll.Core.Rules
130− Hakyll.Core.Rules.Internal
131− Hakyll.Core.Runtime
132− Hakyll.Core.Store
133 Hakyll.Core.UnixFilter
124 Hakyll.Core.UnixFilter
134− Hakyll.Core.Util.File
135 Hakyll.Core.Util.String
125 Hakyll.Core.Util.String
136 Hakyll.Core.Writable
126 Hakyll.Core.Writable
137 Hakyll.Main
127 Hakyll.Main
@@ -139,112 +129,88 @@ Library
139 Hakyll.Web.Feed
129 Hakyll.Web.Feed
140 Hakyll.Web.Html
130 Hakyll.Web.Html
141 Hakyll.Web.Html.RelativizeUrls
131 Hakyll.Web.Html.RelativizeUrls
142− Hakyll.Web.Paginate
132+ Hakyll.Web.Pandoc
143− Hakyll.Web.Redirect
133+ Hakyll.Web.Pandoc.Biblio
134+ Hakyll.Web.Pandoc.FileType
144 Hakyll.Web.Tags
135 Hakyll.Web.Tags
145 Hakyll.Web.Template
136 Hakyll.Web.Template
146 Hakyll.Web.Template.Context
137 Hakyll.Web.Template.Context
147− Hakyll.Web.Template.Internal
148− Hakyll.Web.Template.Internal.Element
149− Hakyll.Web.Template.Internal.Trim
150 Hakyll.Web.Template.List
138 Hakyll.Web.Template.List
139+ Hakyll.Web.Template.Read
151
140
152 Other-Modules:
141 Other-Modules:
153− Data.List.Extended
154− Data.Yaml.Extended
155 Hakyll.Check
142 Hakyll.Check
143+ Hakyll.Commands
144+ Hakyll.Core.Compiler.Internal
156 Hakyll.Core.Compiler.Require
145 Hakyll.Core.Compiler.Require
157− Hakyll.Core.Identifier.Pattern.Internal
158 Hakyll.Core.Item.SomeItem
146 Hakyll.Core.Item.SomeItem
147+ Hakyll.Core.Logger
148+ Hakyll.Core.Provider
159 Hakyll.Core.Provider.Internal
149 Hakyll.Core.Provider.Internal
150+ Hakyll.Core.Provider.Metadata
160 Hakyll.Core.Provider.MetadataCache
151 Hakyll.Core.Provider.MetadataCache
161− Hakyll.Core.Util.Parser
152+ Hakyll.Core.Provider.Modified
153+ Hakyll.Core.Rules.Internal
154+ Hakyll.Core.Runtime
155+ Hakyll.Core.Store
156+ Hakyll.Core.Util.File
157+ Hakyll.Web.Template.Internal
162 Paths_hakyll
158 Paths_hakyll
163
159
164− Build-Depends:
165− base >= 4.8 && < 5,
166− binary >= 0.5 && < 0.10,
167− blaze-html >= 0.5 && < 0.10,
168− blaze-markup >= 0.5.1 && < 0.9,
169− bytestring >= 0.9 && < 0.11,
170− containers >= 0.3 && < 0.7,
171− cryptohash >= 0.7 && < 0.12,
172− data-default >= 0.4 && < 0.8,
173− deepseq >= 1.3 && < 1.5,
174− directory >= 1.0 && < 1.4,
175− file-embed >= 0.0.10.1 && < 0.0.12,
176− filepath >= 1.0 && < 1.5,
177− lrucache >= 1.1.1 && < 1.3,
178− mtl >= 1 && < 2.3,
179− network-uri >= 2.6 && < 2.7,
180− optparse-applicative >= 0.12 && < 0.15,
181− parsec >= 3.0 && < 3.2,
182− process >= 1.6 && < 1.7,
183− random >= 1.0 && < 1.2,
184− regex-tdfa >= 1.1 && < 1.3,
185− resourcet >= 1.1 && < 1.3,
186− scientific >= 0.3.4 && < 0.4,
187− tagsoup >= 0.13.1 && < 0.15,
188− template-haskell >= 2.14 && < 2.15,
189− text >= 0.11 && < 1.3,
190− time >= 1.8 && < 1.10,
191− time-locale-compat >= 0.1 && < 0.2,
192− unordered-containers >= 0.2 && < 0.3,
193− vector >= 0.11 && < 0.13,
194− yaml >= 0.8.11 && < 0.12
195−
196 If flag(previewServer)
160 If flag(previewServer)
197 Build-depends:
161 Build-depends:
198− wai >= 3.2 && < 3.3,
162+ snap-core >= 0.6 && < 0.10,
199− warp >= 3.2 && < 3.3,
163+ snap-server >= 0.6 && < 0.10
200− wai-app-static >= 3.1 && < 3.2,
201− http-types >= 0.9 && < 0.13,
202− fsnotify >= 0.2 && < 0.4
203 Cpp-options:
164 Cpp-options:
204 -DPREVIEW_SERVER
165 -DPREVIEW_SERVER
205 Other-modules:
166 Other-modules:
206 Hakyll.Preview.Poll
167 Hakyll.Preview.Poll
207 Hakyll.Preview.Server
168 Hakyll.Preview.Server
208
169
209− If flag(watchServer)
210− Build-depends:
211− fsnotify >= 0.2 && < 0.4
212− Cpp-options:
213− -DWATCH_SERVER
214− Other-modules:
215− Hakyll.Preview.Poll
216−
217− If flag(checkExternal)
218− Build-depends:
219− http-conduit >= 2.2 && < 2.4,
220− http-types >= 0.7 && < 0.13
221− Cpp-options:
222− -DCHECK_EXTERNAL
223−
224− If flag(usePandoc)
225− Exposed-Modules:
226− Hakyll.Web.Pandoc
227− Hakyll.Web.Pandoc.Biblio
228− Hakyll.Web.Pandoc.FileType
229− Other-Modules:
230− Hakyll.Web.Pandoc.Binary
231− Build-Depends:
232− pandoc >= 2.0.5 && < 2.8,
233− pandoc-citeproc >= 0.14 && < 0.17
234− Cpp-options:
235− -DUSE_PANDOC
236−
237Test-suite hakyll-tests
170Test-suite hakyll-tests
238− Type: exitcode-stdio-1.0
171+ Type: exitcode-stdio-1.0
239− Hs-source-dirs: tests
172+ Hs-source-dirs: src tests
240− Main-is: TestSuite.hs
173+ Main-is: TestSuite.hs
241− Ghc-options: -Wall -threaded
174+ Ghc-options: -Wall
242− Default-language: Haskell2010
175+
176+ Build-Depends:
177+ HUnit >= 1.2 && < 1.3,
178+ QuickCheck >= 2.4 && < 2.6,
179+ test-framework >= 0.4 && < 0.7,
180+ test-framework-hunit >= 0.2 && < 0.3,
181+ test-framework-quickcheck2 >= 0.2 && < 0.3,
182+ -- Copy pasted from hakyll dependencies:
183+ base >= 4 && < 5,
184+ binary >= 0.5 && < 0.7,
185+ blaze-html >= 0.5 && < 0.6,
186+ blaze-markup >= 0.5.1 && < 0.6,
187+ bytestring >= 0.9 && < 0.11,
188+ citeproc-hs >= 0.3.2 && < 0.4,
189+ containers >= 0.3 && < 0.6,
190+ cryptohash >= 0.7 && < 0.9,
191+ deepseq >= 1.3 && < 1.4,
192+ directory >= 1.0 && < 1.3,
193+ filepath >= 1.0 && < 1.4,
194+ http-conduit >= 1.8 && < 1.9,
195+ http-types >= 0.7 && < 0.8,
196+ lrucache >= 1.1.1 && < 1.2,
197+ mtl >= 1 && < 2.2,
198+ old-locale >= 1.0 && < 1.1,
199+ old-time >= 1.0 && < 1.2,
200+ cmdargs >= 0.10 && < 0.11,
201+ pandoc >= 1.9.3 && < 1.10,
202+ parsec >= 3.0 && < 3.2,
203+ process >= 1.0 && < 1.2,
204+ random >= 1.0 && < 1.1,
205+ regex-base >= 0.93 && < 0.94,
206+ regex-tdfa >= 1.1 && < 1.2,
207+ tagsoup >= 0.12.6 && < 0.13,
208+ text >= 0.11 && < 1.12,
209+ time >= 1.1 && < 1.5
243
210
244 Other-modules:
211 Other-modules:
245 Hakyll.Core.Dependencies.Tests
212 Hakyll.Core.Dependencies.Tests
246 Hakyll.Core.Identifier.Tests
213 Hakyll.Core.Identifier.Tests
247− Hakyll.Core.Provider.Metadata.Tests
248 Hakyll.Core.Provider.Tests
214 Hakyll.Core.Provider.Tests
249 Hakyll.Core.Routes.Tests
215 Hakyll.Core.Routes.Tests
250 Hakyll.Core.Rules.Tests
216 Hakyll.Core.Rules.Tests
@@ -252,76 +218,18 @@ Test-suite hakyll-tests
252 Hakyll.Core.Store.Tests
218 Hakyll.Core.Store.Tests
253 Hakyll.Core.UnixFilter.Tests
219 Hakyll.Core.UnixFilter.Tests
254 Hakyll.Core.Util.String.Tests
220 Hakyll.Core.Util.String.Tests
255− Hakyll.Web.CompressCss.Tests
256− Hakyll.Web.Html.RelativizeUrls.Tests
257− Hakyll.Web.Html.Tests
258− Hakyll.Web.Tags.Tests
259 Hakyll.Web.Template.Context.Tests
221 Hakyll.Web.Template.Context.Tests
260 Hakyll.Web.Template.Tests
222 Hakyll.Web.Template.Tests
223+ Hakyll.Web.Html.Tests
224+ Hakyll.Web.Html.RelativizeUrls.Tests
261 TestSuite.Util
225 TestSuite.Util
262
226
263− Build-Depends:
264− hakyll,
265− QuickCheck >= 2.8 && < 2.14,
266− tasty >= 0.11 && < 1.3,
267− tasty-hunit >= 0.9 && < 0.11,
268− tasty-quickcheck >= 0.8 && < 0.11,
269− -- Copy pasted from hakyll dependencies:
270− base >= 4.8 && < 5,
271− bytestring >= 0.9 && < 0.11,
272− containers >= 0.3 && < 0.7,
273− filepath >= 1.0 && < 1.5,
274− text >= 0.11 && < 1.3,
275− unordered-containers >= 0.2 && < 0.3,
276− yaml >= 0.8.11 && < 0.12
277−
278− If flag(previewServer)
279− Cpp-options:
280− -DPREVIEW_SERVER
281−
282− If flag(watchServer)
283− Cpp-options:
284− -DWATCH_SERVER
285−
286− If flag(checkExternal)
287− Cpp-options:
288− -DCHECK_EXTERNAL
289−
290− If flag(usePandoc)
291− Other-modules:
292− Hakyll.Web.Pandoc.FileType.Tests
293− Cpp-options:
294− -DUSE_PANDOC
295−
296Executable hakyll-init
227Executable hakyll-init
297− Main-is: Init.hs
298− Ghc-options: -Wall -threaded
228+ Ghc-options: -Wall
299− Hs-source-dirs: src
229+ Hs-source-dirs: src
300− Default-language: Haskell2010
230+ Main-is: Hakyll/Init.hs
301−
302− Other-modules:
303− Paths_hakyll
304
231
305 Build-depends:
232 Build-depends:
306− hakyll,
307 base >= 4 && < 5,
233 base >= 4 && < 5,
308− directory >= 1.0 && < 1.4,
234+ directory >= 1.0 && < 1.3,
309− filepath >= 1.0 && < 1.5
235+ filepath >= 1.0 && < 1.4
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.hsdeleted−15
@@ -1,15 +0,0 @@
1−module Data.List.Extended
2− ( module Data.List
3− , breakWhen
4− ) where
5−
6−import Data.List
7−
8−-- | Like 'break', but can act on the entire tail of the list.
9−breakWhen :: ([a] -> Bool) -> [a] -> ([a], [a])
10−breakWhen predicate = go []
11− where
12− go buf [] = (reverse buf, [])
13− go buf (x : xs)
14− | predicate (x : xs) = (reverse buf, x : xs)
15− | otherwise = go (x : buf) xs
lib/Data/Yaml/Extended.hsdeleted−24
@@ -1,24 +0,0 @@
1−module Data.Yaml.Extended
2− ( module Data.Yaml
3− , toString
4− , toList
5− ) where
6−
7−import qualified Data.Text as T
8−import qualified Data.Vector as V
9−import Data.Yaml
10−import Data.Scientific
11−
12−toString :: Value -> Maybe String
13−toString (String t) = Just (T.unpack t)
14−toString (Bool True) = Just "true"
15−toString (Bool False) = Just "false"
16−-- | Make sure that numeric fields containing integer numbers are shown as
17−-- | integers (i.e., "42" instead of "42.0").
18−toString (Number d) | isInteger d = Just (formatScientific Fixed (Just 0) d)
19− | otherwise = Just (show d)
20−toString _ = Nothing
21−
22−toList :: Value -> Maybe [Value]
23−toList (Array a) = Just (V.toList a)
24−toList _ = Nothing
lib/Hakyll/Check.hsdeleted−302
@@ -1,302 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Check
5− ( Check (..)
6− , check
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar,
12− readMVar)
13−import Control.Exception (SomeAsyncException (..),
14− SomeException (..), throw, try)
15−import Control.Monad (foldM, forM_)
16−import Control.Monad.Reader (ReaderT, ask, runReaderT)
17−import Control.Monad.State (StateT, get, modify, runStateT)
18−import Control.Monad.Trans (liftIO)
19−import Control.Monad.Trans.Resource (runResourceT)
20−import Data.List (isPrefixOf)
21−import qualified Data.Map.Lazy as Map
22−#if MIN_VERSION_base(4,9,0)
23−import Data.Semigroup (Semigroup (..))
24−#endif
25−import Network.URI (unEscapeString)
26−import System.Directory (doesDirectoryExist,
27− doesFileExist)
28−import System.Exit (ExitCode (..))
29−import System.FilePath (takeDirectory, takeExtension,
30− (</>))
31−import qualified Text.HTML.TagSoup as TS
32−
33−
34−--------------------------------------------------------------------------------
35−#ifdef CHECK_EXTERNAL
36−import Data.List (intercalate)
37−import Data.Typeable (cast)
38−import Data.Version (versionBranch)
39−import GHC.Exts (fromString)
40−import qualified Network.HTTP.Conduit as Http
41−import qualified Network.HTTP.Types as Http
42−import qualified Paths_hakyll as Paths_hakyll
43−#endif
44−
45−
46−--------------------------------------------------------------------------------
47−import Hakyll.Core.Configuration
48−import Hakyll.Core.Logger (Logger)
49−import qualified Hakyll.Core.Logger as Logger
50−import Hakyll.Core.Util.File
51−import Hakyll.Web.Html
52−
53−
54−--------------------------------------------------------------------------------
55−data Check = All | InternalLinks
56− deriving (Eq, Ord, Show)
57−
58−
59−--------------------------------------------------------------------------------
60−check :: Configuration -> Logger -> Check -> IO ExitCode
61−check config logger check' = do
62− ((), state) <- runChecker checkDestination config logger check'
63− failed <- countFailedLinks state
64− return $ if failed > 0 then ExitFailure 1 else ExitSuccess
65−
66−
67−--------------------------------------------------------------------------------
68−countFailedLinks :: CheckerState -> IO Int
69−countFailedLinks state = foldM addIfFailure 0 (Map.elems state)
70− where addIfFailure failures mvar = do
71− checkerWrite <- readMVar mvar
72− return $ failures + checkerFaulty checkerWrite
73−
74−
75−--------------------------------------------------------------------------------
76−data CheckerRead = CheckerRead
77− { checkerConfig :: Configuration
78− , checkerLogger :: Logger
79− , checkerCheck :: Check
80− }
81−
82−
83−--------------------------------------------------------------------------------
84−data CheckerWrite = CheckerWrite
85− { checkerFaulty :: Int
86− , checkerOk :: Int
87− } deriving (Show)
88−
89−
90−--------------------------------------------------------------------------------
91−#if MIN_VERSION_base(4,9,0)
92−instance Semigroup CheckerWrite where
93− (<>) (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
94− CheckerWrite (f1 + f2) (o1 + o2)
95−
96−instance Monoid CheckerWrite where
97− mempty = CheckerWrite 0 0
98− mappend = (<>)
99−#else
100−instance Monoid CheckerWrite where
101− mempty = CheckerWrite 0 0
102− mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
103− CheckerWrite (f1 + f2) (o1 + o2)
104−#endif
105−
106−
107−--------------------------------------------------------------------------------
108−type CheckerState = Map.Map URL (MVar CheckerWrite)
109−
110−
111−--------------------------------------------------------------------------------
112−type Checker a = ReaderT CheckerRead (StateT CheckerState IO) a
113−
114−
115−--------------------------------------------------------------------------------
116−type URL = String
117−
118−
119−--------------------------------------------------------------------------------
120−runChecker :: Checker a -> Configuration -> Logger -> Check
121− -> IO (a, CheckerState)
122−runChecker checker config logger check' = do
123− let read' = CheckerRead
124− { checkerConfig = config
125− , checkerLogger = logger
126− , checkerCheck = check'
127− }
128− Logger.flush logger
129− runStateT (runReaderT checker read') Map.empty
130−
131−
132−--------------------------------------------------------------------------------
133−checkDestination :: Checker ()
134−checkDestination = do
135− config <- checkerConfig <$> ask
136− files <- liftIO $ getRecursiveContents
137− (const $ return False) (destinationDirectory config)
138−
139− let htmls =
140− [ destinationDirectory config </> file
141− | file <- files
142− , takeExtension file == ".html"
143− ]
144−
145− forM_ htmls checkFile
146−
147−
148−--------------------------------------------------------------------------------
149−checkFile :: FilePath -> Checker ()
150−checkFile filePath = do
151− logger <- checkerLogger <$> ask
152− contents <- liftIO $ readFile filePath
153− Logger.header logger $ "Checking file " ++ filePath
154−
155− let urls = getUrls $ TS.parseTags contents
156− forM_ urls $ \url -> do
157− Logger.debug logger $ "Checking link " ++ url
158− m <- liftIO newEmptyMVar
159− checkUrlIfNeeded filePath (canonicalizeUrl url) m
160− where
161− -- Check scheme-relative links
162− canonicalizeUrl url = if schemeRelative url then "http:" ++ url else url
163− schemeRelative = isPrefixOf "//"
164−
165−
166−--------------------------------------------------------------------------------
167−checkUrlIfNeeded :: FilePath -> URL -> MVar CheckerWrite -> Checker ()
168−checkUrlIfNeeded filepath url m = do
169− logger <- checkerLogger <$> ask
170− needsCheck <- (== All) . checkerCheck <$> ask
171− checked <- (url `Map.member`) <$> get
172− if not needsCheck || checked
173− then Logger.debug logger "Already checked, skipping"
174− else do modify $ Map.insert url m
175− checkUrl filepath url
176−
177−
178−--------------------------------------------------------------------------------
179−checkUrl :: FilePath -> URL -> Checker ()
180−checkUrl filePath url
181− | isExternal url = checkExternalUrl url
182− | hasProtocol url = skip url $ Just "Unknown protocol, skipping"
183− | otherwise = checkInternalUrl filePath url
184− where
185− validProtoChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "+-."
186− hasProtocol str = case break (== ':') str of
187− (proto, ':' : _) -> all (`elem` validProtoChars) proto
188− _ -> False
189−
190−
191−--------------------------------------------------------------------------------
192−ok :: URL -> Checker ()
193−ok url = putCheckResult url mempty {checkerOk = 1}
194−
195−
196−--------------------------------------------------------------------------------
197−skip :: URL -> Maybe String -> Checker ()
198−skip url maybeReason = do
199− logger <- checkerLogger <$> ask
200− case maybeReason of
201− Nothing -> return ()
202− Just reason -> Logger.debug logger reason
203− putCheckResult url mempty {checkerOk = 1}
204−
205−
206−--------------------------------------------------------------------------------
207−faulty :: URL -> Maybe String -> Checker ()
208−faulty url reason = do
209− logger <- checkerLogger <$> ask
210− Logger.error logger $ "Broken link to " ++ show url ++ explanation
211− putCheckResult url mempty {checkerFaulty = 1}
212− where
213− formatExplanation = (" (" ++) . (++ ")")
214− explanation = maybe "" formatExplanation reason
215−
216−
217−--------------------------------------------------------------------------------
218−putCheckResult :: URL -> CheckerWrite -> Checker ()
219−putCheckResult url result = do
220− state <- get
221− let maybeMVar = Map.lookup url state
222− case maybeMVar of
223− Just m -> liftIO $ putMVar m result
224− Nothing -> do
225− logger <- checkerLogger <$> ask
226− Logger.debug logger "Failed to find existing entry for checked URL"
227−
228−
229−--------------------------------------------------------------------------------
230−checkInternalUrl :: FilePath -> URL -> Checker ()
231−checkInternalUrl base url = case url' of
232− "" -> ok url
233− _ -> do
234− config <- checkerConfig <$> ask
235− let dest = destinationDirectory config
236− dir = takeDirectory base
237− filePath
238− | "/" `isPrefixOf` url' = dest ++ url'
239− | otherwise = dir </> url'
240−
241− exists <- checkFileExists filePath
242− if exists then ok url else faulty url Nothing
243− where
244− url' = stripFragments $ unEscapeString url
245−
246−
247−--------------------------------------------------------------------------------
248−checkExternalUrl :: URL -> Checker ()
249−#ifdef CHECK_EXTERNAL
250−checkExternalUrl url = do
251− result <- requestExternalUrl url
252− case result of
253− Left (SomeException e) ->
254− case (cast e :: Maybe SomeAsyncException) of
255− Just ae -> throw ae
256− _ -> faulty url (Just $ showException e)
257− Right _ -> ok url
258− where
259− -- Convert exception to a concise form
260− showException e = case cast e of
261− Just (Http.HttpExceptionRequest _ e') -> show e'
262− _ -> head $ words $ show e
263−
264−requestExternalUrl :: URL -> Checker (Either SomeException Bool)
265−requestExternalUrl url = liftIO $ try $ do
266− mgr <- Http.newManager Http.tlsManagerSettings
267− runResourceT $ do
268− request <- Http.parseRequest url
269− response <- Http.http (settings request) mgr
270− let code = Http.statusCode (Http.responseStatus response)
271− return $ code >= 200 && code < 300
272− where
273− -- Add additional request info
274− settings r = r
275− { Http.method = "HEAD"
276− , Http.redirectCount = 10
277− , Http.requestHeaders = ("User-Agent", ua) : Http.requestHeaders r
278− }
279−
280− -- Nice user agent info
281− ua = fromString $ "hakyll-check/" ++
282− (intercalate "." $ map show $ versionBranch Paths_hakyll.version)
283−#else
284−checkExternalUrl url = skip url Nothing
285−#endif
286−
287−
288−--------------------------------------------------------------------------------
289−-- | Wraps doesFileExist, also checks for index.html
290−checkFileExists :: FilePath -> Checker Bool
291−checkFileExists filePath = liftIO $ do
292− file <- doesFileExist filePath
293− dir <- doesDirectoryExist filePath
294− case (file, dir) of
295− (True, _) -> return True
296− (_, True) -> doesFileExist $ filePath </> "index.html"
297− _ -> return False
298−
299−
300−--------------------------------------------------------------------------------
301−stripFragments :: String -> String
302−stripFragments = takeWhile (not . flip elem ['?', '#'])
lib/Hakyll/Commands.hsdeleted−164
@@ -1,164 +0,0 @@
1− --------------------------------------------------------------------------------
2−-- | Implementation of Hakyll commands: build, preview...
3−{-# LANGUAGE CPP #-}
4−module Hakyll.Commands
5− ( Check(..)
6− , build
7− , check
8− , clean
9− , preview
10− , rebuild
11− , server
12− , deploy
13− , watch
14− ) where
15−
16−
17−--------------------------------------------------------------------------------
18−import Control.Concurrent
19−import System.Exit (ExitCode)
20−
21−
22−--------------------------------------------------------------------------------
23−import Hakyll.Check (Check(..))
24−import qualified Hakyll.Check as Check
25−import Hakyll.Core.Configuration
26−import Hakyll.Core.Logger (Logger)
27−import qualified Hakyll.Core.Logger as Logger
28−import Hakyll.Core.Rules
29−import Hakyll.Core.Rules.Internal
30−import Hakyll.Core.Runtime
31−import Hakyll.Core.Util.File
32−
33−
34−--------------------------------------------------------------------------------
35−#ifdef WATCH_SERVER
36−import Hakyll.Preview.Poll (watchUpdates)
37−#endif
38−
39−#ifdef PREVIEW_SERVER
40−import Hakyll.Preview.Server
41−#endif
42−
43−#ifdef mingw32_HOST_OS
44−import Control.Monad (void)
45−import System.IO.Error (catchIOError)
46−#endif
47−
48−
49−--------------------------------------------------------------------------------
50−-- | Build the site
51−build :: Configuration -> Logger -> Rules a -> IO ExitCode
52−build conf logger rules = fst <$> run conf logger rules
53−
54−
55−--------------------------------------------------------------------------------
56−-- | Run the checker and exit
57−check :: Configuration -> Logger -> Check.Check -> IO ExitCode
58−check = Check.check
59−
60−
61−--------------------------------------------------------------------------------
62−-- | Remove the output directories
63−clean :: Configuration -> Logger -> IO ()
64−clean conf logger = do
65− remove $ destinationDirectory conf
66− remove $ storeDirectory conf
67− remove $ tmpDirectory conf
68− where
69− remove dir = do
70− Logger.header logger $ "Removing " ++ dir ++ "..."
71− removeDirectory dir
72−
73−
74−--------------------------------------------------------------------------------
75−-- | Preview the site
76−preview :: Configuration -> Logger -> Rules a -> Int -> IO ()
77−#ifdef PREVIEW_SERVER
78−preview conf logger rules port = do
79− deprecatedMessage
80− watch conf logger "0.0.0.0" port True rules
81− where
82− deprecatedMessage = mapM_ putStrLn [ "The preview command has been deprecated."
83− , "Use the watch command for recompilation and serving."
84− ]
85−#else
86−preview _ _ _ _ = previewServerDisabled
87−#endif
88−
89−
90−--------------------------------------------------------------------------------
91−-- | Watch and recompile for changes
92−
93−watch :: Configuration -> Logger -> String -> Int -> Bool -> Rules a -> IO ()
94−#ifdef WATCH_SERVER
95−watch conf logger host port runServer rules = do
96−#ifndef mingw32_HOST_OS
97− _ <- forkIO $ watchUpdates conf update
98−#else
99− -- Force windows users to compile with -threaded flag, as otherwise
100− -- thread is blocked indefinitely.
101− catchIOError (void $ forkOS $ watchUpdates conf update) $ do
102− fail $ "Hakyll.Commands.watch: Could not start update watching " ++
103− "thread. Did you compile with -threaded flag?"
104−#endif
105− server'
106− where
107− update = do
108− (_, ruleSet) <- run conf logger rules
109− return $ rulesPattern ruleSet
110− loop = threadDelay 100000 >> loop
111− server' = if runServer then server conf logger host port else loop
112−#else
113−watch _ _ _ _ _ _ = watchServerDisabled
114−#endif
115−
116−--------------------------------------------------------------------------------
117−-- | Rebuild the site
118−rebuild :: Configuration -> Logger -> Rules a -> IO ExitCode
119−rebuild conf logger rules =
120− clean conf logger >> build conf logger rules
121−
122−--------------------------------------------------------------------------------
123−-- | Start a server
124−server :: Configuration -> Logger -> String -> Int -> IO ()
125−#ifdef PREVIEW_SERVER
126−server conf logger host port = do
127− let destination = destinationDirectory conf
128− staticServer logger destination host port
129−#else
130−server _ _ _ _ = previewServerDisabled
131−#endif
132−
133−
134−--------------------------------------------------------------------------------
135−-- | Upload the site
136−deploy :: Configuration -> IO ExitCode
137−deploy conf = deploySite conf conf
138−
139−
140−--------------------------------------------------------------------------------
141−-- | Print a warning message about the preview serving not being enabled
142−#ifndef PREVIEW_SERVER
143−previewServerDisabled :: IO ()
144−previewServerDisabled =
145− mapM_ putStrLn
146− [ "PREVIEW SERVER"
147− , ""
148− , "The preview server is not enabled in the version of Hakyll. To"
149− , "enable it, set the flag to True and recompile Hakyll."
150− , "Alternatively, use an external tool to serve your site directory."
151− ]
152−#endif
153−
154−#ifndef WATCH_SERVER
155−watchServerDisabled :: IO ()
156−watchServerDisabled =
157− mapM_ putStrLn
158− [ "WATCH SERVER"
159− , ""
160− , "The watch server is not enabled in the version of Hakyll. To"
161− , "enable it, set the flag to True and recompile Hakyll."
162− , "Alternatively, use an external tool to serve your site directory."
163− ]
164−#endif
lib/Hakyll/Core/Compiler/Internal.hsdeleted−364
@@ -1,364 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Internally used compiler module
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveFunctor #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE GADTs #-}
7−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−module Hakyll.Core.Compiler.Internal
10− ( -- * Types
11− Snapshot
12− , CompilerRead (..)
13− , CompilerWrite (..)
14− , CompilerErrors (..)
15− , CompilerResult (..)
16− , Compiler (..)
17− , runCompiler
18−
19− -- * Core operations
20− , compilerResult
21− , compilerTell
22− , compilerAsk
23− , compilerUnsafeIO
24−
25− -- * Error operations
26− , compilerThrow
27− , compilerNoResult
28− , compilerCatch
29− , compilerTry
30− , compilerErrorMessages
31−
32− -- * Utilities
33− , compilerDebugEntries
34− , compilerTellDependencies
35− , compilerTellCacheHits
36− ) where
37−
38−
39−--------------------------------------------------------------------------------
40−import Control.Applicative (Alternative (..))
41−import Control.Exception (SomeException, handle)
42−import Control.Monad (forM_)
43−import Control.Monad.Except (MonadError (..))
44−import Data.List.NonEmpty (NonEmpty (..))
45−import qualified Data.List.NonEmpty as NonEmpty
46−#if MIN_VERSION_base(4,9,0)
47−import Data.Semigroup (Semigroup (..))
48−#endif
49−import Data.Set (Set)
50−import qualified Data.Set as S
51−
52−
53−--------------------------------------------------------------------------------
54−import Hakyll.Core.Configuration
55−import Hakyll.Core.Dependencies
56−import Hakyll.Core.Identifier
57−import Hakyll.Core.Identifier.Pattern
58−import qualified Hakyll.Core.Logger as Logger
59−import Hakyll.Core.Metadata
60−import Hakyll.Core.Provider
61−import Hakyll.Core.Routes
62−import Hakyll.Core.Store
63−
64−
65−--------------------------------------------------------------------------------
66−-- | Whilst compiling an item, it possible to save multiple snapshots of it, and
67−-- not just the final result.
68−type Snapshot = String
69−
70−
71−--------------------------------------------------------------------------------
72−-- | Environment in which a compiler runs
73−data CompilerRead = CompilerRead
74− { -- | Main configuration
75− compilerConfig :: Configuration
76− , -- | Underlying identifier
77− compilerUnderlying :: Identifier
78− , -- | Resource provider
79− compilerProvider :: Provider
80− , -- | List of all known identifiers
81− compilerUniverse :: Set Identifier
82− , -- | Site routes
83− compilerRoutes :: Routes
84− , -- | Compiler store
85− compilerStore :: Store
86− , -- | Logger
87− compilerLogger :: Logger.Logger
88− }
89−
90−
91−--------------------------------------------------------------------------------
92−data CompilerWrite = CompilerWrite
93− { compilerDependencies :: [Dependency]
94− , compilerCacheHits :: Int
95− } deriving (Show)
96−
97−
98−--------------------------------------------------------------------------------
99−#if MIN_VERSION_base(4,9,0)
100−instance Semigroup CompilerWrite where
101− (<>) (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
102− CompilerWrite (d1 ++ d2) (h1 + h2)
103−
104−instance Monoid CompilerWrite where
105− mempty = CompilerWrite [] 0
106− mappend = (<>)
107−#else
108−instance Monoid CompilerWrite where
109− mempty = CompilerWrite [] 0
110− mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
111− CompilerWrite (d1 ++ d2) (h1 + h2)
112−#endif
113−
114−
115−--------------------------------------------------------------------------------
116−-- | Distinguishes reasons in a 'CompilerError'
117−data CompilerErrors a
118− -- | One or more exceptions occured during compilation
119− = CompilationFailure (NonEmpty a)
120− -- | Absence of any result, most notably in template contexts. May still
121− -- have error messages.
122− | CompilationNoResult [a]
123− deriving Functor
124−
125−
126−-- | Unwrap a `CompilerErrors`
127−compilerErrorMessages :: CompilerErrors a -> [a]
128−compilerErrorMessages (CompilationFailure x) = NonEmpty.toList x
129−compilerErrorMessages (CompilationNoResult x) = x
130−
131−
132−--------------------------------------------------------------------------------
133−-- | An intermediate result of a compilation step
134−data CompilerResult a
135− = CompilerDone a CompilerWrite
136− | CompilerSnapshot Snapshot (Compiler a)
137− | CompilerRequire (Identifier, Snapshot) (Compiler a)
138− | CompilerError (CompilerErrors String)
139−
140−
141−--------------------------------------------------------------------------------
142−-- | A monad which lets you compile items and takes care of dependency tracking
143−-- for you.
144−newtype Compiler a = Compiler
145− { unCompiler :: CompilerRead -> IO (CompilerResult a)
146− }
147−
148−
149−--------------------------------------------------------------------------------
150−instance Functor Compiler where
151− fmap f (Compiler c) = Compiler $ \r -> do
152− res <- c r
153− return $ case res of
154− CompilerDone x w -> CompilerDone (f x) w
155− CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
156− CompilerRequire i c' -> CompilerRequire i (fmap f c')
157− CompilerError e -> CompilerError e
158− {-# INLINE fmap #-}
159−
160−
161−--------------------------------------------------------------------------------
162−instance Monad Compiler where
163− return x = compilerResult $ CompilerDone x mempty
164− {-# INLINE return #-}
165−
166− Compiler c >>= f = Compiler $ \r -> do
167− res <- c r
168− case res of
169− CompilerDone x w -> do
170− res' <- unCompiler (f x) r
171− return $ case res' of
172− CompilerDone y w' -> CompilerDone y (w `mappend` w')
173− CompilerSnapshot s c' -> CompilerSnapshot s $ do
174− compilerTell w -- Save dependencies!
175− c'
176− CompilerRequire i c' -> CompilerRequire i $ do
177− compilerTell w -- Save dependencies!
178− c'
179− CompilerError e -> CompilerError e
180−
181− CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
182− CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183− CompilerError e -> return $ CompilerError e
184− {-# INLINE (>>=) #-}
185−
186− fail = compilerThrow . return
187− {-# INLINE fail #-}
188−
189−
190−--------------------------------------------------------------------------------
191−instance Applicative Compiler where
192− pure x = return x
193− {-# INLINE pure #-}
194−
195− f <*> x = f >>= \f' -> fmap f' x
196− {-# INLINE (<*>) #-}
197−
198−
199−--------------------------------------------------------------------------------
200−-- | Access provided metadata from anywhere
201−instance MonadMetadata Compiler where
202− getMetadata = compilerGetMetadata
203− getMatches = compilerGetMatches
204−
205−
206−--------------------------------------------------------------------------------
207−-- | Compilation may fail with multiple error messages.
208−-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
209−instance MonadError [String] Compiler where
210− throwError = compilerThrow
211− catchError c = compilerCatch c . (. compilerErrorMessages)
212−
213−
214−--------------------------------------------------------------------------------
215−-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
216−runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
217−runCompiler compiler read' = handle handler $ unCompiler compiler read'
218− where
219− handler :: SomeException -> IO (CompilerResult a)
220− handler e = return $ CompilerError $ CompilationFailure $ show e :| []
221−
222−
223−--------------------------------------------------------------------------------
224−-- | Trying alternative compilers if the first fails, regardless whether through
225−-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226−-- Aggregates error messages if all fail.
227−instance Alternative Compiler where
228− empty = compilerNoResult []
229− x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
230− case (rx, ry) of
231− (CompilationFailure xs, CompilationFailure ys) ->
232− compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys
233− (CompilationFailure xs, CompilationNoResult ys) ->
234− debug ys >> compilerThrow (NonEmpty.toList xs)
235− (CompilationNoResult xs, CompilationFailure ys) ->
236− debug xs >> compilerThrow (NonEmpty.toList ys)
237− (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys
238− ))
239− where
240− debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
241− {-# INLINE (<|>) #-}
242−
243−
244−--------------------------------------------------------------------------------
245−-- | Put the result back in a compiler
246−compilerResult :: CompilerResult a -> Compiler a
247−compilerResult x = Compiler $ \_ -> return x
248−{-# INLINE compilerResult #-}
249−
250−
251−--------------------------------------------------------------------------------
252−-- | Get the current environment
253−compilerAsk :: Compiler CompilerRead
254−compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
255−{-# INLINE compilerAsk #-}
256−
257−
258−--------------------------------------------------------------------------------
259−-- | Put a 'CompilerWrite'
260−compilerTell :: CompilerWrite -> Compiler ()
261−compilerTell = compilerResult . CompilerDone ()
262−{-# INLINE compilerTell #-}
263−
264−
265−--------------------------------------------------------------------------------
266−-- | Run an IO computation without dependencies in a Compiler
267−compilerUnsafeIO :: IO a -> Compiler a
268−compilerUnsafeIO io = Compiler $ \_ -> do
269− x <- io
270− return $ CompilerDone x mempty
271−{-# INLINE compilerUnsafeIO #-}
272−
273−
274−--------------------------------------------------------------------------------
275−-- | Throw errors in the 'Compiler'.
276−--
277−-- If no messages are given, this is considered a 'CompilationNoResult' error.
278−-- Otherwise, it is treated as a proper compilation failure.
279−compilerThrow :: [String] -> Compiler a
280−compilerThrow = compilerResult . CompilerError .
281− maybe (CompilationNoResult []) CompilationFailure .
282− NonEmpty.nonEmpty
283−
284−-- | Put a 'CompilerError' with multiple messages as 'CompilationNoResult'
285−compilerNoResult :: [String] -> Compiler a
286−compilerNoResult = compilerResult . CompilerError . CompilationNoResult
287−
288−
289−--------------------------------------------------------------------------------
290−-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'
291−--
292−-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right
293−compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
294−compilerTry (Compiler x) = Compiler $ \r -> do
295− res <- x r
296− case res of
297− CompilerDone res' w -> return (CompilerDone (Right res') w)
298− CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))
299− CompilerRequire i c -> return (CompilerRequire i (compilerTry c))
300− CompilerError e -> return (CompilerDone (Left e) mempty)
301−{-# INLINE compilerTry #-}
302−
303−
304−--------------------------------------------------------------------------------
305−-- | Allows you to recover from 'CompilerError's.
306−-- Uses the same parameter order as 'catchError' so that it can be used infix.
307−--
308−-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309−compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
310−compilerCatch (Compiler x) f = Compiler $ \r -> do
311− res <- x r
312− case res of
313− CompilerDone res' w -> return (CompilerDone res' w)
314− CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
315− CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316− CompilerError e -> unCompiler (f e) r
317−{-# INLINE compilerCatch #-}
318−
319−
320−--------------------------------------------------------------------------------
321−compilerDebugLog :: [String] -> Compiler ()
322−compilerDebugLog ms = do
323− logger <- compilerLogger <$> compilerAsk
324− compilerUnsafeIO $ forM_ ms $ Logger.debug logger
325−
326−--------------------------------------------------------------------------------
327−-- | Pass a list of messages with a heading to the debug logger
328−compilerDebugEntries :: String -> [String] -> Compiler ()
329−compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
330− where
331− indent = unlines . map (" "++) . lines
332−
333−
334−--------------------------------------------------------------------------------
335−compilerTellDependencies :: [Dependency] -> Compiler ()
336−compilerTellDependencies ds = do
337− compilerDebugLog $ map (\d ->
338− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
339− compilerTell mempty {compilerDependencies = ds}
340−{-# INLINE compilerTellDependencies #-}
341−
342−
343−--------------------------------------------------------------------------------
344−compilerTellCacheHits :: Int -> Compiler ()
345−compilerTellCacheHits ch = compilerTell mempty {compilerCacheHits = ch}
346−{-# INLINE compilerTellCacheHits #-}
347−
348−
349−--------------------------------------------------------------------------------
350−compilerGetMetadata :: Identifier -> Compiler Metadata
351−compilerGetMetadata identifier = do
352− provider <- compilerProvider <$> compilerAsk
353− compilerTellDependencies [IdentifierDependency identifier]
354− compilerUnsafeIO $ resourceMetadata provider identifier
355−
356−
357−--------------------------------------------------------------------------------
358−compilerGetMatches :: Pattern -> Compiler [Identifier]
359−compilerGetMatches pattern = do
360− universe <- compilerUniverse <$> compilerAsk
361− let matching = filterMatches pattern $ S.toList universe
362− set' = S.fromList matching
363− compilerTellDependencies [PatternDependency pattern set']
364− return matching
lib/Hakyll/Core/Identifier/Pattern/Internal.hsdeleted−92
@@ -1,92 +0,0 @@
1−-- | This internal module is mostly here to prevent CPP conflicting with Haskell
2−-- comments.
3−{-# LANGUAGE CPP #-}
4−module Hakyll.Core.Identifier.Pattern.Internal
5− ( GlobComponent (..)
6− , Pattern (..)
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Data.Binary (Binary (..), getWord8, putWord8)
12−import Data.Set (Set)
13−
14−
15−--------------------------------------------------------------------------------
16−#if MIN_VERSION_base(4,9,0)
17−import Data.Semigroup (Semigroup (..))
18−#endif
19−
20−
21−--------------------------------------------------------------------------------
22−import Hakyll.Core.Identifier
23−
24−
25−--------------------------------------------------------------------------------
26−-- | Elements of a glob pattern
27−data GlobComponent
28− = Capture
29− | CaptureMany
30− | Literal String
31− deriving (Eq, Show)
32−
33−
34−--------------------------------------------------------------------------------
35−instance Binary GlobComponent where
36− put Capture = putWord8 0
37− put CaptureMany = putWord8 1
38− put (Literal s) = putWord8 2 >> put s
39−
40− get = getWord8 >>= \t -> case t of
41− 0 -> pure Capture
42− 1 -> pure CaptureMany
43− 2 -> Literal <$> get
44− _ -> error "Data.Binary.get: Invalid GlobComponent"
45−
46−
47−--------------------------------------------------------------------------------
48−-- | Type that allows matching on identifiers
49−data Pattern
50− = Everything
51− | Complement Pattern
52− | And Pattern Pattern
53− | Glob [GlobComponent]
54− | List (Set Identifier)
55− | Regex String
56− | Version (Maybe String)
57− deriving (Show)
58−
59−
60−--------------------------------------------------------------------------------
61−instance Binary Pattern where
62− put Everything = putWord8 0
63− put (Complement p) = putWord8 1 >> put p
64− put (And x y) = putWord8 2 >> put x >> put y
65− put (Glob g) = putWord8 3 >> put g
66− put (List is) = putWord8 4 >> put is
67− put (Regex r) = putWord8 5 >> put r
68− put (Version v) = putWord8 6 >> put v
69−
70− get = getWord8 >>= \t -> case t of
71− 0 -> pure Everything
72− 1 -> Complement <$> get
73− 2 -> And <$> get <*> get
74− 3 -> Glob <$> get
75− 4 -> List <$> get
76− 5 -> Regex <$> get
77− _ -> Version <$> get
78−
79−
80−--------------------------------------------------------------------------------
81−#if MIN_VERSION_base(4,9,0)
82−instance Semigroup Pattern where
83− (<>) = And
84−
85−instance Monoid Pattern where
86− mempty = Everything
87− mappend = (<>)
88−#else
89−instance Monoid Pattern where
90− mempty = Everything
91− mappend = And
92−#endif
lib/Hakyll/Core/Metadata.hsdeleted−138
@@ -1,138 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Core.Metadata
3− ( Metadata
4− , lookupString
5− , lookupStringList
6−
7− , MonadMetadata (..)
8− , getMetadataField
9− , getMetadataField'
10− , makePatternDependency
11−
12− , BinaryMetadata (..)
13− ) where
14−
15−
16−--------------------------------------------------------------------------------
17−import Control.Arrow (second)
18−import Control.Monad (forM)
19−import Data.Binary (Binary (..), getWord8,
20− putWord8, Get)
21−import qualified Data.HashMap.Strict as HMS
22−import qualified Data.Set as S
23−import qualified Data.Text as T
24−import qualified Data.Vector as V
25−import qualified Data.Yaml.Extended as Yaml
26−import Hakyll.Core.Dependencies
27−import Hakyll.Core.Identifier
28−import Hakyll.Core.Identifier.Pattern
29−
30−
31−--------------------------------------------------------------------------------
32−type Metadata = Yaml.Object
33−
34−
35−--------------------------------------------------------------------------------
36−lookupString :: String -> Metadata -> Maybe String
37−lookupString key meta = HMS.lookup (T.pack key) meta >>= Yaml.toString
38−
39−
40−--------------------------------------------------------------------------------
41−lookupStringList :: String -> Metadata -> Maybe [String]
42−lookupStringList key meta =
43− HMS.lookup (T.pack key) meta >>= Yaml.toList >>= mapM Yaml.toString
44−
45−
46−--------------------------------------------------------------------------------
47−class Monad m => MonadMetadata m where
48− getMetadata :: Identifier -> m Metadata
49− getMatches :: Pattern -> m [Identifier]
50−
51− getAllMetadata :: Pattern -> m [(Identifier, Metadata)]
52− getAllMetadata pattern = do
53− matches' <- getMatches pattern
54− forM matches' $ \id' -> do
55− metadata <- getMetadata id'
56− return (id', metadata)
57−
58−
59−--------------------------------------------------------------------------------
60−getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
61−getMetadataField identifier key = do
62− metadata <- getMetadata identifier
63− return $ lookupString key metadata
64−
65−
66−--------------------------------------------------------------------------------
67−-- | Version of 'getMetadataField' which throws an error if the field does not
68−-- exist.
69−getMetadataField' :: MonadMetadata m => Identifier -> String -> m String
70−getMetadataField' identifier key = do
71− field <- getMetadataField identifier key
72− case field of
73− Just v -> return v
74− Nothing -> fail $ "Hakyll.Core.Metadata.getMetadataField': " ++
75− "Item " ++ show identifier ++ " has no metadata field " ++ show key
76−
77−
78−--------------------------------------------------------------------------------
79−makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
80−makePatternDependency pattern = do
81− matches' <- getMatches pattern
82− return $ PatternDependency pattern (S.fromList matches')
83−
84−
85−--------------------------------------------------------------------------------
86−-- | Newtype wrapper for serialization.
87−newtype BinaryMetadata = BinaryMetadata
88− {unBinaryMetadata :: Metadata}
89−
90−
91−instance Binary BinaryMetadata where
92− put (BinaryMetadata obj) = put (BinaryYaml $ Yaml.Object obj)
93− get = do
94− BinaryYaml (Yaml.Object obj) <- get
95− return $ BinaryMetadata obj
96−
97−
98−--------------------------------------------------------------------------------
99−newtype BinaryYaml = BinaryYaml {unBinaryYaml :: Yaml.Value}
100−
101−
102−--------------------------------------------------------------------------------
103−instance Binary BinaryYaml where
104− put (BinaryYaml yaml) = case yaml of
105− Yaml.Object obj -> do
106− putWord8 0
107− let list :: [(T.Text, BinaryYaml)]
108− list = map (second BinaryYaml) $ HMS.toList obj
109− put list
110−
111− Yaml.Array arr -> do
112− putWord8 1
113− let list = map BinaryYaml (V.toList arr) :: [BinaryYaml]
114− put list
115−
116− Yaml.String s -> putWord8 2 >> put s
117− Yaml.Number n -> putWord8 3 >> put n
118− Yaml.Bool b -> putWord8 4 >> put b
119− Yaml.Null -> putWord8 5
120−
121− get = do
122− tag <- getWord8
123− case tag of
124− 0 -> do
125− list <- get :: Get [(T.Text, BinaryYaml)]
126− return $ BinaryYaml $ Yaml.Object $
127− HMS.fromList $ map (second unBinaryYaml) list
128−
129− 1 -> do
130− list <- get :: Get [BinaryYaml]
131− return $ BinaryYaml $
132− Yaml.Array $ V.fromList $ map unBinaryYaml list
133−
134− 2 -> BinaryYaml . Yaml.String <$> get
135− 3 -> BinaryYaml . Yaml.Number <$> get
136− 4 -> BinaryYaml . Yaml.Bool <$> get
137− 5 -> return $ BinaryYaml Yaml.Null
138− _ -> fail "Data.Binary.get: Invalid Binary Metadata"
lib/Hakyll/Core/Provider.hsdeleted−43
@@ -1,43 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | This module provides an wrapper API around the file system which does some
3−-- caching.
4−module Hakyll.Core.Provider
5− ( -- * Constructing resource providers
6− Internal.Provider
7− , newProvider
8−
9− -- * Querying resource properties
10− , Internal.resourceList
11− , Internal.resourceExists
12− , Internal.resourceFilePath
13− , Internal.resourceModified
14− , Internal.resourceModificationTime
15−
16− -- * Access to raw resource content
17− , Internal.resourceString
18− , Internal.resourceLBS
19−
20− -- * Access to metadata and body content
21− , Internal.resourceMetadata
22− , Internal.resourceBody
23− ) where
24−
25−
26−--------------------------------------------------------------------------------
27−import qualified Hakyll.Core.Provider.Internal as Internal
28−import qualified Hakyll.Core.Provider.MetadataCache as Internal
29−import Hakyll.Core.Store (Store)
30−
31−
32−--------------------------------------------------------------------------------
33−-- | Create a resource provider
34−newProvider :: Store -- ^ Store to use
35− -> (FilePath -> IO Bool) -- ^ Should we ignore this file?
36− -> FilePath -- ^ Search directory
37− -> IO Internal.Provider -- ^ Resulting provider
38−newProvider store ignore directory = do
39− -- Delete metadata cache where necessary
40− p <- Internal.newProvider store ignore directory
41− mapM_ (Internal.resourceInvalidateMetadataCache p) $
42− filter (Internal.resourceModified p) $ Internal.resourceList p
43− return p
lib/Hakyll/Core/Provider/Internal.hsdeleted−202
@@ -1,202 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE DeriveDataTypeable #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−module Hakyll.Core.Provider.Internal
6− ( ResourceInfo (..)
7− , Provider (..)
8− , newProvider
9−
10− , resourceList
11− , resourceExists
12−
13− , resourceFilePath
14− , resourceString
15− , resourceLBS
16−
17− , resourceModified
18− , resourceModificationTime
19− ) where
20−
21−
22−--------------------------------------------------------------------------------
23−import Control.DeepSeq (NFData (..), deepseq)
24−import Control.Monad (forM)
25−import Data.Binary (Binary (..))
26−import qualified Data.ByteString.Lazy as BL
27−import Data.Map (Map)
28−import qualified Data.Map as M
29−import Data.Maybe (fromMaybe)
30−import Data.Set (Set)
31−import qualified Data.Set as S
32−import Data.Time (Day (..), UTCTime (..))
33−import Data.Typeable (Typeable)
34−import System.Directory (getModificationTime)
35−import System.FilePath (addExtension, (</>))
36−
37−
38−--------------------------------------------------------------------------------
39−#if !MIN_VERSION_directory(1,2,0)
40−import Data.Time (readTime)
41−import System.Locale (defaultTimeLocale)
42−import System.Time (formatCalendarTime, toCalendarTime)
43−#endif
44−
45−
46−--------------------------------------------------------------------------------
47−import Hakyll.Core.Identifier
48−import Hakyll.Core.Store (Store)
49−import qualified Hakyll.Core.Store as Store
50−import Hakyll.Core.Util.File
51−
52−
53−--------------------------------------------------------------------------------
54−-- | Because UTCTime doesn't have a Binary instance...
55−newtype BinaryTime = BinaryTime {unBinaryTime :: UTCTime}
56− deriving (Eq, NFData, Ord, Show, Typeable)
57−
58−
59−--------------------------------------------------------------------------------
60−instance Binary BinaryTime where
61− put (BinaryTime (UTCTime (ModifiedJulianDay d) dt)) =
62− put d >> put (toRational dt)
63−
64− get = fmap BinaryTime $ UTCTime
65− <$> (ModifiedJulianDay <$> get)
66− <*> (fromRational <$> get)
67−
68−
69−--------------------------------------------------------------------------------
70−data ResourceInfo = ResourceInfo
71− { resourceInfoModified :: BinaryTime
72− , resourceInfoMetadata :: Maybe Identifier
73− } deriving (Show, Typeable)
74−
75−
76−--------------------------------------------------------------------------------
77−instance Binary ResourceInfo where
78− put (ResourceInfo mtime meta) = put mtime >> put meta
79− get = ResourceInfo <$> get <*> get
80−
81−
82−--------------------------------------------------------------------------------
83−instance NFData ResourceInfo where
84− rnf (ResourceInfo mtime meta) = rnf mtime `seq` rnf meta `seq` ()
85−
86−
87−--------------------------------------------------------------------------------
88−-- | Responsible for retrieving and listing resources
89−data Provider = Provider
90− { -- Top of the provided directory
91− providerDirectory :: FilePath
92− , -- | A list of all files found
93− providerFiles :: Map Identifier ResourceInfo
94− , -- | A list of the files from the previous run
95− providerOldFiles :: Map Identifier ResourceInfo
96− , -- | Underlying persistent store for caching
97− providerStore :: Store
98− } deriving (Show)
99−
100−
101−--------------------------------------------------------------------------------
102−-- | Create a resource provider
103−newProvider :: Store -- ^ Store to use
104− -> (FilePath -> IO Bool) -- ^ Should we ignore this file?
105− -> FilePath -- ^ Search directory
106− -> IO Provider -- ^ Resulting provider
107−newProvider store ignore directory = do
108− list <- map fromFilePath <$> getRecursiveContents ignore directory
109− let universe = S.fromList list
110− files <- fmap (maxmtime . M.fromList) $ forM list $ \identifier -> do
111− rInfo <- getResourceInfo directory universe identifier
112− return (identifier, rInfo)
113−
114− -- Get the old files from the store, and then immediately replace them by
115− -- the new files.
116− oldFiles <- fromMaybe mempty . Store.toMaybe <$> Store.get store oldKey
117− oldFiles `deepseq` Store.set store oldKey files
118−
119− return $ Provider directory files oldFiles store
120− where
121− oldKey = ["Hakyll.Core.Provider.Internal.newProvider", "oldFiles"]
122−
123− -- Update modified if metadata is modified
124− maxmtime files = flip M.map files $ \rInfo@(ResourceInfo mtime meta) ->
125− let metaMod = fmap resourceInfoModified $ meta >>= flip M.lookup files
126− in rInfo {resourceInfoModified = maybe mtime (max mtime) metaMod}
127−
128−
129−--------------------------------------------------------------------------------
130−getResourceInfo :: FilePath -> Set Identifier -> Identifier -> IO ResourceInfo
131−getResourceInfo directory universe identifier = do
132− mtime <- fileModificationTime $ directory </> toFilePath identifier
133− return $ ResourceInfo (BinaryTime mtime) $
134− if mdRsc `S.member` universe then Just mdRsc else Nothing
135− where
136− mdRsc = fromFilePath $ flip addExtension "metadata" $ toFilePath identifier
137−
138−
139−--------------------------------------------------------------------------------
140−resourceList :: Provider -> [Identifier]
141−resourceList = M.keys . providerFiles
142−
143−
144−--------------------------------------------------------------------------------
145−-- | Check if a given resource exists
146−resourceExists :: Provider -> Identifier -> Bool
147−resourceExists provider =
148− (`M.member` providerFiles provider) . setVersion Nothing
149−
150−
151−--------------------------------------------------------------------------------
152−resourceFilePath :: Provider -> Identifier -> FilePath
153−resourceFilePath p i = providerDirectory p </> toFilePath i
154−
155−
156−--------------------------------------------------------------------------------
157−-- | Get the raw body of a resource as string
158−resourceString :: Provider -> Identifier -> IO String
159−resourceString p i = readFile $ resourceFilePath p i
160−
161−
162−--------------------------------------------------------------------------------
163−-- | Get the raw body of a resource of a lazy bytestring
164−resourceLBS :: Provider -> Identifier -> IO BL.ByteString
165−resourceLBS p i = BL.readFile $ resourceFilePath p i
166−
167−
168−--------------------------------------------------------------------------------
169−-- | A resource is modified if it or its metadata has changed
170−resourceModified :: Provider -> Identifier -> Bool
171−resourceModified p r = case (ri, oldRi) of
172− (Nothing, _) -> False
173− (Just _, Nothing) -> True
174− (Just n, Just o) ->
175− resourceInfoModified n > resourceInfoModified o ||
176− resourceInfoMetadata n /= resourceInfoMetadata o
177− where
178− normal = setVersion Nothing r
179− ri = M.lookup normal (providerFiles p)
180− oldRi = M.lookup normal (providerOldFiles p)
181−
182−
183−--------------------------------------------------------------------------------
184−resourceModificationTime :: Provider -> Identifier -> UTCTime
185−resourceModificationTime p i =
186− case M.lookup (setVersion Nothing i) (providerFiles p) of
187− Just ri -> unBinaryTime $ resourceInfoModified ri
188− Nothing -> error $
189− "Hakyll.Core.Provider.Internal.resourceModificationTime: " ++
190− "resource " ++ show i ++ " does not exist"
191−
192−
193−--------------------------------------------------------------------------------
194−fileModificationTime :: FilePath -> IO UTCTime
195−fileModificationTime fp = do
196−#if MIN_VERSION_directory(1,2,0)
197− getModificationTime fp
198−#else
199− ct <- toCalendarTime =<< getModificationTime fp
200− let str = formatCalendarTime defaultTimeLocale "%s" ct
201− return $ readTime defaultTimeLocale "%s" str
202−#endif
lib/Hakyll/Core/Provider/Metadata.hsdeleted−152
@@ -1,152 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Internal module to parse metadata
3−{-# LANGUAGE BangPatterns #-}
4−{-# LANGUAGE RecordWildCards #-}
5−module Hakyll.Core.Provider.Metadata
6− ( loadMetadata
7− , parsePage
8−
9− , MetadataException (..)
10− ) where
11−
12−
13−--------------------------------------------------------------------------------
14−import Control.Arrow (second)
15−import Control.Exception (Exception, throwIO)
16−import Control.Monad (guard)
17−import qualified Data.ByteString as B
18−import qualified Data.ByteString.Char8 as BC
19−import Data.List.Extended (breakWhen)
20−import qualified Data.Map as M
21−import Data.Maybe (fromMaybe)
22−import Data.Monoid ((<>))
23−import qualified Data.Text as T
24−import qualified Data.Text.Encoding as T
25−import qualified Data.Yaml as Yaml
26−import Hakyll.Core.Identifier
27−import Hakyll.Core.Metadata
28−import Hakyll.Core.Provider.Internal
29−import System.IO as IO
30−import System.IO.Error (modifyIOError, ioeSetLocation)
31−
32−
33−--------------------------------------------------------------------------------
34−loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
35−loadMetadata p identifier = do
36− hasHeader <- probablyHasMetadataHeader fp
37− (md, body) <- if hasHeader
38− then second Just <$> loadMetadataHeader fp
39− else return (mempty, Nothing)
40−
41− emd <- case mi of
42− Nothing -> return mempty
43− Just mi' -> loadMetadataFile $ resourceFilePath p mi'
44−
45− return (md <> emd, body)
46− where
47− normal = setVersion Nothing identifier
48− fp = resourceFilePath p identifier
49− mi = M.lookup normal (providerFiles p) >>= resourceInfoMetadata
50−
51−
52−--------------------------------------------------------------------------------
53−loadMetadataHeader :: FilePath -> IO (Metadata, String)
54−loadMetadataHeader fp = do
55− fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp
56− case parsePage fileContent of
57− Right x -> return x
58− Left err -> throwIO $ MetadataException fp err
59−
60−
61−--------------------------------------------------------------------------------
62−loadMetadataFile :: FilePath -> IO Metadata
63−loadMetadataFile fp = do
64− fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp
65− let errOrMeta = Yaml.decodeEither' fileContent
66− either (fail . show) return errOrMeta
67−
68−
69−--------------------------------------------------------------------------------
70−-- | Check if a file "probably" has a metadata header. The main goal of this is
71−-- to exclude binary files (which are unlikely to start with "---").
72−probablyHasMetadataHeader :: FilePath -> IO Bool
73−probablyHasMetadataHeader fp = do
74− handle <- IO.openFile fp IO.ReadMode
75− bs <- BC.hGet handle 1024
76− IO.hClose handle
77− return $ isMetadataHeader bs
78− where
79− isMetadataHeader bs =
80− let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
81− in BC.length pre >= 3 && BC.all (== '-') pre
82−
83−
84−--------------------------------------------------------------------------------
85−-- | Parse the page metadata and body.
86−splitMetadata :: String -> (Maybe String, String)
87−splitMetadata str0 = fromMaybe (Nothing, str0) $ do
88− guard $ leading >= 3
89− let !str1 = drop leading str0
90− guard $ all isNewline (take 1 str1)
91− let !(!meta, !content0) = breakWhen isTrailing str1
92− guard $ not $ null content0
93− let !content1 = drop (leading + 1) content0
94− !content2 = dropWhile isNewline $ dropWhile isInlineSpace content1
95− -- Adding this newline fixes the line numbers reported by the YAML parser.
96− -- It's a bit ugly but it works.
97− return (Just ('\n' : meta), content2)
98− where
99− -- Parse the leading "---"
100− !leading = length $ takeWhile (== '-') str0
101−
102− -- Predicate to recognize the trailing "---" or "..."
103− isTrailing [] = False
104− isTrailing (x : xs) =
105− isNewline x && length (takeWhile isDash xs) == leading
106−
107− -- Characters
108− isNewline c = c == '\n' || c == '\r'
109− isDash c = c == '-' || c == '.'
110− isInlineSpace c = c == '\t' || c == ' '
111−
112−
113−--------------------------------------------------------------------------------
114−parseMetadata :: String -> Either Yaml.ParseException Metadata
115−parseMetadata = Yaml.decodeEither' . T.encodeUtf8 . T.pack
116−
117−
118−--------------------------------------------------------------------------------
119−parsePage :: String -> Either Yaml.ParseException (Metadata, String)
120−parsePage fileContent = case mbMetaBlock of
121− Nothing -> return (mempty, content)
122− Just metaBlock -> case parseMetadata metaBlock of
123− Left err -> Left err
124− Right meta -> return (meta, content)
125− where
126− !(!mbMetaBlock, !content) = splitMetadata fileContent
127−
128−
129−--------------------------------------------------------------------------------
130−-- | Thrown in the IO monad if things go wrong. Provides a nice-ish error
131−-- message.
132−data MetadataException = MetadataException FilePath Yaml.ParseException
133−
134−
135−--------------------------------------------------------------------------------
136−instance Exception MetadataException
137−
138−
139−--------------------------------------------------------------------------------
140−instance Show MetadataException where
141− show (MetadataException fp err) =
142− fp ++ ": " ++ Yaml.prettyPrintParseException err ++ hint
143−
144− where
145− hint = case err of
146− Yaml.InvalidYaml (Just (Yaml.YamlParseException {..}))
147− | yamlProblem == problem -> "\n" ++
148− "Hint: if the metadata value contains characters such\n" ++
149− "as ':' or '-', try enclosing it in quotes."
150− _ -> ""
151−
152− problem = "mapping values are not allowed in this context"
lib/Hakyll/Core/UnixFilter.hsdeleted−160
@@ -1,160 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−
3−--------------------------------------------------------------------------------
4−-- | A Compiler that supports unix filters.
5−module Hakyll.Core.UnixFilter
6− ( unixFilter
7− , unixFilterLBS
8− ) where
9−
10−
11−--------------------------------------------------------------------------------
12−import Control.Concurrent (forkIO)
13−import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
14−import Control.DeepSeq (deepseq)
15−import Control.Monad (forM_)
16−import Data.ByteString.Lazy (ByteString)
17−import qualified Data.ByteString.Lazy as LB
18−import Data.IORef (newIORef, readIORef, writeIORef)
19−import System.Exit (ExitCode (..))
20−import System.IO (Handle, hClose, hFlush, hGetContents,
21− hPutStr, hSetEncoding, localeEncoding)
22−import System.Process
23−
24−--------------------------------------------------------------------------------
25−import Hakyll.Core.Compiler
26−
27−
28−--------------------------------------------------------------------------------
29−-- | Use a unix filter as compiler. For example, we could use the 'rev' program
30−-- as a compiler.
31−--
32−-- > rev :: Compiler (Item String)
33−-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
34−--
35−-- A more realistic example: one can use this to call, for example, the sass
36−-- compiler on CSS files. More information about sass can be found here:
37−--
38−-- <http://sass-lang.com/>
39−--
40−-- The code is fairly straightforward, given that we use @.scss@ for sass:
41−--
42−-- > match "style.scss" $ do
43−-- > route $ setExtension "css"
44−-- > compile $ getResourceString >>=
45−-- > withItemBody (unixFilter "sass" ["-s", "--scss"]) >>=
46−-- > return . fmap compressCss
47−unixFilter :: String -- ^ Program name
48− -> [String] -- ^ Program args
49− -> String -- ^ Program input
50− -> Compiler String -- ^ Program output
51−unixFilter = unixFilterWith writer reader
52− where
53− writer handle input = do
54− hSetEncoding handle localeEncoding
55− hPutStr handle input
56− reader handle = do
57− hSetEncoding handle localeEncoding
58− out <- hGetContents handle
59− deepseq out (return out)
60−
61−
62−--------------------------------------------------------------------------------
63−-- | Variant of 'unixFilter' that should be used for binary files
64−--
65−-- > match "music.wav" $ do
66−-- > route $ setExtension "ogg"
67−-- > compile $ getResourceLBS >>= withItemBody (unixFilterLBS "oggenc" ["-"])
68−unixFilterLBS :: String -- ^ Program name
69− -> [String] -- ^ Program args
70− -> ByteString -- ^ Program input
71− -> Compiler ByteString -- ^ Program output
72−unixFilterLBS = unixFilterWith LB.hPutStr $ \handle -> do
73− out <- LB.hGetContents handle
74− LB.length out `seq` return out
75−
76−
77−--------------------------------------------------------------------------------
78−-- | Overloaded compiler
79−unixFilterWith :: Monoid o
80− => (Handle -> i -> IO ()) -- ^ Writer
81− -> (Handle -> IO o) -- ^ Reader
82− -> String -- ^ Program name
83− -> [String] -- ^ Program args
84− -> i -- ^ Program input
85− -> Compiler o -- ^ Program output
86−unixFilterWith writer reader programName args input = do
87− debugCompiler ("Executing external program " ++ programName)
88− (output, err, exitCode) <- unsafeCompiler $
89− unixFilterIO writer reader programName args input
90− forM_ (lines err) debugCompiler
91− case exitCode of
92− ExitSuccess -> return output
93− ExitFailure e -> fail $
94− "Hakyll.Core.UnixFilter.unixFilterWith: " ++
95− unwords (programName : args) ++ " gave exit code " ++ show e ++
96− ". (Error: " ++ err ++ ")"
97−
98−
99−--------------------------------------------------------------------------------
100−-- | Internally used function
101−unixFilterIO :: Monoid o
102− => (Handle -> i -> IO ())
103− -> (Handle -> IO o)
104− -> String
105− -> [String]
106− -> i
107− -> IO (o, String, ExitCode)
108−unixFilterIO writer reader programName args input = do
109− -- The problem on Windows is that `proc` is unable to execute
110− -- batch stubs (eg. anything created using 'gem install ...') even if its in
111− -- `$PATH`. A solution to this issue is to execute the batch file explicitly
112− -- using `cmd /c batchfile` but there is no rational way to know where said
113− -- batchfile is on the system. Hence, we detect windows using the
114− -- CPP and instead of using `proc` to create the process, use `shell`
115− -- which will be able to execute everything `proc` can
116− -- as well as batch files.
117−#ifdef mingw32_HOST_OS
118− let pr = shell $ unwords (programName : args)
119−#else
120− let pr = proc programName args
121−#endif
122−
123− (Just inh, Just outh, Just errh, pid) <-
124− createProcess pr
125− { std_in = CreatePipe
126− , std_out = CreatePipe
127− , std_err = CreatePipe
128− }
129−
130− -- Create boxes
131− lock <- newEmptyMVar
132− outRef <- newIORef mempty
133− errRef <- newIORef ""
134−
135− -- Write the input to the child pipe
136− _ <- forkIO $ writer inh input >> hFlush inh >> hClose inh
137−
138− -- Read from stdout
139− _ <- forkIO $ do
140− out <- reader outh
141− hClose outh
142− writeIORef outRef out
143− putMVar lock ()
144−
145− -- Read from stderr
146− _ <- forkIO $ do
147− hSetEncoding errh localeEncoding
148− err <- hGetContents errh
149− _ <- deepseq err (return err)
150− hClose errh
151− writeIORef errRef err
152− putMVar lock ()
153−
154− -- Get exit code & return
155− takeMVar lock
156− takeMVar lock
157− exitCode <- waitForProcess pid
158− out <- readIORef outRef
159− err <- readIORef errRef
160− return (out, err, exitCode)
lib/Hakyll/Core/Util/Parser.hsdeleted−31
@@ -1,31 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Parser utilities
3−module Hakyll.Core.Util.Parser
4− ( metadataKey
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Control.Applicative ((<|>))
10−import Control.Monad (guard, mzero, void)
11−import qualified Text.Parsec as P
12−import Text.Parsec.String (Parser)
13−
14−
15−--------------------------------------------------------------------------------
16−metadataKey :: Parser String
17−metadataKey = do
18− -- Ensure trailing '-' binds to '$' if present.
19− let hyphon = P.try $ do
20− void $ P.char '-'
21− x <- P.lookAhead P.anyChar
22− guard $ x /= '$'
23− pure '-'
24−
25− i <- (:) <$> P.letter <*> P.many (P.alphaNum <|> P.oneOf "_." <|> hyphon)
26− if i `elem` reservedKeys then mzero else return i
27−
28−
29−--------------------------------------------------------------------------------
30−reservedKeys :: [String]
31−reservedKeys = ["if", "else", "endif", "for", "sep", "endfor", "partial"]
lib/Hakyll/Main.hsdeleted−180
@@ -1,180 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module providing the main hakyll function and command-line argument parsing
3−{-# LANGUAGE CPP #-}
4−
5−module Hakyll.Main
6− ( hakyll
7− , hakyllWith
8− , hakyllWithArgs
9− , hakyllWithExitCode
10− , hakyllWithExitCodeAndArgs
11− , Options(..)
12− , Command(..)
13− ) where
14−
15−
16−--------------------------------------------------------------------------------
17−import System.Environment (getProgName)
18−import System.Exit (ExitCode (ExitSuccess), exitWith)
19−import System.IO.Unsafe (unsafePerformIO)
20−
21−
22−--------------------------------------------------------------------------------
23−import Data.Monoid ((<>))
24−import qualified Options.Applicative as OA
25−
26−
27−--------------------------------------------------------------------------------
28−import qualified Hakyll.Check as Check
29−import qualified Hakyll.Commands as Commands
30−import qualified Hakyll.Core.Configuration as Config
31−import qualified Hakyll.Core.Logger as Logger
32−import Hakyll.Core.Rules
33−
34−
35−--------------------------------------------------------------------------------
36−-- | This usually is the function with which the user runs the hakyll compiler
37−hakyll :: Rules a -> IO ()
38−hakyll = hakyllWith Config.defaultConfiguration
39−
40−--------------------------------------------------------------------------------
41−-- | A variant of 'hakyll' which allows the user to specify a custom
42−-- configuration
43−hakyllWith :: Config.Configuration -> Rules a -> IO ()
44−hakyllWith conf rules = hakyllWithExitCode conf rules >>= exitWith
45−
46−--------------------------------------------------------------------------------
47−-- | A variant of 'hakyll' which returns an 'ExitCode'
48−hakyllWithExitCode :: Config.Configuration -> Rules a -> IO ExitCode
49−hakyllWithExitCode conf rules = do
50− args <- defaultParser conf
51− hakyllWithExitCodeAndArgs conf args rules
52−
53−--------------------------------------------------------------------------------
54−-- | A variant of 'hakyll' which expects a 'Configuration' and command-line
55−-- 'Options'. This gives freedom to implement your own parsing.
56−hakyllWithArgs :: Config.Configuration -> Options -> Rules a -> IO ()
57−hakyllWithArgs conf args rules =
58− hakyllWithExitCodeAndArgs conf args rules >>= exitWith
59−
60−--------------------------------------------------------------------------------
61−hakyllWithExitCodeAndArgs :: Config.Configuration ->
62− Options -> Rules a -> IO ExitCode
63−hakyllWithExitCodeAndArgs conf args rules = do
64− let args' = optCommand args
65− verbosity' = if verbosity args then Logger.Debug else Logger.Message
66− check =
67− if internal_links args' then Check.InternalLinks else Check.All
68−
69− logger <- Logger.new verbosity'
70− invokeCommands args' conf check logger rules
71−
72−--------------------------------------------------------------------------------
73−defaultParser :: Config.Configuration -> IO Options
74−defaultParser conf =
75− OA.customExecParser (OA.prefs OA.showHelpOnError)
76− (OA.info (OA.helper <*> optionParser conf)
77− (OA.fullDesc <> OA.progDesc
78− (progName ++ " - Static site compiler created with Hakyll")))
79−
80−
81−--------------------------------------------------------------------------------
82−invokeCommands :: Command -> Config.Configuration ->
83− Check.Check -> Logger.Logger -> Rules a -> IO ExitCode
84−invokeCommands args conf check logger rules =
85− case args of
86− Build -> Commands.build conf logger rules
87− Check _ -> Commands.check conf logger check
88− Clean -> Commands.clean conf logger >> ok
89− Deploy -> Commands.deploy conf
90− Preview p -> Commands.preview conf logger rules p >> ok
91− Rebuild -> Commands.rebuild conf logger rules
92− Server _ _ -> Commands.server conf logger (host args) (port args) >> ok
93− Watch _ p s -> Commands.watch conf logger (host args) p (not s) rules >> ok
94− where
95− ok = return ExitSuccess
96−
97−
98−--------------------------------------------------------------------------------
99−
100−-- | The parsed command-line options.
101−data Options = Options {verbosity :: Bool, optCommand :: Command}
102− deriving (Show)
103−
104−-- | The command to run.
105−data Command
106− = Build
107− -- ^ Generate the site.
108− | Check {internal_links :: Bool}
109− -- ^ Validate the site output.
110− | Clean
111− -- ^ Clean up and remove cache.
112− | Deploy
113− -- ^ Upload/deploy your site.
114− | Preview {port :: Int}
115− -- ^ [DEPRECATED] Please use the watch command.
116− | Rebuild
117− -- ^ Clean and build again.
118− | Server {host :: String, port :: Int}
119− -- ^ Start a preview server.
120− | Watch {host :: String, port :: Int, no_server :: Bool }
121− -- ^ Autocompile on changes and start a preview server.
122− deriving (Show)
123−
124−{-# DEPRECATED Preview "Use Watch instead." #-}
125−
126−optionParser :: Config.Configuration -> OA.Parser Options
127−optionParser conf = Options <$> verboseParser <*> commandParser conf
128− where
129− verboseParser = OA.switch (OA.long "verbose" <> OA.short 'v' <> OA.help "Run in verbose mode")
130−
131−
132−commandParser :: Config.Configuration -> OA.Parser Command
133−commandParser conf = OA.subparser $ foldr ((<>) . produceCommand) mempty commands
134− where
135− portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))
136− hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))
137−
138− produceCommand (c,a,b) = OA.command c (OA.info (OA.helper <*> a) (b))
139−
140− commands =
141− [ ( "build"
142− , pure Build
143− , OA.fullDesc <> OA.progDesc "Generate the site"
144− )
145− , ( "check"
146− , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")
147− , OA.fullDesc <> OA.progDesc "Validate the site output"
148− )
149− , ( "clean"
150− , pure Clean
151− , OA.fullDesc <> OA.progDesc "Clean up and remove cache"
152− )
153− , ( "deploy"
154− , pure Deploy
155− , OA.fullDesc <> OA.progDesc "Upload/deploy your site"
156− )
157− , ( "preview"
158− , pure Preview <*> portParser
159− , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"
160− )
161− , ( "rebuild"
162− , pure Rebuild
163− , OA.fullDesc <> OA.progDesc "Clean and build again"
164− )
165− , ( "server"
166− , pure Server <*> hostParser <*> portParser
167− , OA.fullDesc <> OA.progDesc "Start a preview server"
168− )
169− , ( "watch"
170− , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")
171− , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server."
172− )
173− ]
174−
175−
176−--------------------------------------------------------------------------------
177−-- | This is necessary because not everyone calls their program the same...
178−progName :: String
179−progName = unsafePerformIO getProgName
180−{-# NOINLINE progName #-}
lib/Hakyll/Preview/Poll.hsdeleted−109
@@ -1,109 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−module Hakyll.Preview.Poll
4− ( watchUpdates
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Control.Concurrent (forkIO)
10−import Control.Concurrent.MVar (newEmptyMVar, takeMVar,
11− tryPutMVar)
12−import Control.Exception (AsyncException, fromException,
13− handle, throw)
14−import Control.Monad (forever, void, when)
15−import System.Directory (canonicalizePath)
16−import System.FilePath (pathSeparators)
17−import qualified System.FSNotify as FSNotify
18−
19−#ifdef mingw32_HOST_OS
20−import Control.Concurrent (threadDelay)
21−import Control.Exception (IOException, throw, try)
22−import System.Directory (doesFileExist)
23−import System.Exit (exitFailure)
24−import System.FilePath ((</>))
25−import System.IO (Handle, IOMode (ReadMode),
26− hClose, openFile)
27−import System.IO.Error (isPermissionError)
28−#endif
29−
30−
31−--------------------------------------------------------------------------------
32−import Hakyll.Core.Configuration
33−import Hakyll.Core.Identifier
34−import Hakyll.Core.Identifier.Pattern
35−
36−
37−--------------------------------------------------------------------------------
38−-- | A thread that watches for updates in a 'providerDirectory' and recompiles
39−-- a site as soon as any changes occur
40−watchUpdates :: Configuration -> IO Pattern -> IO ()
41−watchUpdates conf update = do
42− let providerDir = providerDirectory conf
43− shouldBuild <- newEmptyMVar
44− pattern <- update
45− fullProviderDir <- canonicalizePath $ providerDirectory conf
46− manager <- FSNotify.startManager
47−
48− let allowed event = do
49− -- Absolute path of the changed file. This must be inside provider
50− -- dir, since that's the only dir we're watching.
51− let path = FSNotify.eventPath event
52− relative = dropWhile (`elem` pathSeparators) $
53− drop (length fullProviderDir) path
54− identifier = fromFilePath relative
55−
56− shouldIgnore <- shouldIgnoreFile conf path
57− return $ not shouldIgnore && matches pattern identifier
58−
59− -- This thread continually watches the `shouldBuild` MVar and builds
60− -- whenever a value is present.
61− _ <- forkIO $ forever $ do
62− event <- takeMVar shouldBuild
63− handle
64− (\e -> case fromException e of
65− Nothing -> putStrLn (show e)
66− Just async -> throw (async :: AsyncException))
67− (update' event providerDir)
68−
69− -- Send an event whenever something occurs so that the thread described
70− -- above will do a build.
71− void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
72− allowed' <- allowed event
73− when allowed' $ void $ tryPutMVar shouldBuild event
74− where
75−#ifndef mingw32_HOST_OS
76− update' _ _ = void update
77−#else
78− update' event provider = do
79− let path = provider </> FSNotify.eventPath event
80− -- on windows, a 'Modified' event is also sent on file deletion
81− fileExists <- doesFileExist path
82−
83− when fileExists . void $ waitOpen path ReadMode (\_ -> update) 10
84−
85− -- continuously attempts to open the file in between sleep intervals
86− -- handler is run only once it is able to open the file
87− waitOpen :: FilePath -> IOMode -> (Handle -> IO r) -> Integer -> IO r
88− waitOpen _ _ _ 0 = do
89− putStrLn "[ERROR] Failed to retrieve modified file for regeneration"
90− exitFailure
91− waitOpen path mode handler retries = do
92− res <- try $ openFile path mode :: IO (Either IOException Handle)
93− case res of
94− Left ex -> if isPermissionError ex
95− then do
96− threadDelay 100000
97− waitOpen path mode handler (retries - 1)
98− else throw ex
99− Right h -> do
100− handled <- handler h
101− hClose h
102− return handled
103−#endif
104−
105−
106−--------------------------------------------------------------------------------
107−isRemove :: FSNotify.Event -> Bool
108−isRemove (FSNotify.Removed {}) = True
109−isRemove _ = False
lib/Hakyll/Preview/Server.hsdeleted−35
@@ -1,35 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Implements a basic static file server for previewing options
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Preview.Server
5− ( staticServer
6− ) where
7−
8−
9−--------------------------------------------------------------------------------
10−import Data.String
11−import qualified Network.Wai.Handler.Warp as Warp
12−import qualified Network.Wai.Application.Static as Static
13−import qualified Network.Wai as Wai
14−import Network.HTTP.Types.Status (Status)
15−
16−--------------------------------------------------------------------------------
17−import Hakyll.Core.Logger (Logger)
18−import qualified Hakyll.Core.Logger as Logger
19−
20−staticServer :: Logger -- ^ Logger
21− -> FilePath -- ^ Directory to serve
22− -> String -- ^ Host to bind on
23− -> Int -- ^ Port to listen on
24− -> IO () -- ^ Blocks forever
25−staticServer logger directory host port = do
26− Logger.header logger $ "Listening on http://" ++ host ++ ":" ++ show port
27− Warp.runSettings warpSettings $
28− Static.staticApp (Static.defaultFileServerSettings directory)
29− where
30− warpSettings = Warp.setLogger noLog
31− $ Warp.setHost (fromString host)
32− $ Warp.setPort port Warp.defaultSettings
33−
34−noLog :: Wai.Request -> Status -> Maybe Integer -> IO ()
35−noLog _ _ _ = return ()
lib/Hakyll/Web/CompressCss.hsdeleted−101
@@ -1,101 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module used for CSS compression. The compression is currently in a simple
3−-- state, but would typically reduce the number of bytes by about 25%.
4−module Hakyll.Web.CompressCss
5− ( compressCssCompiler
6− , compressCss
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Data.Char (isSpace)
12−import Data.List (dropWhileEnd, isPrefixOf)
13−
14−
15−--------------------------------------------------------------------------------
16−import Hakyll.Core.Compiler
17−import Hakyll.Core.Item
18−import Hakyll.Core.Util.String
19−
20−
21−--------------------------------------------------------------------------------
22−-- | Compiler form of 'compressCss'
23−compressCssCompiler :: Compiler (Item String)
24−compressCssCompiler = fmap compressCss <$> getResourceString
25−
26−
27−--------------------------------------------------------------------------------
28−-- | Compress CSS to speed up your site.
29−compressCss :: String -> String
30−compressCss = withoutStrings (handleCalcExpressions compressSeparators . compressWhitespace)
31− . dropWhileEnd isSpace
32− . dropWhile isSpace
33− . stripComments
34−
35−
36−--------------------------------------------------------------------------------
37−-- | Compresses certain forms of separators.
38−compressSeparators :: String -> String
39−compressSeparators =
40− replaceAll "; *}" (const "}") .
41− replaceAll ";+" (const ";") .
42− replaceAll " *[{};,>+~!] *" (take 1 . dropWhile isSpace) .
43− replaceAll ": *" (take 1) -- not destroying pseudo selectors (#323)
44−
45−-- | Uses `compressCalcExpression` on all parenthesised calc expressions
46−-- and applies `transform` to all parts outside of them
47−handleCalcExpressions :: (String -> String) -> String -> String
48−handleCalcExpressions transform = top transform
49− where
50− top f "" = f ""
51− top f str | "calc(" `isPrefixOf` str = f "calc" ++ nested 0 compressCalcExpression (drop 4 str)
52− top f (x:xs) = top (f . (x:)) xs
53−
54− -- when called with depth=0, the first character must be a '('
55− nested :: Int -> (String -> String) -> String -> String
56− nested _ f "" = f "" -- shouldn't happen, mismatched nesting
57− nested depth f str | "calc(" `isPrefixOf` str = nested depth f (drop 4 str)
58− nested 1 f (')':xs) = f ")" ++ top transform xs
59− nested depth f (x:xs) = nested (case x of
60− '(' -> depth + 1
61− ')' -> depth - 1 -- assert: depth > 1
62− _ -> depth
63− ) (f . (x:)) xs
64−
65−-- | does not remove whitespace around + and -, which is important in calc() expressions
66−compressCalcExpression :: String -> String
67−compressCalcExpression =
68− replaceAll " *[*/] *| *\\)|\\( *" (take 1 . dropWhile isSpace)
69−
70−--------------------------------------------------------------------------------
71−-- | Compresses all whitespace.
72−compressWhitespace :: String -> String
73−compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
74−
75−--------------------------------------------------------------------------------
76−-- | Function that strips CSS comments away (outside of strings).
77−stripComments :: String -> String
78−stripComments "" = ""
79−stripComments ('/':'*':str) = stripComments $ eatComment str
80−stripComments (x:xs) | x `elem` "\"'" = retainString x xs stripComments
81− | otherwise = x : stripComments xs
82−
83−eatComment :: String -> String
84−eatComment "" = ""
85−eatComment ('*':'/':str) = str
86−eatComment (_:str) = eatComment str
87−
88−
89−--------------------------------------------------------------------------------
90−-- | Helper functions to handle string tokens correctly.
91−
92−-- TODO: handle backslash escapes
93−withoutStrings :: (String -> String) -> String -> String
94−withoutStrings f str = case span (`notElem` "\"'") str of
95− (text, "") -> f text
96− (text, d:rest) -> f text ++ retainString d rest (withoutStrings f)
97−
98−retainString :: Char -> String -> (String -> String) -> String
99−retainString delim str cont = case span (/= delim) str of
100− (val, "") -> delim : val
101− (val, _:rest) -> delim : val ++ delim : cont rest
lib/Hakyll/Web/Paginate.hsdeleted−153
@@ -1,153 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE OverloadedStrings #-}
3−module Hakyll.Web.Paginate
4− ( PageNumber
5− , Paginate (..)
6− , buildPaginateWith
7− , paginateEvery
8− , paginateRules
9− , paginateContext
10− ) where
11−
12−
13−--------------------------------------------------------------------------------
14−import Control.Applicative (empty)
15−import Control.Monad (forM_, forM)
16−import qualified Data.Map as M
17−import qualified Data.Set as S
18−
19−
20−--------------------------------------------------------------------------------
21−import Hakyll.Core.Compiler
22−import Hakyll.Core.Identifier
23−import Hakyll.Core.Identifier.Pattern
24−import Hakyll.Core.Item
25−import Hakyll.Core.Metadata
26−import Hakyll.Core.Rules
27−import Hakyll.Web.Html
28−import Hakyll.Web.Template.Context
29−
30−
31−--------------------------------------------------------------------------------
32−type PageNumber = Int
33−
34−
35−--------------------------------------------------------------------------------
36−-- | Data about paginators
37−data Paginate = Paginate
38− { paginateMap :: M.Map PageNumber [Identifier]
39− , paginateMakeId :: PageNumber -> Identifier
40− , paginateDependency :: Dependency
41− }
42−
43−
44−--------------------------------------------------------------------------------
45−paginateNumPages :: Paginate -> Int
46−paginateNumPages = M.size . paginateMap
47−
48−
49−--------------------------------------------------------------------------------
50−paginateEvery :: Int -> [a] -> [[a]]
51−paginateEvery n = go
52− where
53− go [] = []
54− go xs = let (y, ys) = splitAt n xs in y : go ys
55−
56−
57−--------------------------------------------------------------------------------
58−buildPaginateWith
59− :: MonadMetadata m
60− => ([Identifier] -> m [[Identifier]]) -- ^ Group items into pages
61− -> Pattern -- ^ Select items to paginate
62− -> (PageNumber -> Identifier) -- ^ Identifiers for the pages
63− -> m Paginate
64−buildPaginateWith grouper pattern makeId = do
65− ids <- getMatches pattern
66− idGroups <- grouper ids
67− let idsSet = S.fromList ids
68− return Paginate
69− { paginateMap = M.fromList (zip [1 ..] idGroups)
70− , paginateMakeId = makeId
71− , paginateDependency = PatternDependency pattern idsSet
72− }
73−
74−
75−--------------------------------------------------------------------------------
76−paginateRules :: Paginate -> (PageNumber -> Pattern -> Rules ()) -> Rules ()
77−paginateRules paginator rules =
78− forM_ (M.toList $ paginateMap paginator) $ \(idx, identifiers) ->
79− rulesExtraDependencies [paginateDependency paginator] $
80− create [paginateMakeId paginator idx] $
81− rules idx $ fromList identifiers
82−
83−
84−--------------------------------------------------------------------------------
85−-- | Get the identifier for a certain page by passing in the page number.
86−paginatePage :: Paginate -> PageNumber -> Maybe Identifier
87−paginatePage pag pageNumber
88− | pageNumber < 1 = Nothing
89− | pageNumber > (paginateNumPages pag) = Nothing
90− | otherwise = Just $ paginateMakeId pag pageNumber
91−
92−
93−--------------------------------------------------------------------------------
94−-- | A default paginate context which provides the following keys:
95−--
96−--
97−-- * @firstPageNum@
98−-- * @firstPageUrl@
99−-- * @previousPageNum@
100−-- * @previousPageUrl@
101−-- * @nextPageNum@
102−-- * @nextPageUrl@
103−-- * @lastPageNum@
104−-- * @lastPageUrl@
105−-- * @currentPageNum@
106−-- * @currentPageUrl@
107−-- * @numPages@
108−-- * @allPages@
109−paginateContext :: Paginate -> PageNumber -> Context a
110−paginateContext pag currentPage = mconcat
111− [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
112− , field "firstPageUrl" $ \_ -> otherPage 1 >>= url
113− , field "previousPageNum" $ \_ -> otherPage (currentPage - 1) >>= num
114− , field "previousPageUrl" $ \_ -> otherPage (currentPage - 1) >>= url
115− , field "nextPageNum" $ \_ -> otherPage (currentPage + 1) >>= num
116− , field "nextPageUrl" $ \_ -> otherPage (currentPage + 1) >>= url
117− , field "lastPageNum" $ \_ -> otherPage lastPage >>= num
118− , field "lastPageUrl" $ \_ -> otherPage lastPage >>= url
119− , field "currentPageNum" $ \i -> thisPage i >>= num
120− , field "currentPageUrl" $ \i -> thisPage i >>= url
121− , constField "numPages" $ show $ paginateNumPages pag
122− , Context $ \k _ i -> case k of
123− "allPages" -> do
124− let ctx =
125− field "isCurrent" (\n -> if fst (itemBody n) == currentPage then return "true" else empty) `mappend`
126− field "num" (num . itemBody) `mappend`
127− field "url" (url . itemBody)
128−
129− list <- forM [1 .. lastPage] $
130− \n -> if n == currentPage then thisPage i else otherPage n
131− items <- mapM makeItem list
132− return $ ListField ctx items
133− _ -> do
134− empty
135−
136− ]
137− where
138− lastPage = paginateNumPages pag
139−
140− thisPage i = return (currentPage, itemIdentifier i)
141− otherPage n
142− | n == currentPage = fail $ "This is the current page: " ++ show n
143− | otherwise = case paginatePage pag n of
144− Nothing -> fail $ "No such page: " ++ show n
145− Just i -> return (n, i)
146−
147− num :: (Int, Identifier) -> Compiler String
148− num = return . show . fst
149−
150− url :: (Int, Identifier) -> Compiler String
151− url (n, i) = getRoute i >>= \mbR -> case mbR of
152− Just r -> return $ toUrl r
153− Nothing -> fail $ "No URL for page: " ++ show n
lib/Hakyll/Web/Pandoc.hsdeleted−165
@@ -1,165 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module exporting convenient pandoc bindings
3−module Hakyll.Web.Pandoc
4− ( -- * The basic building blocks
5− readPandoc
6− , readPandocWith
7− , writePandoc
8− , writePandocWith
9− , renderPandoc
10− , renderPandocWith
11−
12− -- * Derived compilers
13− , pandocCompiler
14− , pandocCompilerWith
15− , pandocCompilerWithTransform
16− , pandocCompilerWithTransformM
17−
18− -- * Default options
19− , defaultHakyllReaderOptions
20− , defaultHakyllWriterOptions
21− ) where
22−
23−
24−--------------------------------------------------------------------------------
25−import qualified Data.Text as T
26−import Text.Pandoc
27−import Text.Pandoc.Highlighting (pygments)
28−
29−
30−--------------------------------------------------------------------------------
31−import Hakyll.Core.Compiler
32−import Hakyll.Core.Item
33−import Hakyll.Web.Pandoc.FileType
34−
35−
36−--------------------------------------------------------------------------------
37−-- | Read a string using pandoc, with the default options
38−readPandoc
39− :: Item String -- ^ String to read
40− -> Compiler (Item Pandoc) -- ^ Resulting document
41−readPandoc = readPandocWith defaultHakyllReaderOptions
42−
43−
44−--------------------------------------------------------------------------------
45−-- | Read a string using pandoc, with the supplied options
46−readPandocWith
47− :: ReaderOptions -- ^ Parser options
48− -> Item String -- ^ String to read
49− -> Compiler (Item Pandoc) -- ^ Resulting document
50−readPandocWith ropt item =
51− case runPure $ traverse (reader ropt (itemFileType item)) (fmap T.pack item) of
52− Left err -> fail $
53− "Hakyll.Web.Pandoc.readPandocWith: parse failed: " ++ show err
54− Right item' -> return item'
55− where
56− reader ro t = case t of
57− DocBook -> readDocBook ro
58− Html -> readHtml ro
59− LaTeX -> readLaTeX ro
60− LiterateHaskell t' -> reader (addExt ro Ext_literate_haskell) t'
61− Markdown -> readMarkdown ro
62− MediaWiki -> readMediaWiki ro
63− OrgMode -> readOrg ro
64− Rst -> readRST ro
65− Textile -> readTextile ro
66− _ -> error $
67− "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
68− "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
69−
70− addExt ro e = ro {readerExtensions = enableExtension e $ readerExtensions ro}
71−
72−
73−--------------------------------------------------------------------------------
74−-- | Write a document (as HTML) using pandoc, with the default options
75−writePandoc :: Item Pandoc -- ^ Document to write
76− -> Item String -- ^ Resulting HTML
77−writePandoc = writePandocWith defaultHakyllWriterOptions
78−
79−
80−--------------------------------------------------------------------------------
81−-- | Write a document (as HTML) using pandoc, with the supplied options
82−writePandocWith :: WriterOptions -- ^ Writer options for pandoc
83− -> Item Pandoc -- ^ Document to write
84− -> Item String -- ^ Resulting HTML
85−writePandocWith wopt (Item itemi doc) =
86− case runPure $ writeHtml5String wopt doc of
87− Left err -> error $ "Hakyll.Web.Pandoc.writePandocWith: " ++ show err
88− Right item' -> Item itemi $ T.unpack item'
89−
90−
91−--------------------------------------------------------------------------------
92−-- | Render the resource using pandoc
93−renderPandoc :: Item String -> Compiler (Item String)
94−renderPandoc =
95− renderPandocWith defaultHakyllReaderOptions defaultHakyllWriterOptions
96−
97−
98−--------------------------------------------------------------------------------
99−-- | Render the resource using pandoc
100−renderPandocWith
101− :: ReaderOptions -> WriterOptions -> Item String -> Compiler (Item String)
102−renderPandocWith ropt wopt item =
103− writePandocWith wopt <$> readPandocWith ropt item
104−
105−
106−--------------------------------------------------------------------------------
107−-- | Read a page render using pandoc
108−pandocCompiler :: Compiler (Item String)
109−pandocCompiler =
110− pandocCompilerWith defaultHakyllReaderOptions defaultHakyllWriterOptions
111−
112−
113−--------------------------------------------------------------------------------
114−-- | A version of 'pandocCompiler' which allows you to specify your own pandoc
115−-- options
116−pandocCompilerWith :: ReaderOptions -> WriterOptions -> Compiler (Item String)
117−pandocCompilerWith ropt wopt =
118− cached "Hakyll.Web.Pandoc.pandocCompilerWith" $
119− pandocCompilerWithTransform ropt wopt id
120−
121−
122−--------------------------------------------------------------------------------
123−-- | An extension of 'pandocCompilerWith' which allows you to specify a custom
124−-- pandoc transformation for the content
125−pandocCompilerWithTransform :: ReaderOptions -> WriterOptions
126− -> (Pandoc -> Pandoc)
127− -> Compiler (Item String)
128−pandocCompilerWithTransform ropt wopt f =
129− pandocCompilerWithTransformM ropt wopt (return . f)
130−
131−
132−--------------------------------------------------------------------------------
133−-- | Similar to 'pandocCompilerWithTransform', but the transformation
134−-- function is monadic. This is useful when you want the pandoc
135−-- transformation to use the 'Compiler' information such as routes,
136−-- metadata, etc
137−pandocCompilerWithTransformM :: ReaderOptions -> WriterOptions
138− -> (Pandoc -> Compiler Pandoc)
139− -> Compiler (Item String)
140−pandocCompilerWithTransformM ropt wopt f =
141− writePandocWith wopt <$>
142− (traverse f =<< readPandocWith ropt =<< getResourceBody)
143−
144−
145−--------------------------------------------------------------------------------
146−-- | The default reader options for pandoc parsing in hakyll
147−defaultHakyllReaderOptions :: ReaderOptions
148−defaultHakyllReaderOptions = def
149− { -- The following option causes pandoc to read smart typography, a nice
150− -- and free bonus.
151− readerExtensions = enableExtension Ext_smart pandocExtensions
152− }
153−
154−
155−--------------------------------------------------------------------------------
156−-- | The default writer options for pandoc rendering in hakyll
157−defaultHakyllWriterOptions :: WriterOptions
158−defaultHakyllWriterOptions = def
159− { -- This option causes literate haskell to be written using '>' marks in
160− -- html, which I think is a good default.
161− writerExtensions = enableExtension Ext_smart pandocExtensions
162− , -- We want to have hightlighting by default, to be compatible with earlier
163− -- Hakyll releases
164− writerHighlightStyle = Just pygments
165− }
lib/Hakyll/Web/Pandoc/Binary.hsdeleted−33
@@ -1,33 +0,0 @@
1−{-# OPTIONS_GHC -fno-warn-orphans #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−module Hakyll.Web.Pandoc.Binary where
4−
5−import Data.Binary (Binary (..))
6−
7−import qualified Text.CSL as CSL
8−import qualified Text.CSL.Reference as REF
9−import qualified Text.CSL.Style as STY
10−import Text.Pandoc
11−
12−--------------------------------------------------------------------------------
13−-- orphans
14−
15−instance Binary Alignment
16−instance Binary Block
17−instance Binary CSL.Reference
18−instance Binary Citation
19−instance Binary CitationMode
20−instance Binary Format
21−instance Binary Inline
22−instance Binary ListNumberDelim
23−instance Binary ListNumberStyle
24−instance Binary MathType
25−instance Binary QuoteType
26−instance Binary REF.CLabel
27−instance Binary REF.CNum
28−instance Binary REF.Literal
29−instance Binary REF.RefDate
30−instance Binary REF.RefType
31−instance Binary REF.Season
32−instance Binary STY.Agent
33−instance Binary STY.Formatted
lib/Hakyll/Web/Redirect.hsdeleted−100
@@ -1,100 +0,0 @@
1−-- | Module used for generating HTML redirect pages. This allows renaming pages
2−-- to avoid breaking existing links without requiring server-side support for
3−-- formal 301 Redirect error codes
4−module Hakyll.Web.Redirect
5− ( Redirect (..)
6− , createRedirects
7− ) where
8−
9−import Control.Applicative ((<$>))
10−import Control.Monad (forM_, when)
11−import Data.Binary (Binary (..))
12−import Data.List (sort, group)
13−import Hakyll.Core.Compiler
14−import Hakyll.Core.Identifier
15−import Hakyll.Core.Routes
16−import Hakyll.Core.Rules
17−import Hakyll.Core.Writable (Writable (..))
18−
19−-- | This function exposes a higher-level interface compared to using the
20−-- 'Redirect' type manually.
21−--
22−-- This creates, using a database mapping broken URLs to working ones, HTML
23−-- files which will do HTML META tag redirect pages (since, as a static site, we
24−-- can't use web-server-level 301 redirects, and using JS is gross).
25−--
26−-- This is useful for sending people using old URLs to renamed versions, dealing
27−-- with common typos etc, and will increase site traffic. Such broken URLs can
28−-- be found by looking at server logs or by using Google Webmaster Tools.
29−-- Broken URLs must be valid Haskell strings, non-URL-escaped valid POSIX
30−-- filenames, and relative links, since they will be defined in a @hakyll.hs@
31−-- and during generation, written to disk with the filename corresponding to the
32−-- broken URLs. (Target URLs can be absolute or relative, but should be
33−-- URL-escaped.) So broken incoming links like <http://www.gwern.net/foo/> which
34−-- should be <http://www.gwern.net/foobar> cannot be fixed (since you cannot
35−-- create a HTML file named @"foo/"@ on disk, as that would be a directory).
36−--
37−-- An example of a valid association list would be:
38−--
39−-- > brokenLinks =
40−-- > [ ("projects.html", "http://github.com/gwern")
41−-- > , ("/Black-market archive", "Black-market%20archives")
42−-- > ]
43−--
44−-- In which case the functionality can then be used in `main` with a line like:
45−--
46−-- > version "redirects" $ createRedirects brokenLinks
47−--
48−-- The 'version' is recommended to separate these items from your other pages.
49−--
50−-- The on-disk files can then be uploaded with HTML mimetypes
51−-- (either explicitly by generating and uploading them separately, by
52−-- auto-detection of the filetype, or an upload tool defaulting to HTML
53−-- mimetype, such as calling @s3cmd@ with @--default-mime-type=text/html@) and
54−-- will redirect browsers and search engines going to the old/broken URLs.
55−--
56−-- See also <https://groups.google.com/d/msg/hakyll/sWc6zxfh-uM/fUpZPsFNDgAJ>.
57−createRedirects :: [(Identifier, String)] -> Rules ()
58−createRedirects redirects =
59− do -- redirects are many-to-fewer; keys must be unique, and must point somewhere else:
60− let gkeys = group $ sort $ map fst redirects
61− forM_ gkeys $ \gkey -> case gkey of
62− (k : _ : _) -> fail $
63− "Duplicate 301 redirects; " ++ show k ++ " is ambiguous."
64− _ -> return ()
65−
66− forM_ redirects $ \(r, t) ->
67− when (toFilePath r == t) $ fail $
68− "Self-redirect detected: " ++ show r ++ " points to itself."
69−
70− forM_ redirects $ \(ident, to) ->
71− create [ident] $ do
72− route idRoute
73− compile $ makeItem $! Redirect to
74−
75−-- | This datatype can be used directly if you want a lower-level interface to
76−-- generate redirects. For example, if you want to redirect @foo.html@ to
77−-- @bar.jpg@, you can use:
78−--
79−-- > create ["foo.html"] $ do
80−-- > route idRoute
81−-- > compile $ makeItem $ Redirect "bar.jpg"
82−data Redirect = Redirect
83− { redirectTo :: String
84− } deriving (Eq, Ord, Show)
85−
86−instance Binary Redirect where
87− put (Redirect to) = put to
88− get = Redirect <$> get
89−
90−instance Writable Redirect where
91− write path = write path . fmap redirectToHtml
92−
93−redirectToHtml :: Redirect -> String
94−redirectToHtml (Redirect working) =
95− "<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><meta name=\"generator\" content=\"hakyll\"/>" ++
96− "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" ++
97− "<meta http-equiv=\"refresh\" content=\"0; url=" ++ working ++
98− "\"><link rel=\"canonical\" href=\"" ++ working ++
99− "\"><title>Permanent Redirect</title></head><body><p>The page has moved to: <a href=\"" ++ working ++
100− "\">this page</a></p></body></html>"
lib/Hakyll/Web/Template.hsdeleted−178
@@ -1,178 +0,0 @@
1−-- | This module provides means for reading and applying 'Template's.
2−--
3−-- Templates are tools to convert items into a string. They are perfectly suited
4−-- for laying out your site.
5−--
6−-- Let's look at an example template:
7−--
8−-- > <html>
9−-- > <head>
10−-- > <title>My crazy homepage - $title$</title>
11−-- > </head>
12−-- > <body>
13−-- > <div id="header">
14−-- > <h1>My crazy homepage - $title$</h1>
15−-- > </div>
16−-- > <div id="content">
17−-- > $body$
18−-- > </div>
19−-- > <div id="footer">
20−-- > By reading this you agree that I now own your soul
21−-- > </div>
22−-- > </body>
23−-- > </html>
24−--
25−-- As you can see, the format is very simple -- @$key$@ is used to render the
26−-- @$key$@ field from the page, everything else is literally copied. If you want
27−-- to literally insert @\"$key$\"@ into your page (for example, when you're
28−-- writing a Hakyll tutorial) you can use
29−--
30−-- > <p>
31−-- > A literal $$key$$.
32−-- > </p>
33−--
34−-- Because of it's simplicity, these templates can be used for more than HTML:
35−-- you could make, for example, CSS or JS templates as well.
36−--
37−-- Apart from interpolating @$key$@s from the 'Context' you can also
38−-- use the following macros:
39−--
40−-- * @$if(key)$@
41−--
42−-- > $if(key)$
43−-- > <b> Defined </b>
44−-- > $else$
45−-- > <b> Non-defined </b>
46−-- > $endif$
47−--
48−-- This example will print @Defined@ if @key@ is defined in the
49−-- context and @Non-defined@ otherwise. The @$else$@ clause is
50−-- optional.
51−--
52−-- * @$for(key)$@
53−--
54−-- The @for@ macro is used for enumerating 'Context' elements that are
55−-- lists, i.e. constructed using the 'listField' function. Assume that
56−-- in a context we have an element @listField \"key\" c itms@. Then
57−-- the snippet
58−--
59−-- > $for(key)$
60−-- > $x$
61−-- > $sep$,
62−-- > $endfor$
63−--
64−-- would, for each item @i@ in 'itms', lookup @$x$@ in the context @c@
65−-- with item @i@, interpolate it, and join the resulting list with
66−-- @,@.
67−--
68−-- Another concrete example one may consider is the following. Given the
69−-- context
70−--
71−-- > listField "things" (field "thing" (return . itemBody))
72−-- > (sequence [makeItem "fruits", makeItem "vegetables"])
73−--
74−-- and a template
75−--
76−-- > I like
77−-- > $for(things)$
78−-- > fresh $thing$$sep$, and
79−-- > $endfor$
80−--
81−-- the resulting page would look like
82−--
83−-- > <p>
84−-- > I like
85−-- >
86−-- > fresh fruits, and
87−-- >
88−-- > fresh vegetables
89−-- > </p>
90−--
91−-- The @$sep$@ part can be omitted. Usually, you can get by using the
92−-- 'applyListTemplate' and 'applyJoinListTemplate' functions.
93−--
94−-- * @$partial(path)$@
95−--
96−-- Loads a template located in a separate file and interpolates it
97−-- under the current context.
98−--
99−-- Assuming that the file @test.html@ contains
100−--
101−-- > <b>$key$</b>
102−--
103−-- The result of rendering
104−--
105−-- > <p>
106−-- > $partial("test.html")$
107−-- > </p>
108−--
109−-- is the same as the result of rendering
110−--
111−-- > <p>
112−-- > <b>$key$</b>
113−-- > </p>
114−--
115−-- That is, calling @$partial$@ is equivalent to just copying and pasting
116−-- template code.
117−--
118−-- In the examples above you can see that the outputs contain a lot of leftover
119−-- whitespace that you may wish to remove. Using @'$-'@ or @'-$'@ instead of
120−-- @'$'@ in a macro strips all whitespace to the left or right of that clause
121−-- respectively. Given the context
122−--
123−-- > listField "counts" (field "count" (return . itemBody))
124−-- > (sequence [makeItem "3", makeItem "2", makeItem "1"])
125−--
126−-- and a template
127−--
128−-- > <p>
129−-- > $for(counts)-$
130−-- > $count$
131−-- > $-sep$...
132−-- > $-endfor$
133−-- > </p>
134−--
135−-- the resulting page would look like
136−--
137−-- > <p>
138−-- > 3...2...1
139−-- > </p>
140−--
141−{-# LANGUAGE TemplateHaskell #-}
142−module Hakyll.Web.Template
143− ( Template
144− , templateBodyCompiler
145− , templateCompiler
146− , applyTemplate
147− , loadAndApplyTemplate
148− , applyAsTemplate
149− , readTemplate
150− , compileTemplateItem
151− , unsafeReadTemplateFile
152− , embedTemplate
153− ) where
154−
155−
156−--------------------------------------------------------------------------------
157−import Hakyll.Web.Template.Internal
158−
159−
160−--------------------------------------------------------------------------------
161−import Data.FileEmbed (embedFile)
162−import qualified Data.Text as T
163−import qualified Data.Text.Encoding as T
164−import Language.Haskell.TH (Exp, Q)
165−
166−
167−--------------------------------------------------------------------------------
168−-- | Embed template allows you embed a template within the Haskell binary.
169−-- Example:
170−--
171−-- > myTemplate :: Template
172−-- > myTemplate = $(embedTemplate "test.html")
173−embedTemplate :: FilePath -> Q Exp
174−embedTemplate filePath = [|
175− let source = T.unpack $ T.decodeUtf8 $(embedFile filePath) in
176− case parseTemplateElemsFile filePath source of
177− Left err -> error err
178− Right tpl -> template filePath tpl |]
lib/Hakyll/Web/Template/Context.hsdeleted−481
@@ -1,481 +0,0 @@
1−-- | This module provides 'Context's which are used to expand expressions in
2−-- templates and allow for arbitrary customisation.
3−--
4−-- 'Template's define a small expression DSL which consists of strings,
5−-- identifiers and function application. There is no type system, every value is
6−-- a string and on the top level they get substituted verbatim into the page.
7−--
8−-- For example, you can build a context that contains
9−--
10−-- > … <> functionField "concat" (const . concat) <> …
11−--
12−-- which will allow you to use the @concat@ identifier as a function that takes
13−-- arbitrarily many stings and concatenates them to a new string:
14−--
15−-- > $partial(concat("templates/categories/", category))$
16−--
17−-- This will evaluate the @category@ field in the context, then prepend he path,
18−-- and include the referenced file as a template.
19−
20−
21−--------------------------------------------------------------------------------
22−{-# LANGUAGE CPP #-}
23−{-# LANGUAGE ExistentialQuantification #-}
24−{-# LANGUAGE RankNTypes #-}
25−module Hakyll.Web.Template.Context
26− ( ContextField (..)
27− , Context (..)
28− , field
29− , boolField
30− , constField
31− , listField
32− , listFieldWith
33− , functionField
34− , mapContext
35−
36− , defaultContext
37− , bodyField
38− , metadataField
39− , urlField
40− , pathField
41− , titleField
42− , snippetField
43− , dateField
44− , dateFieldWith
45− , getItemUTC
46− , getItemModificationTime
47− , modificationTimeField
48− , modificationTimeFieldWith
49− , teaserField
50− , teaserFieldWithSeparator
51− , missingField
52− , bindItem
53− ) where
54−
55−
56−--------------------------------------------------------------------------------
57−import Control.Applicative (Alternative (..))
58−import Control.Monad (msum)
59−import Data.List (intercalate, tails)
60−#if MIN_VERSION_base(4,9,0)
61−import Data.Semigroup (Semigroup (..))
62−#endif
63−import Data.Time.Clock (UTCTime (..))
64−import Data.Time.Format (formatTime)
65−import qualified Data.Time.Format as TF
66−import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale)
67−import Hakyll.Core.Compiler
68−import Hakyll.Core.Compiler.Internal
69−import Hakyll.Core.Identifier
70−import Hakyll.Core.Item
71−import Hakyll.Core.Metadata
72−import Hakyll.Core.Provider
73−import Hakyll.Core.Util.String (needlePrefix, splitAll)
74−import Hakyll.Web.Html
75−import Prelude hiding (id)
76−import System.FilePath (dropExtension, splitDirectories,
77− takeBaseName)
78−
79−
80−--------------------------------------------------------------------------------
81−-- | Mostly for internal usage
82−data ContextField
83− = EmptyField
84− | StringField String
85− | forall a. ListField (Context a) [Item a]
86−
87−
88−--------------------------------------------------------------------------------
89−-- | The 'Context' monoid. Please note that the order in which you
90−-- compose the items is important. For example in
91−--
92−-- > field "A" f1 <> field "A" f2
93−--
94−-- the first context will overwrite the second. This is especially
95−-- important when something is being composed with
96−-- 'metadataField' (or 'defaultContext'). If you want your context to be
97−-- overwritten by the metadata fields, compose it from the right:
98−--
99−-- @
100−-- 'metadataField' \<\> field \"date\" fDate
101−-- @
102−--
103−newtype Context a = Context
104− { unContext :: String -> [String] -> Item a -> Compiler ContextField
105− }
106−
107−
108−--------------------------------------------------------------------------------
109−-- | Tries to find a key in the left context,
110−-- or when that fails in the right context.
111−#if MIN_VERSION_base(4,9,0)
112−instance Semigroup (Context a) where
113− (<>) (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
114−
115−instance Monoid (Context a) where
116− mempty = missingField
117− mappend = (<>)
118−#else
119−instance Monoid (Context a) where
120− mempty = missingField
121− mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122−#endif
123−
124−
125−--------------------------------------------------------------------------------
126−field' :: String -> (Item a -> Compiler ContextField) -> Context a
127−field' key value = Context $ \k _ i ->
128− if k == key
129− then value i
130− else noResult $ "Tried field " ++ key
131−
132−
133−--------------------------------------------------------------------------------
134−-- | Constructs a new field for a 'Context'.
135−-- If the key matches, the compiler is run and its result is substituted in the
136−-- template.
137−--
138−-- If the compiler fails, the field will be considered non-existent
139−-- in an @$if()$@ macro or ultimately break the template application
140−-- (unless the key is found in another context when using '<>').
141−-- Use 'empty' or 'noResult' for intentional failures of fields used in
142−-- @$if()$@, to distinguish them from exceptions thrown with 'fail'.
143−field
144− :: String -- ^ Key
145− -> (Item a -> Compiler String) -- ^ Function that constructs a value based
146− -- on the item (e.g. accessing metadata)
147− -> Context a
148−field key value = field' key (fmap StringField . value)
149−
150−
151−--------------------------------------------------------------------------------
152−-- | Creates a 'field' to use with the @$if()$@ template macro.
153−-- Attempting to substitute the field into the template will cause an error.
154−boolField
155− :: String
156− -> (Item a -> Bool)
157− -> Context a
158−boolField name f = field' name (\i -> if f i
159− then return EmptyField
160− else noResult $ "Field " ++ name ++ " is false")
161−
162−
163−--------------------------------------------------------------------------------
164−-- | Creates a 'field' that does not depend on the 'Item' but always yields
165−-- the same string
166−constField :: String -- ^ Key
167− -> String -- ^ Value
168− -> Context a
169−constField key = field key . const . return
170−
171−
172−--------------------------------------------------------------------------------
173−-- | Creates a list field to be consumed by a @$for(…)$@ expression.
174−-- The compiler returns multiple items which are rendered in the loop body
175−-- with the supplied context.
176−listField :: String -> Context a -> Compiler [Item a] -> Context b
177−listField key c xs = listFieldWith key c (const xs)
178−
179−
180−--------------------------------------------------------------------------------
181−-- | Produces a new 'Context' which has list field 'key'. All fields from
182−-- 'c' are also accessible from the produced context.
183−-- Be careful when doing @listFieldWith k ca f <> cb@ as any fields in @ca@
184−-- will override fields in @cb@ with the same name.
185−-- Creates a list field like 'listField', but supplies the current page
186−-- to the compiler.
187−listFieldWith
188− :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
189−listFieldWith key c f = field' key $ fmap (ListField c) . f
190−
191−
192−--------------------------------------------------------------------------------
193−-- | Creates a variadic function field.
194−--
195−-- The function will be called with the dynamically evaluated string arguments
196−-- from the template as well as the page that is currently rendered.
197−functionField :: String -- ^ Key
198− -> ([String] -> Item a -> Compiler String) -- ^ Function
199− -> Context a
200−functionField name value = Context $ \k args i ->
201− if k == name
202− then StringField <$> value args i
203− else noResult $ "Tried function field " ++ name
204−
205−
206−--------------------------------------------------------------------------------
207−-- | Transform the respective string results of all fields in a context.
208−-- For example,
209−--
210−-- > mapContext (++"c") (constField "x" "a" <> constField "y" "b")
211−--
212−-- is equivalent to
213−--
214−-- > constField "x" "ac" <> constField "y" "bc"
215−--
216−mapContext :: (String -> String) -> Context a -> Context a
217−mapContext f (Context c) = Context $ \k a i -> do
218− fld <- c k a i
219− case fld of
220− EmptyField -> wrongType "boolField"
221− StringField str -> return $ StringField (f str)
222− _ -> wrongType "ListField"
223− where
224− wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
225− "can't map over a " ++ typ ++ "!"
226−
227−--------------------------------------------------------------------------------
228−-- | A context that allows snippet inclusion. In processed file, use as:
229−--
230−-- > ...
231−-- > $snippet("path/to/snippet/")$
232−-- > ...
233−--
234−-- The contents of the included file will not be interpolated like @partial@
235−-- does it.
236−--
237−snippetField :: Context String
238−snippetField = functionField "snippet" f
239− where
240− f [contentsPath] _ = loadBody (fromFilePath contentsPath)
241− f [] _ = fail "No argument to function 'snippet()'"
242− f _ _ = fail "Too many arguments to function 'snippet()'"
243−
244−--------------------------------------------------------------------------------
245−-- | A context that contains (in that order)
246−--
247−-- 1. A @$body$@ field
248−--
249−-- 2. Metadata fields
250−--
251−-- 3. A @$url$@ 'urlField'
252−--
253−-- 4. A @$path$@ 'pathField'
254−--
255−-- 5. A @$title$@ 'titleField'
256−defaultContext :: Context String
257−defaultContext =
258− bodyField "body" `mappend`
259− metadataField `mappend`
260− urlField "url" `mappend`
261− pathField "path" `mappend`
262− titleField "title"
263−
264−
265−--------------------------------------------------------------------------------
266−teaserSeparator :: String
267−teaserSeparator = "<!--more-->"
268−
269−
270−--------------------------------------------------------------------------------
271−-- | Constructs a 'field' that contains the body of the item.
272−bodyField :: String -> Context String
273−bodyField key = field key $ return . itemBody
274−
275−
276−--------------------------------------------------------------------------------
277−-- | Map any field to its metadata value, if present
278−metadataField :: Context a
279−metadataField = Context $ \k _ i -> do
280− let id = itemIdentifier i
281− empty' = noResult $ "No '" ++ k ++ "' field in metadata " ++
282− "of item " ++ show id
283− value <- getMetadataField id k
284− maybe empty' (return . StringField) value
285−
286−
287−--------------------------------------------------------------------------------
288−-- | Absolute url to the resulting item
289−urlField :: String -> Context a
290−urlField key = field key $ \i -> do
291− let id = itemIdentifier i
292− empty' = fail $ "No route url found for item " ++ show id
293− fmap (maybe empty' toUrl) $ getRoute id
294−
295−
296−--------------------------------------------------------------------------------
297−-- | Filepath of the underlying file of the item
298−pathField :: String -> Context a
299−pathField key = field key $ return . toFilePath . itemIdentifier
300−
301−
302−--------------------------------------------------------------------------------
303−-- | This title 'field' takes the basename of the underlying file by default
304−titleField :: String -> Context a
305−titleField = mapContext takeBaseName . pathField
306−
307−
308−--------------------------------------------------------------------------------
309−-- | When the metadata has a field called @published@ in one of the
310−-- following formats then this function can render the date.
311−--
312−-- * @Mon, 06 Sep 2010 00:01:00 +0000@
313−--
314−-- * @Mon, 06 Sep 2010 00:01:00 UTC@
315−--
316−-- * @Mon, 06 Sep 2010 00:01:00@
317−--
318−-- * @2010-09-06T00:01:00+0000@
319−--
320−-- * @2010-09-06T00:01:00Z@
321−--
322−-- * @2010-09-06T00:01:00@
323−--
324−-- * @2010-09-06 00:01:00+0000@
325−--
326−-- * @2010-09-06 00:01:00@
327−--
328−-- * @September 06, 2010 00:01 AM@
329−--
330−-- Following date-only formats are supported too (@00:00:00@ for time is
331−-- assumed)
332−--
333−-- * @2010-09-06@
334−--
335−-- * @September 06, 2010@
336−--
337−-- Alternatively, when the metadata has a field called @path@ in a
338−-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
339−-- and no @published@ metadata field set, this function can render
340−-- the date. This pattern matches the file name or directory names
341−-- that begins with @yyyy-mm-dd@ . For example:
342−-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
343−-- In case of multiple matches, the rightmost one is used.
344−--
345−-- As another alternative, if none of the above matches, and the file has a
346−-- path which contains nested directories specifying a date, then that date
347−-- will be used. In other words, if the path is of the form
348−-- @**//yyyy//mm//dd//**//main.extension@ .
349−-- As above, in case of multiple matches, the rightmost one is used.
350−
351−dateField :: String -- ^ Key in which the rendered date should be placed
352− -> String -- ^ Format to use on the date
353− -> Context a -- ^ Resulting context
354−dateField = dateFieldWith defaultTimeLocale
355−
356−
357−--------------------------------------------------------------------------------
358−-- | This is an extended version of 'dateField' that allows you to
359−-- specify a time locale that is used for outputting the date. For more
360−-- details, see 'dateField' and 'formatTime'.
361−dateFieldWith :: TimeLocale -- ^ Output time locale
362− -> String -- ^ Destination key
363− -> String -- ^ Format to use on the date
364− -> Context a -- ^ Resulting context
365−dateFieldWith locale key format = field key $ \i -> do
366− time <- getItemUTC locale $ itemIdentifier i
367− return $ formatTime locale format time
368−
369−
370−--------------------------------------------------------------------------------
371−-- | Parser to try to extract and parse the time from the @published@
372−-- field or from the filename. See 'dateField' for more information.
373−-- Exported for user convenience.
374−getItemUTC :: MonadMetadata m
375− => TimeLocale -- ^ Output time locale
376− -> Identifier -- ^ Input page
377− -> m UTCTime -- ^ Parsed UTCTime
378−getItemUTC locale id' = do
379− metadata <- getMetadata id'
380− let tryField k fmt = lookupString k metadata >>= parseTime' fmt
381− paths = splitDirectories $ (dropExtension . toFilePath) id'
382−
383− maybe empty' return $ msum $
384− [tryField "published" fmt | fmt <- formats] ++
385− [tryField "date" fmt | fmt <- formats] ++
386− [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths] ++
387− [parseTime' "%Y-%m-%d" $ intercalate "-" $ fnCand | fnCand <- map (take 3) $ reverse . tails $ paths]
388− where
389− empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
390− "could not parse time for " ++ show id'
391− parseTime' = parseTimeM True locale
392− formats =
393− [ "%a, %d %b %Y %H:%M:%S %Z"
394− , "%Y-%m-%dT%H:%M:%S%Z"
395− , "%Y-%m-%d %H:%M:%S%Z"
396− , "%Y-%m-%d"
397− , "%B %e, %Y %l:%M %p"
398− , "%B %e, %Y"
399− , "%b %d, %Y"
400− ]
401−
402−
403−--------------------------------------------------------------------------------
404−-- | Get the time on which the actual file was last modified. This only works if
405−-- there actually is an underlying file, of couse.
406−getItemModificationTime
407− :: Identifier
408− -> Compiler UTCTime
409−getItemModificationTime identifier = do
410− provider <- compilerProvider <$> compilerAsk
411− return $ resourceModificationTime provider identifier
412−
413−
414−--------------------------------------------------------------------------------
415−-- | Creates a field with the last modification date of the underlying item.
416−modificationTimeField :: String -- ^ Key
417− -> String -- ^ Format
418− -> Context a -- ^ Resulting context
419−modificationTimeField = modificationTimeFieldWith defaultTimeLocale
420−
421−
422−--------------------------------------------------------------------------------
423−-- | Creates a field with the last modification date of the underlying item
424−-- in a custom localisation format (see 'formatTime').
425−modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
426− -> String -- ^ Key
427− -> String -- ^ Format
428− -> Context a -- ^ Resulting context
429−modificationTimeFieldWith locale key fmt = field key $ \i -> do
430− mtime <- getItemModificationTime $ itemIdentifier i
431− return $ formatTime locale fmt mtime
432−
433−
434−--------------------------------------------------------------------------------
435−-- | A context with "teaser" key which contain a teaser of the item.
436−-- The item is loaded from the given snapshot (which should be saved
437−-- in the user code before any templates are applied).
438−teaserField :: String -- ^ Key to use
439− -> Snapshot -- ^ Snapshot to load
440− -> Context String -- ^ Resulting context
441−teaserField = teaserFieldWithSeparator teaserSeparator
442−
443−
444−--------------------------------------------------------------------------------
445−-- | A context with "teaser" key which contain a teaser of the item, defined as
446−-- the snapshot content before the teaser separator. The item is loaded from the
447−-- given snapshot (which should be saved in the user code before any templates
448−-- are applied).
449−teaserFieldWithSeparator :: String -- ^ Separator to use
450− -> String -- ^ Key to use
451− -> Snapshot -- ^ Snapshot to load
452− -> Context String -- ^ Resulting context
453−teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
454− body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
455− case needlePrefix separator body of
456− Nothing -> fail $
457− "Hakyll.Web.Template.Context: no teaser defined for " ++
458− show (itemIdentifier item)
459− Just t -> return t
460−
461−
462−--------------------------------------------------------------------------------
463−-- | Constantly reports any field as missing. Mostly for internal usage,
464−-- it is the last choice in every context used in a template application.
465−missingField :: Context a
466−missingField = Context $ \k _ _ -> noResult $
467− "Missing field '" ++ k ++ "' in context"
468−
469−parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
470−#if MIN_VERSION_time(1,5,0)
471−parseTimeM = TF.parseTimeM
472−#else
473−parseTimeM _ = TF.parseTime
474−#endif
475−
476−--------------------------------------------------------------------------------
477−
478−-- | Binds an 'Item' to a given 'Context', allowing it to be combined with any
479−-- other 'Context' of any type.
480−bindItem :: Context a -> Item a -> forall b. Context b
481−bindItem (Context ctx) ia = Context $ \k args _ -> ctx k args ia
lib/Hakyll/Web/Template/Internal.hsdeleted−261
@@ -1,261 +0,0 @@
1−{-# LANGUAGE DeriveAnyClass #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−
6−module Hakyll.Web.Template.Internal
7− ( Template (..)
8− , template
9− , templateBodyCompiler
10− , templateCompiler
11− , applyTemplate
12− , loadAndApplyTemplate
13− , applyAsTemplate
14− , readTemplate
15− , compileTemplateItem
16− , unsafeReadTemplateFile
17−
18− , module Hakyll.Web.Template.Internal.Element
19− , module Hakyll.Web.Template.Internal.Trim
20− ) where
21−
22−
23−--------------------------------------------------------------------------------
24−import Control.Monad (forM)
25−import Control.Monad.Except (MonadError (..), catchError)
26−import Data.Binary (Binary)
27−import Data.List (intercalate)
28−import qualified Data.List.NonEmpty as NonEmpty
29−import Data.Typeable (Typeable)
30−import GHC.Exts (IsString (..))
31−import GHC.Generics (Generic)
32−
33−
34−--------------------------------------------------------------------------------
35−import Hakyll.Core.Compiler
36−import Hakyll.Core.Compiler.Internal
37−import Hakyll.Core.Identifier
38−import Hakyll.Core.Item
39−import Hakyll.Core.Writable
40−import Hakyll.Web.Template.Context
41−import Hakyll.Web.Template.Internal.Element
42−import Hakyll.Web.Template.Internal.Trim
43−
44−
45−--------------------------------------------------------------------------------
46−-- | Datatype used for template substitutions.
47−data Template = Template
48− { tplElements :: [TemplateElement]
49− , tplOrigin :: FilePath -- Only for error messages.
50− } deriving (Show, Eq, Generic, Binary, Typeable)
51−
52−
53−--------------------------------------------------------------------------------
54−instance Writable Template where
55− -- Writing a template is impossible
56− write _ _ = return ()
57−
58−
59−--------------------------------------------------------------------------------
60−instance IsString Template where
61− fromString = readTemplate
62−
63−
64−--------------------------------------------------------------------------------
65−-- | Wrap the constructor to ensure trim is called.
66−template :: FilePath -> [TemplateElement] -> Template
67−template p = flip Template p . trim
68−
69−
70−--------------------------------------------------------------------------------
71−-- | Parse a string into a template.
72−-- You should prefer 'compileTemplateItem' over this.
73−readTemplate :: String -> Template
74−readTemplate = either error (template origin) . parseTemplateElemsFile origin
75− where
76− origin = "{literal}"
77−{-# DEPRECATED readTemplate "Use templateCompiler instead" #-}
78−
79−--------------------------------------------------------------------------------
80−-- | Parse an item body into a template.
81−-- Provides useful error messages in the 'Compiler' monad.
82−compileTemplateItem :: Item String -> Compiler Template
83−compileTemplateItem item = let file = itemIdentifier item
84− in compileTemplateFile file (itemBody item)
85−
86−--------------------------------------------------------------------------------
87−compileTemplateFile :: Identifier -> String -> Compiler Template
88−compileTemplateFile file = either fail (return . template origin)
89− . parseTemplateElemsFile origin
90− where
91− origin = show file
92−
93−--------------------------------------------------------------------------------
94−-- | Read a template, without metadata header
95−templateBodyCompiler :: Compiler (Item Template)
96−templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
97− item <- getResourceBody
98− file <- getUnderlying
99− withItemBody (compileTemplateFile file) item
100−
101−--------------------------------------------------------------------------------
102−-- | Read complete file contents as a template
103−templateCompiler :: Compiler (Item Template)
104−templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
105− item <- getResourceString
106− file <- getUnderlying
107− withItemBody (compileTemplateFile file) item
108−
109−
110−--------------------------------------------------------------------------------
111−-- | Interpolate template expressions from context values in a page
112−applyTemplate :: Template -- ^ Template
113− -> Context a -- ^ Context
114− -> Item a -- ^ Page
115− -> Compiler (Item String) -- ^ Resulting item
116−applyTemplate tpl context item = do
117− body <- applyTemplate' (tplElements tpl) context item `catchError` handler
118− return $ itemSetBody body item
119− where
120− tplName = tplOrigin tpl
121− itemName = show $ itemIdentifier item
122− handler es = fail $ "Hakyll.Web.Template.applyTemplate: Failed to " ++
123− (if tplName == itemName
124− then "interpolate template in item " ++ itemName
125− else "apply template " ++ tplName ++ " to item " ++ itemName) ++
126− ":\n" ++ intercalate ",\n" es
127−
128−
129−
130−--------------------------------------------------------------------------------
131−applyTemplate'
132− :: forall a.
133− [TemplateElement] -- ^ Unwrapped Template
134− -> Context a -- ^ Context
135− -> Item a -- ^ Page
136− -> Compiler String -- ^ Resulting item
137−applyTemplate' tes context x = go tes
138− where
139− context' :: String -> [String] -> Item a -> Compiler ContextField
140− context' = unContext (context `mappend` missingField)
141−
142− go = fmap concat . mapM applyElem
143−
144− ---------------------------------------------------------------------------
145−
146− applyElem :: TemplateElement -> Compiler String
147−
148− applyElem TrimL = trimError
149−
150− applyElem TrimR = trimError
151−
152− applyElem (Chunk c) = return c
153−
154− applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)
155− where
156− evalMsg = "In expr '$" ++ show e ++ "$'"
157− typeMsg = "expr '$" ++ show e ++ "$'"
158−
159− applyElem Escaped = return "$"
160−
161− applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
162− where
163− f = maybe (return "") go mf
164− handle (Right _) = go t
165− handle (Left (CompilationNoResult _)) = f
166− handle (Left (CompilationFailure es)) = debug (NonEmpty.toList es) >> f
167− debug = compilerDebugEntries ("Hakyll.Web.Template.applyTemplate: " ++
168− "[ERROR] in 'if' condition on expr '" ++ show e ++ "':")
169−
170− applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of
171− EmptyField -> expected "list" "boolean" typeMsg
172− StringField _ -> expected "list" "string" typeMsg
173− ListField c xs -> withErrorMessage bodyMsg $ do
174− sep <- maybe (return "") go s
175− bs <- forM xs $ applyTemplate' b $ c <> bindItem context x
176− return $ intercalate sep bs
177− where
178− headMsg = "In expr '$for(" ++ show e ++ ")$'"
179− typeMsg = "loop expr '" ++ show e ++ "'"
180− bodyMsg = "In loop context of '$for(" ++ show e ++ ")$'"
181−
182− applyElem (Partial e) = withErrorMessage headMsg $
183− applyStringExpr typeMsg e >>= \p ->
184− withErrorMessage inclMsg $ do
185− tpl' <- loadBody (fromFilePath p)
186− itemBody <$> applyTemplate tpl' context x
187− where
188− headMsg = "In expr '$partial(" ++ show e ++ ")$'"
189− typeMsg = "partial expr '" ++ show e ++ "'"
190− inclMsg = "In inclusion of '$partial(" ++ show e ++ ")$'"
191−
192− ---------------------------------------------------------------------------
193−
194− applyExpr :: TemplateExpr -> Compiler ContextField
195−
196− applyExpr (Ident (TemplateKey k)) = context' k [] x
197−
198− applyExpr (Call (TemplateKey k) args) = do
199− args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
200− context' k args' x
201− where
202− typeMsg e = "argument '" ++ show e ++ "'"
203−
204− applyExpr (StringLiteral s) = return (StringField s)
205−
206− ----------------------------------------------------------------------------
207−
208− applyStringExpr :: String -> TemplateExpr -> Compiler String
209− applyStringExpr msg expr =
210− applyExpr expr >>= getString
211− where
212− getString EmptyField = expected "string" "boolean" msg
213− getString (StringField s) = return s
214− getString (ListField _ _) = expected "string" "list" msg
215−
216− expected typ act expr = fail $ unwords ["Hakyll.Web.Template.applyTemplate:",
217− "expected", typ, "but got", act, "for", expr]
218−
219− -- expected to never happen with all templates constructed by 'template'
220− trimError = fail $
221− "Hakyll.Web.Template.applyTemplate: template not fully trimmed."
222−
223−
224−--------------------------------------------------------------------------------
225−-- | The following pattern is so common:
226−--
227−-- > tpl <- loadBody "templates/foo.html"
228−-- > someCompiler
229−-- > >>= applyTemplate tpl context
230−--
231−-- That we have a single function which does this:
232−--
233−-- > someCompiler
234−-- > >>= loadAndApplyTemplate "templates/foo.html" context
235−loadAndApplyTemplate :: Identifier -- ^ Template identifier
236− -> Context a -- ^ Context
237− -> Item a -- ^ Page
238− -> Compiler (Item String) -- ^ Resulting item
239−loadAndApplyTemplate identifier context item = do
240− tpl <- loadBody identifier
241− applyTemplate tpl context item
242−
243−
244−--------------------------------------------------------------------------------
245−-- | It is also possible that you want to substitute @$key$@s within the body of
246−-- an item. This function does that by interpreting the item body as a template,
247−-- and then applying it to itself.
248−applyAsTemplate :: Context String -- ^ Context
249− -> Item String -- ^ Item and template
250− -> Compiler (Item String) -- ^ Resulting item
251−applyAsTemplate context item = do
252− tpl <- compileTemplateItem item
253− applyTemplate tpl context item
254−
255−
256−--------------------------------------------------------------------------------
257−unsafeReadTemplateFile :: FilePath -> Compiler Template
258−unsafeReadTemplateFile file = do
259− tpl <- unsafeCompiler $ readFile file
260− compileTemplateFile (fromFilePath file) tpl
261−{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
lib/Hakyll/Web/Template/Internal/Element.hsdeleted−291
@@ -1,291 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module containing the elements used in a template. A template is generally
3−-- just a list of these elements.
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−module Hakyll.Web.Template.Internal.Element
6− ( TemplateKey (..)
7− , TemplateExpr (..)
8− , TemplateElement (..)
9− , templateElems
10− , parseTemplateElemsFile
11− ) where
12−
13−
14−--------------------------------------------------------------------------------
15−import Control.Applicative ((<|>), (<*))
16−import Control.Monad (void)
17−import Control.Arrow (left)
18−import Data.Binary (Binary, get, getWord8, put, putWord8)
19−import Data.List (intercalate)
20−import Data.Maybe (isJust)
21−import Data.Typeable (Typeable)
22−import GHC.Exts (IsString (..))
23−import qualified Text.Parsec as P
24−import qualified Text.Parsec.String as P
25−
26−
27−--------------------------------------------------------------------------------
28−import Hakyll.Core.Util.Parser
29−
30−
31−--------------------------------------------------------------------------------
32−newtype TemplateKey = TemplateKey String
33− deriving (Binary, Show, Eq, Typeable)
34−
35−
36−--------------------------------------------------------------------------------
37−instance IsString TemplateKey where
38− fromString = TemplateKey
39−
40−
41−--------------------------------------------------------------------------------
42−-- | Elements of a template.
43−data TemplateElement
44− = Chunk String
45− | Expr TemplateExpr
46− | Escaped
47− -- expr, then, else
48− | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
49− -- expr, body, separator
50− | For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
51− -- filename
52− | Partial TemplateExpr
53− | TrimL
54− | TrimR
55− deriving (Show, Eq, Typeable)
56−
57−
58−--------------------------------------------------------------------------------
59−instance Binary TemplateElement where
60− put (Chunk string) = putWord8 0 >> put string
61− put (Expr e) = putWord8 1 >> put e
62− put Escaped = putWord8 2
63− put (If e t f) = putWord8 3 >> put e >> put t >> put f
64− put (For e b s) = putWord8 4 >> put e >> put b >> put s
65− put (Partial e) = putWord8 5 >> put e
66− put TrimL = putWord8 6
67− put TrimR = putWord8 7
68−
69− get = getWord8 >>= \tag -> case tag of
70− 0 -> Chunk <$> get
71− 1 -> Expr <$> get
72− 2 -> pure Escaped
73− 3 -> If <$> get <*> get <*> get
74− 4 -> For <$> get <*> get <*> get
75− 5 -> Partial <$> get
76− 6 -> pure TrimL
77− 7 -> pure TrimR
78− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
79−
80−
81−--------------------------------------------------------------------------------
82−-- | Expression in a template
83−data TemplateExpr
84− = Ident TemplateKey
85− | Call TemplateKey [TemplateExpr]
86− | StringLiteral String
87− deriving (Eq, Typeable)
88−
89−
90−--------------------------------------------------------------------------------
91−instance Show TemplateExpr where
92− show (Ident (TemplateKey k)) = k
93− show (Call (TemplateKey k) as) =
94− k ++ "(" ++ intercalate ", " (map show as) ++ ")"
95− show (StringLiteral s) = show s
96−
97−
98−--------------------------------------------------------------------------------
99−instance Binary TemplateExpr where
100− put (Ident k) = putWord8 0 >> put k
101− put (Call k as) = putWord8 1 >> put k >> put as
102− put (StringLiteral s) = putWord8 2 >> put s
103−
104− get = getWord8 >>= \tag -> case tag of
105− 0 -> Ident <$> get
106− 1 -> Call <$> get <*> get
107− 2 -> StringLiteral <$> get
108− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109−
110−--------------------------------------------------------------------------------
111−parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
112−parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
113− . P.parse (templateElems <* P.eof) file
114−
115−
116−--------------------------------------------------------------------------------
117−templateElems :: P.Parser [TemplateElement]
118−templateElems = mconcat <$> P.many (P.choice [ lift chunk
119− , lift escaped
120− , conditional
121− , for
122− , partial
123− , expr
124− ])
125− where lift = fmap (:[])
126−
127−
128−--------------------------------------------------------------------------------
129−chunk :: P.Parser TemplateElement
130−chunk = Chunk <$> P.many1 (P.noneOf "$")
131−
132−
133−--------------------------------------------------------------------------------
134−expr :: P.Parser [TemplateElement]
135−expr = P.try $ do
136− trimLExpr <- trimOpen
137− e <- expr'
138− trimRExpr <- trimClose
139− return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
140−
141−
142−--------------------------------------------------------------------------------
143−expr' :: P.Parser TemplateExpr
144−expr' = stringLiteral <|> call <|> ident
145−
146−
147−--------------------------------------------------------------------------------
148−escaped :: P.Parser TemplateElement
149−escaped = Escaped <$ P.try (P.string "$$")
150−
151−
152−--------------------------------------------------------------------------------
153−trimOpen :: P.Parser Bool
154−trimOpen = do
155− void $ P.char '$'
156− trimLIf <- P.optionMaybe $ P.try (P.char '-')
157− pure $ isJust trimLIf
158−
159−
160−--------------------------------------------------------------------------------
161−trimClose :: P.Parser Bool
162−trimClose = do
163− trimIfR <- P.optionMaybe $ (P.char '-')
164− void $ P.char '$'
165− pure $ isJust trimIfR
166−
167−
168−--------------------------------------------------------------------------------
169−conditional :: P.Parser [TemplateElement]
170−conditional = P.try $ do
171− -- if
172− trimLIf <- trimOpen
173− void $ P.string "if("
174− e <- expr'
175− void $ P.char ')'
176− trimRIf <- trimClose
177− -- then
178− thenBranch <- templateElems
179− -- else
180− elseParse <- opt "else"
181− -- endif
182− trimLEnd <- trimOpen
183− void $ P.string "endif"
184− trimREnd <- trimClose
185−
186− -- As else is optional we need to sort out where any Trim_s need to go.
187− let (thenBody, elseBody) = maybe (thenNoElse, Nothing) thenElse elseParse
188− where thenNoElse =
189− [TrimR | trimRIf] ++ thenBranch ++ [TrimL | trimLEnd]
190−
191− thenElse (trimLElse, elseBranch, trimRElse) = (thenB, elseB)
192− where thenB = [TrimR | trimRIf]
193− ++ thenBranch
194− ++ [TrimL | trimLElse]
195−
196− elseB = Just $ [TrimR | trimRElse]
197− ++ elseBranch
198− ++ [TrimL | trimLEnd]
199−
200− pure $ [TrimL | trimLIf] ++ [If e thenBody elseBody] ++ [TrimR | trimREnd]
201−
202−
203−--------------------------------------------------------------------------------
204−for :: P.Parser [TemplateElement]
205−for = P.try $ do
206− -- for
207− trimLFor <- trimOpen
208− void $ P.string "for("
209− e <- expr'
210− void $ P.char ')'
211− trimRFor <- trimClose
212− -- body
213− bodyBranch <- templateElems
214− -- sep
215− sepParse <- opt "sep"
216− -- endfor
217− trimLEnd <- trimOpen
218− void $ P.string "endfor"
219− trimREnd <- trimClose
220−
221− -- As sep is optional we need to sort out where any Trim_s need to go.
222− let (forBody, sepBody) = maybe (forNoSep, Nothing) forSep sepParse
223− where forNoSep =
224− [TrimR | trimRFor] ++ bodyBranch ++ [TrimL | trimLEnd]
225−
226− forSep (trimLSep, sepBranch, trimRSep) = (forB, sepB)
227− where forB = [TrimR | trimRFor]
228− ++ bodyBranch
229− ++ [TrimL | trimLSep]
230−
231− sepB = Just $ [TrimR | trimRSep]
232− ++ sepBranch
233− ++ [TrimL | trimLEnd]
234−
235− pure $ [TrimL | trimLFor] ++ [For e forBody sepBody] ++ [TrimR | trimREnd]
236−
237−
238−--------------------------------------------------------------------------------
239−partial :: P.Parser [TemplateElement]
240−partial = P.try $ do
241− trimLPart <- trimOpen
242− void $ P.string "partial("
243− e <- expr'
244− void $ P.char ')'
245− trimRPart <- trimClose
246−
247− pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
248−
249−
250−--------------------------------------------------------------------------------
251−ident :: P.Parser TemplateExpr
252−ident = P.try $ Ident <$> key
253−
254−
255−--------------------------------------------------------------------------------
256−call :: P.Parser TemplateExpr
257−call = P.try $ do
258− f <- key
259− void $ P.char '('
260− P.spaces
261− as <- P.sepBy expr' (P.spaces >> P.char ',' >> P.spaces)
262− P.spaces
263− void $ P.char ')'
264− return $ Call f as
265−
266−
267−--------------------------------------------------------------------------------
268−stringLiteral :: P.Parser TemplateExpr
269−stringLiteral = do
270− void $ P.char '\"'
271− str <- P.many $ do
272− x <- P.noneOf "\""
273− if x == '\\' then P.anyChar else return x
274− void $ P.char '\"'
275− return $ StringLiteral str
276−
277−
278−--------------------------------------------------------------------------------
279−key :: P.Parser TemplateKey
280−key = TemplateKey <$> metadataKey
281−
282−
283−--------------------------------------------------------------------------------
284−opt :: String -> P.Parser (Maybe (Bool, [TemplateElement], Bool))
285−opt clause = P.optionMaybe $ P.try $ do
286− trimL <- trimOpen
287− void $ P.string clause
288− trimR <- trimClose
289− branch <- templateElems
290− pure (trimL, branch, trimR)
291−
lib/Hakyll/Web/Template/Internal/Trim.hsdeleted−95
@@ -1,95 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module for trimming whitespace from tempaltes.
3−module Hakyll.Web.Template.Internal.Trim
4− ( trim
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Data.Char (isSpace)
10−import Data.List (dropWhileEnd)
11−
12−
13−--------------------------------------------------------------------------------
14−import Hakyll.Web.Template.Internal.Element
15−
16−
17−--------------------------------------------------------------------------------
18−trim :: [TemplateElement] -> [TemplateElement]
19−trim = cleanse . canonicalize
20−
21−
22−--------------------------------------------------------------------------------
23−-- | Apply the Trim nodes to the Chunks.
24−cleanse :: [TemplateElement] -> [TemplateElement]
25−cleanse = recurse cleanse . process
26− where process [] = []
27− process (TrimR:Chunk str:ts) = let str' = dropWhile isSpace str
28− in if null str'
29− then process ts
30− -- Might need to TrimL.
31− else process $ Chunk str':ts
32−
33− process (Chunk str:TrimL:ts) = let str' = dropWhileEnd isSpace str
34− in if null str'
35− then process ts
36− else Chunk str':process ts
37−
38− process (t:ts) = t:process ts
39−
40−--------------------------------------------------------------------------------
41−-- | Enforce the invariant that:
42−--
43−-- * Every 'TrimL' has a 'Chunk' to its left.
44−-- * Every 'TrimR' has a 'Chunk' to its right.
45−--
46−canonicalize :: [TemplateElement] -> [TemplateElement]
47−canonicalize = go
48− where go t = let t' = redundant . swap $ dedupe t
49− in if t == t' then t else go t'
50−
51−
52−--------------------------------------------------------------------------------
53−-- | Remove the 'TrimR' and 'TrimL's that are no-ops.
54−redundant :: [TemplateElement] -> [TemplateElement]
55−redundant = recurse redundant . process
56− where -- Remove the leading 'TrimL's.
57− process (TrimL:ts) = process ts
58− -- Remove trailing 'TrimR's.
59− process ts = foldr trailing [] ts
60− where trailing TrimR [] = []
61− trailing x xs = x:xs
62−
63−
64−--------------------------------------------------------------------------------
65−-- >>> swap $ [TrimR, TrimL]
66−-- [TrimL, TrimR]
67−swap :: [TemplateElement] -> [TemplateElement]
68−swap = recurse swap . process
69− where process [] = []
70− process (TrimR:TrimL:ts) = TrimL:process (TrimR:ts)
71− process (t:ts) = t:process ts
72−
73−
74−--------------------------------------------------------------------------------
75−-- | Remove 'TrimR' and 'TrimL' duplication.
76−dedupe :: [TemplateElement] -> [TemplateElement]
77−dedupe = recurse dedupe . process
78− where process [] = []
79− process (TrimR:TrimR:ts) = process (TrimR:ts)
80− process (TrimL:TrimL:ts) = process (TrimL:ts)
81− process (t:ts) = t:process ts
82−
83−
84−--------------------------------------------------------------------------------
85−-- | @'recurse' f t@ applies f to every '[TemplateElement]' in t.
86−recurse :: ([TemplateElement] -> [TemplateElement])
87− -> [TemplateElement]
88− -> [TemplateElement]
89−recurse _ [] = []
90−recurse f (x:xs) = process x:recurse f xs
91− where process y = case y of
92− If e tb eb -> If e (f tb) (f <$> eb)
93− For e t s -> For e (f t) (f <$> s)
94− _ -> y
95−
lib/Hakyll/Web/Template/List.hsdeleted−91
@@ -1,91 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Provides an easy way to combine several items in a list. The applications
3−-- are obvious:
4−--
5−-- * A post list on a blog
6−--
7−-- * An image list in a gallery
8−--
9−-- * A sitemap
10−{-# LANGUAGE TupleSections #-}
11−module Hakyll.Web.Template.List
12− ( applyTemplateList
13− , applyJoinTemplateList
14− , chronological
15− , recentFirst
16− , sortChronological
17− , sortRecentFirst
18− ) where
19−
20−
21−--------------------------------------------------------------------------------
22−import Control.Monad (liftM)
23−import Data.List (intersperse, sortBy)
24−import Data.Ord (comparing)
25−import Data.Time.Locale.Compat (defaultTimeLocale)
26−
27−
28−--------------------------------------------------------------------------------
29−import Hakyll.Core.Compiler
30−import Hakyll.Core.Identifier
31−import Hakyll.Core.Item
32−import Hakyll.Core.Metadata
33−import Hakyll.Web.Template
34−import Hakyll.Web.Template.Context
35−
36−
37−--------------------------------------------------------------------------------
38−-- | Generate a string of a listing of pages, after applying a template to each
39−-- page.
40−applyTemplateList :: Template
41− -> Context a
42− -> [Item a]
43− -> Compiler String
44−applyTemplateList = applyJoinTemplateList ""
45−
46−
47−--------------------------------------------------------------------------------
48−-- | Join a listing of pages with a string in between, after applying a template
49−-- to each page.
50−applyJoinTemplateList :: String
51− -> Template
52− -> Context a
53− -> [Item a]
54− -> Compiler String
55−applyJoinTemplateList delimiter tpl context items = do
56− items' <- mapM (applyTemplate tpl context) items
57− return $ concat $ intersperse delimiter $ map itemBody items'
58−
59−
60−--------------------------------------------------------------------------------
61−-- | Sort pages chronologically. Uses the same method as 'dateField' for
62−-- extracting the date.
63−chronological :: MonadMetadata m => [Item a] -> m [Item a]
64−chronological =
65− sortByM $ getItemUTC defaultTimeLocale . itemIdentifier
66− where
67− sortByM :: (Monad m, Ord k) => (a -> m k) -> [a] -> m [a]
68− sortByM f xs = liftM (map fst . sortBy (comparing snd)) $
69− mapM (\x -> liftM (x,) (f x)) xs
70−
71−
72−--------------------------------------------------------------------------------
73−-- | The reverse of 'chronological'
74−recentFirst :: MonadMetadata m => [Item a] -> m [Item a]
75−recentFirst = liftM reverse . chronological
76−
77−
78−--------------------------------------------------------------------------------
79−-- | Version of 'chronological' which doesn't need the actual items.
80−sortChronological
81− :: MonadMetadata m => [Identifier] -> m [Identifier]
82−sortChronological ids =
83− liftM (map itemIdentifier) $ chronological [Item i () | i <- ids]
84−
85−
86−--------------------------------------------------------------------------------
87−-- | Version of 'recentFirst' which doesn't need the actual items.
88−sortRecentFirst
89− :: MonadMetadata m => [Identifier] -> m [Identifier]
90−sortRecentFirst ids =
91− liftM (map itemIdentifier) $ recentFirst [Item i () | i <- ids]
lib/Hakyll.hs → src/Hakyll.hsrenamed+2−8
@@ -20,17 +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
25 , module Hakyll.Web.Pandoc
23 , module Hakyll.Web.Pandoc
26 , module Hakyll.Web.Pandoc.Biblio
24 , module Hakyll.Web.Pandoc.Biblio
27 , module Hakyll.Web.Pandoc.FileType
25 , module Hakyll.Web.Pandoc.FileType
28−#endif
29− , module Hakyll.Web.Redirect
30 , module Hakyll.Web.Tags
26 , module Hakyll.Web.Tags
31 , module Hakyll.Web.Template
27 , module Hakyll.Web.Template
32 , module Hakyll.Web.Template.Context
28 , module Hakyll.Web.Template.Context
33 , module Hakyll.Web.Template.List
29 , module Hakyll.Web.Template.List
30+ , module Hakyll.Web.Template.Read
34 ) where
31 ) where
35
32
36
33
@@ -53,14 +50,11 @@ import Hakyll.Web.CompressCss
53import Hakyll.Web.Feed
50import Hakyll.Web.Feed
54import Hakyll.Web.Html
51import Hakyll.Web.Html
55import Hakyll.Web.Html.RelativizeUrls
52import Hakyll.Web.Html.RelativizeUrls
56−import Hakyll.Web.Paginate
57−#ifdef USE_PANDOC
58import Hakyll.Web.Pandoc
53import Hakyll.Web.Pandoc
59import Hakyll.Web.Pandoc.Biblio
54import Hakyll.Web.Pandoc.Biblio
60import Hakyll.Web.Pandoc.FileType
55import Hakyll.Web.Pandoc.FileType
61−#endif
62−import Hakyll.Web.Redirect
63import Hakyll.Web.Tags
56import Hakyll.Web.Tags
64import Hakyll.Web.Template
57import Hakyll.Web.Template
65import Hakyll.Web.Template.Context
58import Hakyll.Web.Template.Context
66import Hakyll.Web.Template.List
59import Hakyll.Web.Template.List
60+import Hakyll.Web.Template.Read
src/Hakyll/Check.hsnew file+199
@@ -0,0 +1,199 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Check
4+ ( Check (..)
5+ , check
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Applicative ((<$>))
11+import Control.Exception (AsyncException (..),
12+ SomeException (..), handle, throw)
13+import Control.Monad (forM_)
14+import Control.Monad.Reader (ask)
15+import Control.Monad.RWS (RWST, runRWST)
16+import Control.Monad.State (get, modify)
17+import Control.Monad.Trans (liftIO)
18+import Control.Monad.Writer (tell)
19+import Data.List (isPrefixOf)
20+import Data.Monoid (Monoid (..))
21+import Data.Set (Set)
22+import qualified Data.Set as S
23+import Data.Typeable (cast)
24+import qualified Network.HTTP.Conduit as Http
25+import qualified Network.HTTP.Types as Http
26+import System.Directory (doesDirectoryExist, doesFileExist)
27+import System.Exit (ExitCode (..))
28+import System.FilePath (takeDirectory, takeExtension, (</>))
29+import qualified Text.HTML.TagSoup as TS
30+
31+
32+--------------------------------------------------------------------------------
33+import Hakyll.Core.Configuration
34+import Hakyll.Core.Logger (Logger, Verbosity)
35+import qualified Hakyll.Core.Logger as Logger
36+import Hakyll.Core.Util.File
37+import Hakyll.Web.Html
38+
39+
40+--------------------------------------------------------------------------------
41+data Check = All | InternalLinks
42+ deriving (Eq, Ord, Show)
43+
44+
45+--------------------------------------------------------------------------------
46+check :: Configuration -> Verbosity -> Check -> IO ExitCode
47+check config verbosity check' = do
48+ ((), write) <- runChecker checkDestination config verbosity check'
49+ return $ if checkerFaulty write >= 0 then ExitFailure 1 else ExitSuccess
50+
51+
52+--------------------------------------------------------------------------------
53+data CheckerRead = CheckerRead
54+ { checkerConfig :: Configuration
55+ , checkerLogger :: Logger
56+ , checkerCheck :: Check
57+ }
58+
59+
60+--------------------------------------------------------------------------------
61+data CheckerWrite = CheckerWrite
62+ { checkerFaulty :: Int
63+ , checkerOk :: Int
64+ } deriving (Show)
65+
66+
67+--------------------------------------------------------------------------------
68+instance Monoid CheckerWrite where
69+ mempty = CheckerWrite 0 0
70+ mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
71+ CheckerWrite (f1 + f2) (o1 + o2)
72+
73+
74+--------------------------------------------------------------------------------
75+type CheckerState = Set String
76+
77+
78+--------------------------------------------------------------------------------
79+type Checker a = RWST CheckerRead CheckerWrite CheckerState IO a
80+
81+
82+--------------------------------------------------------------------------------
83+runChecker :: Checker a -> Configuration -> Verbosity -> Check
84+ -> IO (a, CheckerWrite)
85+runChecker checker config verbosity check' = do
86+ logger <- Logger.new verbosity
87+ let read' = CheckerRead config logger check'
88+ (x, _, write) <- runRWST checker read' S.empty
89+ Logger.flush logger
90+ return (x, write)
91+
92+
93+--------------------------------------------------------------------------------
94+checkDestination :: Checker ()
95+checkDestination = do
96+ config <- checkerConfig <$> ask
97+ files <- liftIO $ getRecursiveContents (destinationDirectory config)
98+
99+ let htmls =
100+ [ destinationDirectory config </> file
101+ | file <- files
102+ , takeExtension file == ".html"
103+ ]
104+
105+ forM_ htmls checkFile
106+
107+
108+--------------------------------------------------------------------------------
109+checkFile :: FilePath -> Checker ()
110+checkFile filePath = do
111+ logger <- checkerLogger <$> ask
112+ contents <- liftIO $ readFile filePath
113+ Logger.header logger $ "Checking file " ++ filePath
114+
115+ let urls = getUrls $ TS.parseTags contents
116+ forM_ urls $ \url -> do
117+ Logger.debug logger $ "Checking link " ++ url
118+ if isExternal url
119+ then checkExternalUrl url
120+ else checkInternalUrl filePath url
121+
122+
123+--------------------------------------------------------------------------------
124+ok :: String -> Checker ()
125+ok _ = tell $ mempty {checkerOk = 1}
126+
127+
128+--------------------------------------------------------------------------------
129+faulty :: String -> Checker ()
130+faulty url = do
131+ logger <- checkerLogger <$> ask
132+ Logger.error logger $ "Broken link to " ++ show url
133+ tell $ mempty {checkerFaulty = 1}
134+
135+
136+--------------------------------------------------------------------------------
137+checkInternalUrl :: FilePath -> String -> Checker ()
138+checkInternalUrl base url = case url' of
139+ "" -> ok url
140+ _ -> do
141+ config <- checkerConfig <$> ask
142+ let dest = destinationDirectory config
143+ dir = takeDirectory base
144+ filePath
145+ | "/" `isPrefixOf` url' = dest ++ url'
146+ | otherwise = dir </> url'
147+
148+ exists <- checkFileExists filePath
149+ if exists then ok url else faulty url
150+ where
151+ url' = stripFragments url
152+
153+
154+--------------------------------------------------------------------------------
155+checkExternalUrl :: String -> Checker ()
156+checkExternalUrl url = do
157+ logger <- checkerLogger <$> ask
158+ needsCheck <- (== All) . checkerCheck <$> ask
159+ checked <- (url `S.member`) <$> get
160+
161+ if not needsCheck || checked
162+ then Logger.debug logger "Already checked, skipping"
163+ else do
164+ isOk <- liftIO $ handle (failure logger) $
165+ Http.withManager $ \mgr -> do
166+ request <- Http.parseUrl url
167+ response <- Http.http (settings request) mgr
168+ let code = Http.statusCode (Http.responseStatus response)
169+ return $ code >= 200 && code < 300
170+
171+ modify $ S.insert url
172+ if isOk then ok url else faulty url
173+ where
174+ settings r = r
175+ { Http.method = "HEAD"
176+ , Http.redirectCount = 10
177+ }
178+
179+ -- Catch all the things except UserInterrupt
180+ failure logger (SomeException e) = case cast e of
181+ Just UserInterrupt -> throw UserInterrupt
182+ _ -> Logger.error logger (show e) >> return False
183+
184+
185+--------------------------------------------------------------------------------
186+-- | Wraps doesFileExist, also checks for index.html
187+checkFileExists :: FilePath -> Checker Bool
188+checkFileExists filePath = liftIO $ do
189+ file <- doesFileExist filePath
190+ dir <- doesDirectoryExist filePath
191+ case (file, dir) of
192+ (True, _) -> return True
193+ (_, True) -> doesFileExist $ filePath </> "index.html"
194+ _ -> return False
195+
196+
197+--------------------------------------------------------------------------------
198+stripFragments :: String -> String
199+stripFragments = takeWhile (not . flip elem ['?', '#'])
src/Hakyll/Commands.hsnew file+129
@@ -0,0 +1,129 @@
1+--------------------------------------------------------------------------------
2+-- | Implementation of Hakyll commands: build, preview...
3+{-# LANGUAGE CPP #-}
4+module Hakyll.Commands
5+ ( build
6+ , check
7+ , clean
8+ , preview
9+ , rebuild
10+ , server
11+ , deploy
12+ ) where
13+
14+
15+--------------------------------------------------------------------------------
16+import System.Exit (ExitCode (ExitSuccess), exitWith)
17+import System.Process (system)
18+
19+
20+--------------------------------------------------------------------------------
21+import qualified Hakyll.Check as Check
22+import Hakyll.Core.Configuration
23+import Hakyll.Core.Logger (Verbosity)
24+import Hakyll.Core.Rules
25+import Hakyll.Core.Runtime
26+import Hakyll.Core.Util.File
27+
28+
29+--------------------------------------------------------------------------------
30+#ifdef PREVIEW_SERVER
31+import Control.Concurrent (forkIO)
32+import qualified Data.Set as S
33+import Hakyll.Core.Identifier
34+import Hakyll.Core.Rules.Internal
35+import Hakyll.Preview.Poll
36+import Hakyll.Preview.Server
37+#endif
38+
39+
40+--------------------------------------------------------------------------------
41+-- | Build the site
42+build :: Configuration -> Verbosity -> Rules a -> IO ()
43+build conf verbosity rules = do
44+ _ <- run conf verbosity rules
45+ return ()
46+
47+
48+--------------------------------------------------------------------------------
49+-- | Run the checker and exit
50+check :: Configuration -> Verbosity -> Check.Check -> IO ()
51+check config verbosity check' = Check.check config verbosity check' >>= exitWith
52+
53+
54+--------------------------------------------------------------------------------
55+-- | Remove the output directories
56+clean :: Configuration -> IO ()
57+clean conf = do
58+ remove $ destinationDirectory conf
59+ remove $ storeDirectory conf
60+ remove $ tmpDirectory conf
61+ where
62+ remove dir = do
63+ putStrLn $ "Removing " ++ dir ++ "..."
64+ removeDirectory dir
65+
66+
67+--------------------------------------------------------------------------------
68+-- | Preview the site
69+preview :: Configuration -> Verbosity -> Rules a -> Int -> IO ()
70+#ifdef PREVIEW_SERVER
71+preview conf verbosity rules port = do
72+ -- Run the server in a separate thread
73+ _ <- forkIO $ server conf port
74+ previewPoll conf update
75+ where
76+ update = do
77+ (exitCode, ruleSet) <- run conf verbosity rules
78+ case exitCode of
79+ ExitSuccess -> return $ map toFilePath $ S.toList $
80+ rulesResources ruleSet
81+ _ -> exitWith exitCode
82+#else
83+preview _ _ _ _ = previewServerDisabled
84+#endif
85+
86+
87+--------------------------------------------------------------------------------
88+-- | Rebuild the site
89+rebuild :: Configuration -> Verbosity -> Rules a -> IO ()
90+rebuild conf verbosity rules = do
91+ clean conf
92+ build conf verbosity rules
93+
94+
95+--------------------------------------------------------------------------------
96+-- | Start a server
97+server :: Configuration -> Int -> IO ()
98+#ifdef PREVIEW_SERVER
99+server conf port = do
100+ let destination = destinationDirectory conf
101+ staticServer destination preServeHook port
102+ where
103+ preServeHook _ = return ()
104+#else
105+server _ _ = previewServerDisabled
106+#endif
107+
108+
109+--------------------------------------------------------------------------------
110+-- | Upload the site
111+deploy :: Configuration -> IO ()
112+deploy conf = do
113+ _ <- system $ deployCommand conf
114+ return ()
115+
116+
117+--------------------------------------------------------------------------------
118+-- | Print a warning message about the preview serving not being enabled
119+#ifndef PREVIEW_SERVER
120+previewServerDisabled :: IO ()
121+previewServerDisabled =
122+ mapM_ putStrLn
123+ [ "PREVIEW SERVER"
124+ , ""
125+ , "The preview server is not enabled in the version of Hakyll. To"
126+ , "enable it, set the flag to True and recompile Hakyll."
127+ , "Alternatively, use an external tool to serve your site directory."
128+ ]
129+#endif
lib/Hakyll/Core/Compiler.hs → src/Hakyll/Core/Compiler.hsrenamed+12−72
@@ -10,7 +10,7 @@ module Hakyll.Core.Compiler
10 , getResourceBody
10 , getResourceBody
11 , getResourceString
11 , getResourceString
12 , getResourceLBS
12 , getResourceLBS
13− , getResourceFilePath
13+ , getResourceWith
14
14
15 , Internal.Snapshot
15 , Internal.Snapshot
16 , saveSnapshot
16 , saveSnapshot
@@ -24,16 +24,13 @@ module Hakyll.Core.Compiler
24 , cached
24 , cached
25 , unsafeCompiler
25 , unsafeCompiler
26 , debugCompiler
26 , debugCompiler
27− , noResult
28− , withErrorMessage
29 ) where
27 ) where
30
28
31
29
32--------------------------------------------------------------------------------
30--------------------------------------------------------------------------------
33−import Control.Monad (unless, when, (>=>))
31+import Control.Applicative ((<$>))
34import Data.Binary (Binary)
32import Data.Binary (Binary)
35import Data.ByteString.Lazy (ByteString)
33import Data.ByteString.Lazy (ByteString)
36−import qualified Data.List.NonEmpty as NonEmpty
37import Data.Typeable (Typeable)
34import Data.Typeable (Typeable)
38import System.Environment (getProgName)
35import System.Environment (getProgName)
39import System.FilePath (takeExtension)
36import System.FilePath (takeExtension)
@@ -42,7 +39,6 @@ import System.FilePath (takeExtension)
42--------------------------------------------------------------------------------
39--------------------------------------------------------------------------------
43import Hakyll.Core.Compiler.Internal
40import Hakyll.Core.Compiler.Internal
44import qualified Hakyll.Core.Compiler.Require as Internal
41import qualified Hakyll.Core.Compiler.Require as Internal
45−import Hakyll.Core.Dependencies
46import Hakyll.Core.Identifier
42import Hakyll.Core.Identifier
47import Hakyll.Core.Item
43import Hakyll.Core.Item
48import Hakyll.Core.Logger as Logger
44import Hakyll.Core.Logger as Logger
@@ -65,7 +61,6 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
65
61
66
62
67--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
68−-- | Create an item from the underlying identifier and a given value.
69makeItem :: a -> Compiler (Item a)
64makeItem :: a -> Compiler (Item a)
70makeItem x = do
65makeItem x = do
71 identifier <- getUnderlying
66 identifier <- getUnderlying
@@ -76,43 +71,28 @@ makeItem x = do
76-- | Get the route for a specified item
71-- | Get the route for a specified item
77getRoute :: Identifier -> Compiler (Maybe FilePath)
72getRoute :: Identifier -> Compiler (Maybe FilePath)
78getRoute identifier = do
73getRoute identifier = do
79− provider <- compilerProvider <$> compilerAsk
80− routes <- compilerRoutes <$> compilerAsk
74+ routes <- compilerRoutes <$> compilerAsk
81− -- Note that this makes us dependend on that identifier: when the metadata
75+ return $ runRoutes routes identifier
82− -- of that item changes, the route may change, hence we have to recompile
83− (mfp, um) <- compilerUnsafeIO $ runRoutes routes provider identifier
84− when um $ compilerTellDependencies [IdentifierDependency identifier]
85− return mfp
86
76
87
77
88--------------------------------------------------------------------------------
78--------------------------------------------------------------------------------
89−-- | Get the full contents of the matched source file as a string,
79+-- | Get the body of the underlying resource
90−-- but without metadata preamble, if there was one.
91getResourceBody :: Compiler (Item String)
80getResourceBody :: Compiler (Item String)
92getResourceBody = getResourceWith resourceBody
81getResourceBody = getResourceWith resourceBody
93
82
94
83
95--------------------------------------------------------------------------------
84--------------------------------------------------------------------------------
96−-- | Get the full contents of the matched source file as a string.
85+-- | Get the resource we are compiling as a string
97getResourceString :: Compiler (Item String)
86getResourceString :: Compiler (Item String)
98getResourceString = getResourceWith resourceString
87getResourceString = getResourceWith resourceString
99
88
100
89
101--------------------------------------------------------------------------------
90--------------------------------------------------------------------------------
102−-- | Get the full contents of the matched source file as a lazy bytestring.
91+-- | Get the resource we are compiling as a lazy bytestring
103getResourceLBS :: Compiler (Item ByteString)
92getResourceLBS :: Compiler (Item ByteString)
104getResourceLBS = getResourceWith resourceLBS
93getResourceLBS = getResourceWith resourceLBS
105
94
106
95
107−--------------------------------------------------------------------------------
108−-- | Get the file path of the resource we are compiling
109−getResourceFilePath :: Compiler FilePath
110−getResourceFilePath = do
111− provider <- compilerProvider <$> compilerAsk
112− id' <- compilerUnderlying <$> compilerAsk
113− return $ resourceFilePath provider id'
114−
115−
116--------------------------------------------------------------------------------
96--------------------------------------------------------------------------------
117-- | Overloadable function for 'getResourceString' and 'getResourceLBS'
97-- | Overloadable function for 'getResourceString' and 'getResourceLBS'
118getResourceWith :: (Provider -> Identifier -> IO a) -> Compiler (Item a)
98getResourceWith :: (Provider -> Identifier -> IO a) -> Compiler (Item a)
@@ -122,7 +102,7 @@ getResourceWith reader = do
122 let filePath = toFilePath id'
102 let filePath = toFilePath id'
123 if resourceExists provider id'
103 if resourceExists provider id'
124 then compilerUnsafeIO $ Item id' <$> reader provider id'
104 then compilerUnsafeIO $ Item id' <$> reader provider id'
125− else fail $ error' filePath
105+ else compilerThrow $ error' filePath
126 where
106 where
127 error' fp = "Hakyll.Core.Compiler.getResourceWith: resource " ++
107 error' fp = "Hakyll.Core.Compiler.getResourceWith: resource " ++
128 show fp ++ " not found"
108 show fp ++ " not found"
@@ -139,16 +119,10 @@ saveSnapshot snapshot item = do
139 compilerUnsafeIO $ do
119 compilerUnsafeIO $ do
140 Logger.debug logger $ "Storing snapshot: " ++ snapshot
120 Logger.debug logger $ "Storing snapshot: " ++ snapshot
141 Internal.saveSnapshot store snapshot item
121 Internal.saveSnapshot store snapshot item
142−
122+ return item
143− -- Signal that we saved the snapshot.
144− Compiler $ \_ -> return $ CompilerSnapshot snapshot (return item)
145
123
146
124
147--------------------------------------------------------------------------------
125--------------------------------------------------------------------------------
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.
152cached :: (Binary a, Typeable a)
126cached :: (Binary a, Typeable a)
153 => String
127 => String
154 -> Compiler a
128 -> Compiler a
@@ -157,11 +131,7 @@ cached name compiler = do
157 id' <- compilerUnderlying <$> compilerAsk
131 id' <- compilerUnderlying <$> compilerAsk
158 store <- compilerStore <$> compilerAsk
132 store <- compilerStore <$> compilerAsk
159 provider <- compilerProvider <$> compilerAsk
133 provider <- compilerProvider <$> compilerAsk
160−
161− -- Give a better error message when the resource is not there at all.
162− unless (resourceExists provider id') $ fail $ itDoesntEvenExist id'
163−
164− let modified = resourceModified provider id'
134+ modified <- compilerUnsafeIO $ resourceModified provider id'
165 if modified
135 if modified
166 then do
136 then do
167 x <- compiler
137 x <- compiler
@@ -172,50 +142,20 @@ cached name compiler = do
172 x <- compilerUnsafeIO $ Store.get store [name, show id']
142 x <- compilerUnsafeIO $ Store.get store [name, show id']
173 progName <- compilerUnsafeIO getProgName
143 progName <- compilerUnsafeIO getProgName
174 case x of Store.Found x' -> return x'
144 case x of Store.Found x' -> return x'
175− _ -> fail $ error' progName
145+ _ -> compilerThrow (error' progName)
176 where
146 where
177 error' progName =
147 error' progName =
178 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
148 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
179 "Try running: " ++ progName ++ " clean"
149 "Try running: " ++ progName ++ " clean"
180
150
181− itDoesntEvenExist id' =
182− "Hakyll.Core.Compiler.cached: You are trying to (perhaps " ++
183− "indirectly) use `cached` on a non-existing resource: there " ++
184− "is no file backing " ++ show id'
185−
186
151
187--------------------------------------------------------------------------------
152--------------------------------------------------------------------------------
188−-- | Run an IO computation without dependencies in a Compiler
189unsafeCompiler :: IO a -> Compiler a
153unsafeCompiler :: IO a -> Compiler a
190unsafeCompiler = compilerUnsafeIO
154unsafeCompiler = compilerUnsafeIO
191
155
192
156
193--------------------------------------------------------------------------------
157--------------------------------------------------------------------------------
194−-- | Fail so that it is treated as non-defined in an @\$if()\$@ branching
195−-- "Hakyll.Web.Template" macro, and alternative
196−-- 'Hakyll.Web.Template.Context.Context's are tried
197−--
198−-- @since 4.13.0
199−noResult :: String -> Compiler a
200−noResult = compilerNoResult . return
201−
202−
203−--------------------------------------------------------------------------------
204−-- | Prepend an error line to the error, if there is one. This allows you to
205−-- add helpful context to error messages.
206−--
207−-- @since 4.13.0
208−withErrorMessage :: String -> Compiler a -> Compiler a
209−withErrorMessage x = do
210− compilerTry >=> either (compilerResult . CompilerError . prepend) return
211− where
212− prepend (CompilationFailure es) = CompilationFailure (x `NonEmpty.cons` es)
213− prepend (CompilationNoResult es) = CompilationNoResult (x : es)
214−
215−
216−--------------------------------------------------------------------------------
217−-- | Compiler for debugging purposes.
158+-- | Compiler for debugging purposes
218−-- Passes a message to the debug logger that is printed in verbose mode.
219debugCompiler :: String -> Compiler ()
159debugCompiler :: String -> Compiler ()
220debugCompiler msg = do
160debugCompiler msg = do
221 logger <- compilerLogger <$> compilerAsk
161 logger <- compilerLogger <$> compilerAsk
src/Hakyll/Core/Compiler/Internal.hsnew file+232
@@ -0,0 +1,232 @@
1+--------------------------------------------------------------------------------
2+-- | Internally used compiler module
3+{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+module Hakyll.Core.Compiler.Internal
6+ ( -- * Types
7+ CompilerRead (..)
8+ , CompilerWrite (..)
9+ , CompilerResult (..)
10+ , Compiler (..)
11+ , runCompiler
12+
13+ -- * Core operations
14+ , compilerTell
15+ , compilerAsk
16+ , compilerThrow
17+ , compilerCatch
18+ , compilerResult
19+ , compilerUnsafeIO
20+
21+ -- * Utilities
22+ , compilerTellDependencies
23+ , compilerTellCacheHits
24+ ) where
25+
26+
27+--------------------------------------------------------------------------------
28+import Control.Applicative (Alternative (..),
29+ Applicative (..), (<$>))
30+import Control.Exception (SomeException, handle)
31+import Data.Monoid (Monoid (..))
32+import Data.Set (Set)
33+import qualified Data.Set as S
34+
35+
36+--------------------------------------------------------------------------------
37+import Hakyll.Core.Configuration
38+import Hakyll.Core.Dependencies
39+import Hakyll.Core.Identifier
40+import Hakyll.Core.Identifier.Pattern
41+import Hakyll.Core.Logger
42+import Hakyll.Core.Metadata
43+import Hakyll.Core.Provider
44+import Hakyll.Core.Routes
45+import Hakyll.Core.Store
46+
47+
48+--------------------------------------------------------------------------------
49+-- | Environment in which a compiler runs
50+data CompilerRead = CompilerRead
51+ { -- | Main configuration
52+ compilerConfig :: Configuration
53+ , -- | Underlying identifier
54+ compilerUnderlying :: Identifier
55+ , -- | Resource provider
56+ compilerProvider :: Provider
57+ , -- | List of all known identifiers
58+ compilerUniverse :: Set Identifier
59+ , -- | Site routes
60+ compilerRoutes :: Routes
61+ , -- | Compiler store
62+ compilerStore :: Store
63+ , -- | Logger
64+ compilerLogger :: Logger
65+ }
66+
67+
68+--------------------------------------------------------------------------------
69+data CompilerWrite = CompilerWrite
70+ { compilerDependencies :: [Dependency]
71+ , compilerCacheHits :: Int
72+ } deriving (Show)
73+
74+
75+--------------------------------------------------------------------------------
76+instance Monoid CompilerWrite where
77+ mempty = CompilerWrite [] 0
78+ mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
79+ CompilerWrite (d1 ++ d2) (h1 + h2)
80+
81+
82+--------------------------------------------------------------------------------
83+data CompilerResult a where
84+ CompilerDone :: a -> CompilerWrite -> CompilerResult a
85+ CompilerError :: String -> CompilerResult a
86+ CompilerRequire :: Identifier -> Compiler a -> CompilerResult a
87+
88+
89+--------------------------------------------------------------------------------
90+-- | A monad which lets you compile items and takes care of dependency tracking
91+-- for you.
92+newtype Compiler a = Compiler
93+ { unCompiler :: CompilerRead -> IO (CompilerResult a)
94+ }
95+
96+
97+--------------------------------------------------------------------------------
98+instance Functor Compiler where
99+ fmap f (Compiler c) = Compiler $ \r -> do
100+ res <- c r
101+ return $ case res of
102+ CompilerDone x w -> CompilerDone (f x) w
103+ CompilerError e -> CompilerError e
104+ CompilerRequire i c' -> CompilerRequire i (fmap f c')
105+ {-# INLINE fmap #-}
106+
107+
108+--------------------------------------------------------------------------------
109+instance Monad Compiler where
110+ return x = Compiler $ \_ -> return $ CompilerDone x mempty
111+ {-# INLINE return #-}
112+
113+ Compiler c >>= f = Compiler $ \r -> do
114+ res <- c r
115+ case res of
116+ CompilerDone x w -> do
117+ res' <- unCompiler (f x) r
118+ return $ case res' of
119+ CompilerDone y w' -> CompilerDone y (w `mappend` w')
120+ CompilerError e -> CompilerError e
121+ CompilerRequire i c' -> CompilerRequire i $ do
122+ compilerTell w -- Save dependencies!
123+ c'
124+
125+ CompilerError e -> return $ CompilerError e
126+ CompilerRequire i c' -> return $ CompilerRequire i $ c' >>= f
127+ {-# INLINE (>>=) #-}
128+
129+ fail = compilerThrow
130+ {-# INLINE fail #-}
131+
132+
133+--------------------------------------------------------------------------------
134+instance Applicative Compiler where
135+ pure x = return x
136+ {-# INLINE pure #-}
137+
138+ f <*> x = f >>= \f' -> fmap f' x
139+ {-# INLINE (<*>) #-}
140+
141+
142+--------------------------------------------------------------------------------
143+instance MonadMetadata Compiler where
144+ getMetadata = compilerGetMetadata
145+ getMatches = compilerGetMatches
146+
147+
148+--------------------------------------------------------------------------------
149+runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
150+runCompiler compiler read' = handle handler $ unCompiler compiler read'
151+ where
152+ handler :: SomeException -> IO (CompilerResult a)
153+ handler e = return $ CompilerError $ show e
154+
155+
156+--------------------------------------------------------------------------------
157+instance Alternative Compiler where
158+ empty = compilerThrow "Hakyll.Core.Compiler.Internal: empty alternative"
159+ x <|> y = compilerCatch x (\_ -> y)
160+ {-# INLINE (<|>) #-}
161+
162+
163+--------------------------------------------------------------------------------
164+compilerAsk :: Compiler CompilerRead
165+compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
166+{-# INLINE compilerAsk #-}
167+
168+
169+--------------------------------------------------------------------------------
170+compilerTell :: CompilerWrite -> Compiler ()
171+compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
172+{-# INLINE compilerTell #-}
173+
174+
175+--------------------------------------------------------------------------------
176+compilerThrow :: String -> Compiler a
177+compilerThrow e = Compiler $ \_ -> return $ CompilerError e
178+{-# INLINE compilerThrow #-}
179+
180+
181+--------------------------------------------------------------------------------
182+compilerCatch :: Compiler a -> (String -> Compiler a) -> Compiler a
183+compilerCatch (Compiler x) f = Compiler $ \r -> do
184+ res <- x r
185+ case res of
186+ CompilerError e -> unCompiler (f e) r
187+ _ -> return res
188+{-# INLINE compilerCatch #-}
189+
190+
191+--------------------------------------------------------------------------------
192+-- | Put the result back in a compiler
193+compilerResult :: CompilerResult a -> Compiler a
194+compilerResult x = Compiler $ \_ -> return x
195+{-# INLINE compilerResult #-}
196+
197+
198+--------------------------------------------------------------------------------
199+compilerUnsafeIO :: IO a -> Compiler a
200+compilerUnsafeIO io = Compiler $ \_ -> do
201+ x <- io
202+ return $ CompilerDone x mempty
203+{-# INLINE compilerUnsafeIO #-}
204+
205+
206+--------------------------------------------------------------------------------
207+compilerTellDependencies :: [Dependency] -> Compiler ()
208+compilerTellDependencies ds = compilerTell mempty {compilerDependencies = ds}
209+{-# INLINE compilerTellDependencies #-}
210+
211+
212+--------------------------------------------------------------------------------
213+compilerTellCacheHits :: Int -> Compiler ()
214+compilerTellCacheHits ch = compilerTell mempty {compilerCacheHits = ch}
215+{-# INLINE compilerTellCacheHits #-}
216+
217+
218+--------------------------------------------------------------------------------
219+compilerGetMetadata :: Identifier -> Compiler Metadata
220+compilerGetMetadata identifier = do
221+ provider <- compilerProvider <$> compilerAsk
222+ compilerTellDependencies [IdentifierDependency identifier]
223+ compilerUnsafeIO $ resourceMetadata provider identifier
224+
225+
226+--------------------------------------------------------------------------------
227+compilerGetMatches :: Pattern -> Compiler [Identifier]
228+compilerGetMatches pattern = do
229+ universe <- compilerUniverse <$> compilerAsk
230+ let matching = filterMatches pattern $ S.toList universe
231+ compilerTellDependencies [PatternDependency pattern matching]
232+ return matching
lib/Hakyll/Core/Compiler/Require.hs → src/Hakyll/Core/Compiler/Require.hsrenamed+11−6
@@ -13,6 +13,7 @@ module Hakyll.Core.Compiler.Require
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
16+import Control.Applicative ((<$>))
16import Control.Monad (when)
17import Control.Monad (when)
17import Data.Binary (Binary)
18import Data.Binary (Binary)
18import qualified Data.Set as S
19import qualified Data.Set as S
@@ -30,6 +31,12 @@ import Hakyll.Core.Store (Store)
30import qualified Hakyll.Core.Store as Store
31import qualified Hakyll.Core.Store as Store
31
32
32
33
34+--------------------------------------------------------------------------------
35+-- | Whilst compiling an item, it possible to save multiple snapshots of it, and
36+-- not just the final result.
37+type Snapshot = String
38+
39+
33--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
34save :: (Binary a, Typeable a) => Store -> Item a -> IO ()
41save :: (Binary a, Typeable a) => Store -> Item a -> IO ()
35save store item = saveSnapshot store final item
42save store item = saveSnapshot store final item
@@ -60,14 +67,14 @@ loadSnapshot id' snapshot = do
60 universe <- compilerUniverse <$> compilerAsk
67 universe <- compilerUniverse <$> compilerAsk
61
68
62 -- Quick check for better error messages
69 -- Quick check for better error messages
63− when (id' `S.notMember` universe) $ fail notFound
70+ when (id' `S.notMember` universe) $ compilerThrow notFound
64
71
65 compilerTellDependencies [IdentifierDependency id']
72 compilerTellDependencies [IdentifierDependency id']
66− compilerResult $ CompilerRequire (id', snapshot) $ do
73+ compilerResult $ CompilerRequire id' $ do
67 result <- compilerUnsafeIO $ Store.get store (key id' snapshot)
74 result <- compilerUnsafeIO $ Store.get store (key id' snapshot)
68 case result of
75 case result of
69− Store.NotFound -> fail notFound
76+ Store.NotFound -> compilerThrow notFound
70− Store.WrongType e r -> fail $ wrongType e r
77+ Store.WrongType e r -> compilerThrow $ wrongType e r
71 Store.Found x -> return $ Item id' x
78 Store.Found x -> return $ Item id' x
72 where
79 where
73 notFound =
80 notFound =
@@ -91,7 +98,6 @@ loadBody id' = loadSnapshotBody id' final
91
98
92
99
93--------------------------------------------------------------------------------
100--------------------------------------------------------------------------------
94−-- | A shortcut for only requiring the body for a specific snapshot of an item
95loadSnapshotBody :: (Binary a, Typeable a)
101loadSnapshotBody :: (Binary a, Typeable a)
96 => Identifier -> Snapshot -> Compiler a
102 => Identifier -> Snapshot -> Compiler a
97loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
103loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -104,7 +110,6 @@ loadAll pattern = loadAllSnapshots pattern final
104
110
105
111
106--------------------------------------------------------------------------------
112--------------------------------------------------------------------------------
107−-- | Load a specific snapshot for each of dynamic list of items
108loadAllSnapshots :: (Binary a, Typeable a)
113loadAllSnapshots :: (Binary a, Typeable a)
109 => Pattern -> Snapshot -> Compiler [Item a]
114 => Pattern -> Snapshot -> Compiler [Item a]
110loadAllSnapshots pattern snapshot = do
115loadAllSnapshots pattern snapshot = do
lib/Hakyll/Core/Configuration.hs → src/Hakyll/Core/Configuration.hsrenamed+11−53
@@ -8,13 +8,8 @@ module Hakyll.Core.Configuration
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import Data.Default (Default (..))
12−import Data.List (isPrefixOf, isSuffixOf)
11+import Data.List (isPrefixOf, isSuffixOf)
13−import System.Directory (canonicalizePath)
14−import System.Exit (ExitCode)
15−import System.FilePath (isAbsolute, normalise, takeFileName)
12+import System.FilePath (normalise, takeFileName)
16−import System.IO.Error (catchIOError)
17−import System.Process (system)
18
13
19
14
20--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
@@ -42,7 +37,7 @@ data Configuration = Configuration
42 --
37 --
43 -- Note that the files in 'destinationDirectory' and 'storeDirectory' will
38 -- Note that the files in 'destinationDirectory' and 'storeDirectory' will
44 -- also be ignored. Note that this is the configuration parameter, if you
39 -- also be ignored. Note that this is the configuration parameter, if you
45− -- want to use the test, you should use 'shouldIgnoreFile'.
40+ -- want to use the test, you should use 'shouldIgnoreFile'.
46 --
41 --
47 ignoreFile :: FilePath -> Bool
42 ignoreFile :: FilePath -> Bool
48 , -- | Here, you can plug in a system command to upload/deploy your site.
43 , -- | Here, you can plug in a system command to upload/deploy your site.
@@ -56,33 +51,11 @@ data Configuration = Configuration
56 -- > ./site deploy
51 -- > ./site deploy
57 --
52 --
58 deployCommand :: String
53 deployCommand :: String
59− , -- | Function to deploy the site from Haskell.
60− --
61− -- By default, this command executes the shell command stored in
62− -- 'deployCommand'. If you override it, 'deployCommand' will not
63− -- be used implicitely.
64− --
65− -- The 'Configuration' object is passed as a parameter to this
66− -- function.
67− --
68− deploySite :: Configuration -> IO ExitCode
69 , -- | Use an in-memory cache for items. This is faster but uses more
54 , -- | Use an in-memory cache for items. This is faster but uses more
70 -- memory.
55 -- memory.
71 inMemoryCache :: Bool
56 inMemoryCache :: Bool
72− , -- | Override default host for preview server. Default is "127.0.0.1",
73− -- which binds only on the loopback address.
74− -- One can also override the host as a command line argument:
75− -- ./site preview -h "0.0.0.0"
76− previewHost :: String
77− , -- | Override default port for preview server. Default is 8000.
78− -- One can also override the port as a command line argument:
79− -- ./site preview -p 1234
80− previewPort :: Int
81 }
57 }
82
58
83−--------------------------------------------------------------------------------
84−instance Default Configuration where
85− def = defaultConfiguration
86
59
87--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
88-- | Default configuration for a hakyll application
61-- | Default configuration for a hakyll application
@@ -93,11 +66,8 @@ defaultConfiguration = Configuration
93 , tmpDirectory = "_cache/tmp"
66 , tmpDirectory = "_cache/tmp"
94 , providerDirectory = "."
67 , providerDirectory = "."
95 , ignoreFile = ignoreFile'
68 , ignoreFile = ignoreFile'
96− , deployCommand = "echo 'No deploy command specified' && exit 1"
69+ , deployCommand = "echo 'No deploy command specified'"
97− , deploySite = system . deployCommand
98 , inMemoryCache = True
70 , inMemoryCache = True
99− , previewHost = "127.0.0.1"
100− , previewPort = 8000
101 }
71 }
102 where
72 where
103 ignoreFile' path
73 ignoreFile' path
@@ -112,23 +82,11 @@ defaultConfiguration = Configuration
112
82
113--------------------------------------------------------------------------------
83--------------------------------------------------------------------------------
114-- | Check if a file should be ignored
84-- | Check if a file should be ignored
115−shouldIgnoreFile :: Configuration -> FilePath -> IO Bool
85+shouldIgnoreFile :: Configuration -> FilePath -> Bool
116−shouldIgnoreFile conf path = orM
86+shouldIgnoreFile conf path =
117− [ inDir (destinationDirectory conf)
87+ destinationDirectory conf `isPrefixOf` path' ||
118− , inDir (storeDirectory conf)
88+ storeDirectory conf `isPrefixOf` path' ||
119− , inDir (tmpDirectory conf)
89+ tmpDirectory conf `isPrefixOf` path' ||
120− , return (ignoreFile conf path')
90+ ignoreFile conf path'
121− ]
122 where
91 where
123− path' = normalise path
92+ path' = normalise path
124− absolute = isAbsolute path
125−
126− inDir dir
127− | absolute = do
128− dir' <- catchIOError (canonicalizePath dir) (const $ return dir)
129− return $ dir' `isPrefixOf` path'
130− | otherwise = return $ dir `isPrefixOf` path'
131−
132− orM :: [IO Bool] -> IO Bool
133− orM [] = return False
134− orM (x : xs) = x >>= \b -> if b then return True else orM xs
lib/Hakyll/Core/Dependencies.hs → src/Hakyll/Core/Dependencies.hsrenamed+4−3
@@ -8,6 +8,7 @@ module Hakyll.Core.Dependencies
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>), (<*>))
11import Control.Monad (foldM, forM_, unless, when)
12import Control.Monad (foldM, forM_, unless, when)
12import Control.Monad.Reader (ask)
13import Control.Monad.Reader (ask)
13import Control.Monad.RWS (RWS, runRWS)
14import Control.Monad.RWS (RWS, runRWS)
@@ -31,7 +32,7 @@ import Hakyll.Core.Identifier.Pattern
31
32
32--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
33data Dependency
34data Dependency
34− = PatternDependency Pattern (Set Identifier)
35+ = PatternDependency Pattern [Identifier]
35 | IdentifierDependency Identifier
36 | IdentifierDependency Identifier
36 deriving (Show, Typeable)
37 deriving (Show, Typeable)
37
38
@@ -90,7 +91,7 @@ dependenciesFor id' = do
90 return $ concatMap dependenciesFor' $ fromMaybe [] $ M.lookup id' facts
91 return $ concatMap dependenciesFor' $ fromMaybe [] $ M.lookup id' facts
91 where
92 where
92 dependenciesFor' (IdentifierDependency i) = [i]
93 dependenciesFor' (IdentifierDependency i) = [i]
93− dependenciesFor' (PatternDependency _ is) = S.toList is
94+ dependenciesFor' (PatternDependency _ is) = is
94
95
95
96
96--------------------------------------------------------------------------------
97--------------------------------------------------------------------------------
@@ -115,7 +116,7 @@ checkChangedPatterns = do
115 go _ ds (IdentifierDependency i) = return $ IdentifierDependency i : ds
116 go _ ds (IdentifierDependency i) = return $ IdentifierDependency i : ds
116 go id' ds (PatternDependency p ls) = do
117 go id' ds (PatternDependency p ls) = do
117 universe <- ask
118 universe <- ask
118− let ls' = S.fromList $ filterMatches p universe
119+ let ls' = filterMatches p universe
119 if ls == ls'
120 if ls == ls'
120 then return $ PatternDependency p ls : ds
121 then return $ PatternDependency p ls : ds
121 else do
122 else do
lib/Hakyll/Core/File.hs → src/Hakyll/Core/File.hsrenamed+15−19
@@ -1,6 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Exports simple compilers to just copy files
2-- | Exports simple compilers to just copy files
3−{-# LANGUAGE CPP #-}
4{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6module Hakyll.Core.File
5module Hakyll.Core.File
@@ -12,14 +11,10 @@ module Hakyll.Core.File
12
11
13
12
14--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
14+import Control.Applicative ((<$>))
15import Data.Binary (Binary (..))
15import Data.Binary (Binary (..))
16import Data.Typeable (Typeable)
16import Data.Typeable (Typeable)
17−#if MIN_VERSION_directory(1,2,6)
18−import System.Directory (copyFileWithMetadata)
19−#else
20−import System.Directory (copyFile)
17+import System.Directory (copyFile, doesFileExist,
21−#endif
22−import System.Directory (doesFileExist,
23 renameFile)
18 renameFile)
24import System.FilePath ((</>))
19import System.FilePath ((</>))
25import System.Random (randomIO)
20import System.Random (randomIO)
@@ -29,8 +24,8 @@ import System.Random (randomIO)
29import Hakyll.Core.Compiler
24import Hakyll.Core.Compiler
30import Hakyll.Core.Compiler.Internal
25import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Configuration
26import Hakyll.Core.Configuration
27+import Hakyll.Core.Identifier
32import Hakyll.Core.Item
28import Hakyll.Core.Item
33−import Hakyll.Core.Provider
34import qualified Hakyll.Core.Store as Store
29import qualified Hakyll.Core.Store as Store
35import Hakyll.Core.Util.File
30import Hakyll.Core.Util.File
36import Hakyll.Core.Writable
31import Hakyll.Core.Writable
@@ -38,23 +33,24 @@ import Hakyll.Core.Writable
38
33
39--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
40-- | This will copy any file directly by using a system call
35-- | This will copy any file directly by using a system call
41−newtype CopyFile = CopyFile FilePath
36+data CopyFile = CopyFile
42− deriving (Binary, Eq, Ord, Show, Typeable)
37+ deriving (Show, Eq, Ord, Typeable)
38+
39+
40+--------------------------------------------------------------------------------
41+instance Binary CopyFile where
42+ put CopyFile = return ()
43+ get = return CopyFile
43
44
44
45
45--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
46instance Writable CopyFile where
47instance Writable CopyFile where
47−#if MIN_VERSION_directory(1,2,6)
48+ write dst item = copyFile (toFilePath $ itemIdentifier item) dst
48− write dst (Item _ (CopyFile src)) = copyFileWithMetadata src dst
49+
49−#else
50+
50− write dst (Item _ (CopyFile src)) = copyFile src dst
51−#endif
52--------------------------------------------------------------------------------
51--------------------------------------------------------------------------------
53copyFileCompiler :: Compiler (Item CopyFile)
52copyFileCompiler :: Compiler (Item CopyFile)
54−copyFileCompiler = do
53+copyFileCompiler = makeItem CopyFile
55− identifier <- getUnderlying
56− provider <- compilerProvider <$> compilerAsk
57− makeItem $ CopyFile $ resourceFilePath provider identifier
58
54
59
55
60--------------------------------------------------------------------------------
56--------------------------------------------------------------------------------
lib/Hakyll/Core/Identifier.hs → src/Hakyll/Core/Identifier.hsrenamed+2−1
@@ -19,6 +19,7 @@ module Hakyll.Core.Identifier
19
19
20
20
21--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
22+import Control.Applicative ((<$>), (<*>))
22import Control.DeepSeq (NFData (..))
23import Control.DeepSeq (NFData (..))
23import Data.List (intercalate)
24import Data.List (intercalate)
24import System.FilePath (dropTrailingPathSeparator, splitPath)
25import System.FilePath (dropTrailingPathSeparator, splitPath)
@@ -62,7 +63,7 @@ instance Show Identifier where
62
63
63--------------------------------------------------------------------------------
64--------------------------------------------------------------------------------
64-- | Parse an identifier from a string
65-- | Parse an identifier from a string
65−fromFilePath :: FilePath -> Identifier
66+fromFilePath :: String -> Identifier
66fromFilePath = Identifier Nothing .
67fromFilePath = Identifier Nothing .
67 intercalate "/" . filter (not . null) . split'
68 intercalate "/" . filter (not . null) . split'
68 where
69 where
lib/Hakyll/Core/Identifier/Pattern.hs → src/Hakyll/Core/Identifier/Pattern.hsrenamed+75−15
@@ -27,8 +27,7 @@
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 a glob or regex pattern.
30+-- by the capture elements in the pattern.
31−{-# OPTIONS_GHC -fno-warn-orphans #-}
32module Hakyll.Core.Identifier.Pattern
31module Hakyll.Core.Identifier.Pattern
33 ( -- * The pattern type
32 ( -- * The pattern type
34 Pattern
33 Pattern
@@ -58,22 +57,79 @@ module Hakyll.Core.Identifier.Pattern
58
57
59
58
60--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
61−import Control.Arrow ((&&&), (>>>))
60+import Control.Applicative (pure, (<$>), (<*>))
61+import Control.Arrow ((&&&), (>>>))
62−import Control.Monad (msum)
62+import Control.Monad (msum)
63+import Data.Binary (Binary (..), getWord8, putWord8)
63−import Data.List (inits, isPrefixOf,
64+import Data.List (inits, isPrefixOf, tails)
64− tails)
65−import Data.Maybe (isJust)
65+import Data.Maybe (isJust)
66+import Data.Monoid (Monoid, mappend, mempty)
67+import Data.Set (Set)
66−import qualified Data.Set as S
68+import qualified Data.Set as S
67
69
68
70
69--------------------------------------------------------------------------------
71--------------------------------------------------------------------------------
70−import GHC.Exts (IsString, fromString)
72+import GHC.Exts (IsString, fromString)
71−import Text.Regex.TDFA ((=~))
73+import Text.Regex.TDFA ((=~))
72
74
73
75
74--------------------------------------------------------------------------------
76--------------------------------------------------------------------------------
75import Hakyll.Core.Identifier
77import Hakyll.Core.Identifier
76−import Hakyll.Core.Identifier.Pattern.Internal
78+
79+
80+--------------------------------------------------------------------------------
81+-- | Elements of a glob pattern
82+data GlobComponent
83+ = Capture
84+ | CaptureMany
85+ | Literal String
86+ deriving (Eq, Show)
87+
88+
89+--------------------------------------------------------------------------------
90+instance Binary GlobComponent where
91+ put Capture = putWord8 0
92+ put CaptureMany = putWord8 1
93+ put (Literal s) = putWord8 2 >> put s
94+
95+ get = getWord8 >>= \t -> case t of
96+ 0 -> pure Capture
97+ 1 -> pure CaptureMany
98+ 2 -> Literal <$> get
99+ _ -> error "Data.Binary.get: Invalid GlobComponent"
100+
101+
102+--------------------------------------------------------------------------------
103+-- | Type that allows matching on identifiers
104+data Pattern
105+ = Everything
106+ | Complement Pattern
107+ | And Pattern Pattern
108+ | Glob [GlobComponent]
109+ | List (Set Identifier)
110+ | Regex String
111+ | Version (Maybe String)
112+ deriving (Show)
113+
114+
115+--------------------------------------------------------------------------------
116+instance Binary Pattern where
117+ put Everything = putWord8 0
118+ put (Complement p) = putWord8 1 >> put p
119+ put (And x y) = putWord8 2 >> put x >> put y
120+ put (Glob g) = putWord8 3 >> put g
121+ put (List is) = putWord8 4 >> put is
122+ put (Regex r) = putWord8 5 >> put r
123+ put (Version v) = putWord8 6 >> put v
124+
125+ get = getWord8 >>= \t -> case t of
126+ 0 -> pure Everything
127+ 1 -> Complement <$> get
128+ 2 -> And <$> get <*> get
129+ 3 -> Glob <$> get
130+ 4 -> List <$> get
131+ 5 -> Regex <$> get
132+ _ -> Version <$> get
77
133
78
134
79--------------------------------------------------------------------------------
135--------------------------------------------------------------------------------
@@ -81,6 +137,12 @@ instance IsString Pattern where
81 fromString = fromGlob
137 fromString = fromGlob
82
138
83
139
140+--------------------------------------------------------------------------------
141+instance Monoid Pattern where
142+ mempty = Everything
143+ mappend = (.&&.)
144+
145+
84--------------------------------------------------------------------------------
146--------------------------------------------------------------------------------
85-- | Parse a pattern from a string
147-- | Parse a pattern from a string
86fromGlob :: String -> Pattern
148fromGlob :: String -> Pattern
@@ -200,11 +262,9 @@ splits = inits &&& tails >>> uncurry zip >>> reverse
200
262
201
263
202--------------------------------------------------------------------------------
264--------------------------------------------------------------------------------
203−-- | Match a glob or regex pattern against an identifier, generating a list of captures
265+-- | Match a glob against a pattern, generating a list of captures
204capture :: Pattern -> Identifier -> Maybe [String]
266capture :: Pattern -> Identifier -> Maybe [String]
205capture (Glob p) i = capture' p (toFilePath i)
267capture (Glob p) i = capture' p (toFilePath i)
206−capture (Regex pat) i = Just groups
207− where (_, _, _, groups) = ((toFilePath i) =~ pat) :: (String, String, String, [String])
208capture _ _ = Nothing
268capture _ _ = Nothing
209
269
210
270
@@ -232,7 +292,7 @@ capture' (CaptureMany : ms) str =
232--
292--
233-- Example:
293-- Example:
234--
294--
235−-- > fromCapture (fromGlob "tags/*") "foo"
295+-- > fromCapture (parseGlob "tags/*") "foo"
236--
296--
237-- Result:
297-- Result:
238--
298--
lib/Hakyll/Core/Item.hs → src/Hakyll/Core/Item.hsrenamed+2
@@ -10,8 +10,10 @@ module Hakyll.Core.Item
10
10
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
13+import Control.Applicative (Applicative, (<$>), (<*>))
13import Data.Binary (Binary (..))
14import Data.Binary (Binary (..))
14import Data.Foldable (Foldable (..))
15import Data.Foldable (Foldable (..))
16+import Data.Traversable (Traversable (..))
15import Data.Typeable (Typeable)
17import Data.Typeable (Typeable)
16import Prelude hiding (foldr)
18import Prelude hiding (foldr)
17
19
lib/Hakyll/Core/Item/SomeItem.hs → src/Hakyll/Core/Item/SomeItem.hsrenamed

The content is the same.

lib/Hakyll/Core/Logger.hs → src/Hakyll/Core/Logger.hsrenamed+4−9
@@ -13,12 +13,12 @@ module Hakyll.Core.Logger
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
16+import Control.Applicative (pure, (<$>), (<*>))
16import Control.Concurrent (forkIO)
17import Control.Concurrent (forkIO)
17import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
18import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
18import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
19import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
19import Control.Monad (forever)
20import Control.Monad (forever)
20import Control.Monad.Trans (MonadIO, liftIO)
21import 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] " ++ indent m
83+error l m = string l Error $ " [ERROR] " ++ m
84
84
85
85
86--------------------------------------------------------------------------------
86--------------------------------------------------------------------------------
@@ -90,14 +90,9 @@ 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 $ " " ++ indent m
93+message l m = string l Message $ " " ++ 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] " ++ indent m
98+debug l m = string l Debug $ " [DEBUG] " ++ m
99−
100−
101−--------------------------------------------------------------------------------
102−indent :: String -> String
103−indent = intercalate "\n " . lines
src/Hakyll/Core/Metadata.hsnew file+32
@@ -0,0 +1,32 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Metadata
3+ ( Metadata
4+ , MonadMetadata (..)
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Control.Monad (forM)
10+import Data.Map (Map)
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Core.Identifier
15+import Hakyll.Core.Identifier.Pattern
16+
17+
18+--------------------------------------------------------------------------------
19+type Metadata = Map String String
20+
21+
22+--------------------------------------------------------------------------------
23+class Monad m => MonadMetadata m where
24+ getMetadata :: Identifier -> m Metadata
25+ getMatches :: Pattern -> m [Identifier]
26+
27+ getAllMetadata :: Pattern -> m [(Identifier, Metadata)]
28+ getAllMetadata pattern = do
29+ matches' <- getMatches pattern
30+ forM matches' $ \id' -> do
31+ metadata <- getMetadata id'
32+ return (id', metadata)
src/Hakyll/Core/Provider.hsnew file+46
@@ -0,0 +1,46 @@
1+--------------------------------------------------------------------------------
2+-- | This module provides an wrapper API around the file system which does some
3+-- caching.
4+module Hakyll.Core.Provider
5+ ( -- * Constructing resource providers
6+ Provider
7+ , newProvider
8+
9+ -- * Querying resource properties
10+ , resourceList
11+ , resourceExists
12+ , resourceModified
13+ , resourceModificationTime
14+
15+ -- * Access to raw resource content
16+ , resourceString
17+ , resourceLBS
18+
19+ -- * Access to metadata and body content
20+ , resourceMetadata
21+ , resourceBody
22+ ) where
23+
24+
25+--------------------------------------------------------------------------------
26+import Hakyll.Core.Identifier
27+import Hakyll.Core.Metadata
28+import Hakyll.Core.Provider.Internal
29+import qualified Hakyll.Core.Provider.MetadataCache as Internal
30+import Hakyll.Core.Provider.Modified
31+
32+
33+--------------------------------------------------------------------------------
34+-- | Wrapper to ensure metadata cache is invalidated if necessary
35+resourceMetadata :: Provider -> Identifier -> IO Metadata
36+resourceMetadata rp r = do
37+ _ <- resourceModified rp r
38+ Internal.resourceMetadata rp r
39+
40+
41+--------------------------------------------------------------------------------
42+-- | Wrapper to ensure metadata cache is invalidated if necessary
43+resourceBody :: Provider -> Identifier -> IO String
44+resourceBody rp r = do
45+ _ <- resourceModified rp r
46+ Internal.resourceBody rp r
src/Hakyll/Core/Provider/Internal.hsnew file+94
@@ -0,0 +1,94 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Provider.Internal
3+ ( Provider (..)
4+ , newProvider
5+
6+ , resourceList
7+ , resourceExists
8+ , resourceMetadataResource
9+
10+ , resourceFilePath
11+ , resourceString
12+ , resourceLBS
13+ ) where
14+
15+
16+--------------------------------------------------------------------------------
17+import Control.Applicative ((<$>))
18+import qualified Data.ByteString.Lazy as BL
19+import Data.IORef
20+import Data.Map (Map)
21+import qualified Data.Map as M
22+import Data.Set (Set)
23+import qualified Data.Set as S
24+import System.FilePath (addExtension, (</>))
25+
26+
27+--------------------------------------------------------------------------------
28+import Hakyll.Core.Identifier
29+import Hakyll.Core.Store
30+import Hakyll.Core.Util.File
31+
32+
33+--------------------------------------------------------------------------------
34+-- | Responsible for retrieving and listing resources
35+data Provider = Provider
36+ { -- Top of the provided directory
37+ providerDirectory :: FilePath
38+ , -- | A list of all files found
39+ providerSet :: Set Identifier
40+ , -- | Cache keeping track of modified files
41+ providerModifiedCache :: IORef (Map Identifier Bool)
42+ , -- | Underlying persistent store for caching
43+ providerStore :: Store
44+ }
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Create a resource provider
49+newProvider :: Store -- ^ Store to use
50+ -> (FilePath -> Bool) -- ^ Should we ignore this file?
51+ -> FilePath -- ^ Search directory
52+ -> IO Provider -- ^ Resulting provider
53+newProvider store ignore directory = do
54+ list <- map fromFilePath . filter (not . ignore) <$>
55+ getRecursiveContents directory
56+ cache <- newIORef M.empty
57+ return $ Provider directory (S.fromList list) cache store
58+
59+
60+--------------------------------------------------------------------------------
61+resourceList :: Provider -> [Identifier]
62+resourceList = S.toList . providerSet
63+
64+
65+--------------------------------------------------------------------------------
66+-- | Check if a given resource exists
67+resourceExists :: Provider -> Identifier -> Bool
68+resourceExists provider =
69+ (`S.member` providerSet provider) . setVersion Nothing
70+
71+
72+--------------------------------------------------------------------------------
73+-- | Each resource may have an associated metadata resource (with a @.metadata@
74+-- filename)
75+resourceMetadataResource :: Identifier -> Identifier
76+resourceMetadataResource =
77+ fromFilePath . flip addExtension "metadata" . toFilePath
78+
79+
80+--------------------------------------------------------------------------------
81+resourceFilePath :: Provider -> Identifier -> FilePath
82+resourceFilePath p i = providerDirectory p </> toFilePath i
83+
84+
85+--------------------------------------------------------------------------------
86+-- | Get the raw body of a resource as string
87+resourceString :: Provider -> Identifier -> IO String
88+resourceString p i = readFile $ resourceFilePath p i
89+
90+
91+--------------------------------------------------------------------------------
92+-- | Get the raw body of a resource of a lazy bytestring
93+resourceLBS :: Provider -> Identifier -> IO BL.ByteString
94+resourceLBS p i = BL.readFile $ resourceFilePath p i
src/Hakyll/Core/Provider/Metadata.hsnew file+125
@@ -0,0 +1,125 @@
1+--------------------------------------------------------------------------------
2+-- | Internal module to parse metadata
3+module Hakyll.Core.Provider.Metadata
4+ ( loadMetadata
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Control.Applicative
10+import Control.Arrow (second)
11+import qualified Data.ByteString.Char8 as BC
12+import qualified Data.Map as M
13+import System.IO as IO
14+import Text.Parsec ((<?>))
15+import qualified Text.Parsec as P
16+import Text.Parsec.String (Parser)
17+
18+
19+--------------------------------------------------------------------------------
20+import Hakyll.Core.Identifier
21+import Hakyll.Core.Metadata
22+import Hakyll.Core.Provider.Internal
23+import Hakyll.Core.Util.String
24+
25+
26+--------------------------------------------------------------------------------
27+loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
28+loadMetadata p identifier = do
29+ hasHeader <- probablyHasMetadataHeader fp
30+ (md, body) <- if hasHeader
31+ then second Just <$> loadMetadataHeader fp
32+ else return (M.empty, Nothing)
33+
34+ emd <- if resourceExists p mi then loadMetadataFile mfp else return M.empty
35+
36+ return (M.union md emd, body)
37+ where
38+ fp = resourceFilePath p identifier
39+ mi = resourceMetadataResource identifier
40+ mfp = resourceFilePath p mi
41+
42+
43+--------------------------------------------------------------------------------
44+loadMetadataHeader :: FilePath -> IO (Metadata, String)
45+loadMetadataHeader fp = do
46+ contents <- readFile fp
47+ case P.parse page fp contents of
48+ Left err -> error (show err)
49+ Right (md, b) -> return (M.fromList md, b)
50+
51+
52+--------------------------------------------------------------------------------
53+loadMetadataFile :: FilePath -> IO Metadata
54+loadMetadataFile fp = do
55+ contents <- readFile fp
56+ case P.parse metadata fp contents of
57+ Left err -> error (show err)
58+ Right md -> return $ M.fromList md
59+
60+
61+--------------------------------------------------------------------------------
62+-- | Check if a file "probably" has a metadata header. The main goal of this is
63+-- to exclude binary files (which are unlikely to start with "---").
64+probablyHasMetadataHeader :: FilePath -> IO Bool
65+probablyHasMetadataHeader fp = do
66+ handle <- IO.openFile fp IO.ReadMode
67+ bs <- BC.hGet handle 1024
68+ IO.hClose handle
69+ return $ isMetadataHeader bs
70+ where
71+ isMetadataHeader bs =
72+ let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
73+ in BC.length pre >= 3 && BC.all (== '-') pre
74+
75+
76+--------------------------------------------------------------------------------
77+-- | Space or tab, no newline
78+inlineSpace :: Parser Char
79+inlineSpace = P.oneOf ['\t', ' '] <?> "space"
80+
81+
82+--------------------------------------------------------------------------------
83+-- | Parse Windows newlines as well (i.e. "\n" or "\r\n")
84+newline :: Parser String
85+newline = P.string "\n" <|> P.string "\r\n"
86+
87+
88+--------------------------------------------------------------------------------
89+-- | Parse a single metadata field
90+metadataField :: Parser (String, String)
91+metadataField = do
92+ key <- P.manyTill P.alphaNum $ P.char ':'
93+ P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
94+ value <- P.manyTill P.anyChar newline
95+ trailing' <- P.many trailing
96+ return (key, trim $ value ++ concat trailing')
97+ where
98+ trailing = (++) <$> P.many1 inlineSpace <*> P.manyTill P.anyChar newline
99+
100+
101+--------------------------------------------------------------------------------
102+-- | Parse a metadata block
103+metadata :: Parser [(String, String)]
104+metadata = P.many metadataField
105+
106+
107+--------------------------------------------------------------------------------
108+-- | Parse a metadata block, including delimiters and trailing newlines
109+metadataBlock :: Parser [(String, String)]
110+metadataBlock = do
111+ open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline
112+ metadata' <- metadata
113+ _ <- P.choice $ map (P.string . replicate (length open)) ['-', '.']
114+ P.skipMany inlineSpace
115+ P.skipMany1 newline
116+ return metadata'
117+
118+
119+--------------------------------------------------------------------------------
120+-- | Parse a page consisting of a metadata header and a body
121+page :: Parser ([(String, String)], String)
122+page = do
123+ metadata' <- P.option [] metadataBlock
124+ body <- P.many P.anyChar
125+ return (metadata', body)
lib/Hakyll/Core/Provider/MetadataCache.hs → src/Hakyll/Core/Provider/MetadataCache.hsrenamed+15−9
@@ -7,7 +7,10 @@ module Hakyll.Core.Provider.MetadataCache
7
7
8
8
9--------------------------------------------------------------------------------
9--------------------------------------------------------------------------------
10−import Control.Monad (unless)
10+import qualified Data.Map as M
11+
12+
13+--------------------------------------------------------------------------------
11import Hakyll.Core.Identifier
14import Hakyll.Core.Identifier
12import Hakyll.Core.Metadata
15import Hakyll.Core.Metadata
13import Hakyll.Core.Provider.Internal
16import Hakyll.Core.Provider.Internal
@@ -18,11 +21,10 @@ import qualified Hakyll.Core.Store as Store
18--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
19resourceMetadata :: Provider -> Identifier -> IO Metadata
22resourceMetadata :: Provider -> Identifier -> IO Metadata
20resourceMetadata p r
23resourceMetadata p r
21− | not (resourceExists p r) = return mempty
24+ | not (resourceExists p r) = return M.empty
22 | otherwise = do
25 | otherwise = do
23− -- TODO keep time in md cache
24 load p r
26 load p r
25− Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
27+ Store.Found md <- Store.get (providerStore p)
26 [name, toFilePath r, "metadata"]
28 [name, toFilePath r, "metadata"]
27 return md
29 return md
28
30
@@ -46,11 +48,15 @@ resourceInvalidateMetadataCache p r = do
46--------------------------------------------------------------------------------
48--------------------------------------------------------------------------------
47load :: Provider -> Identifier -> IO ()
49load :: Provider -> Identifier -> IO ()
48load p r = do
50load p r = do
49− mmof <- Store.isMember store mdk
51+ mmd <- Store.get store mdk :: IO (Store.Result Metadata)
50− unless mmof $ do
52+ case mmd of
53+ -- Already loaded
54+ Store.Found _ -> return ()
55+ -- Not yet loaded
56+ _ -> do
51− (md, body) <- loadMetadata p r
57+ (metadata, body) <- loadMetadata p r
52− Store.set store mdk (BinaryMetadata md)
58+ Store.set store mdk metadata
53− Store.set store bk body
59+ Store.set store bk body
54 where
60 where
55 store = providerStore p
61 store = providerStore p
56 mdk = [name, toFilePath r, "metadata"]
62 mdk = [name, toFilePath r, "metadata"]
src/Hakyll/Core/Provider/Modified.hsnew file+101
@@ -0,0 +1,101 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+module Hakyll.Core.Provider.Modified
4+ ( resourceModified
5+ , resourceModificationTime
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Applicative ((<$>), (<*>))
11+import Control.Monad (when)
12+import qualified Crypto.Hash.MD5 as MD5
13+import qualified Data.ByteString as B
14+import qualified Data.ByteString.Lazy as BL
15+import Data.IORef
16+import qualified Data.Map as M
17+import Data.Time (UTCTime)
18+import System.Directory (getModificationTime)
19+
20+
21+--------------------------------------------------------------------------------
22+#if !MIN_VERSION_directory(1,2,0)
23+import Data.Time (readTime)
24+import System.Locale (defaultTimeLocale)
25+import System.Time (formatCalendarTime,
26+ toCalendarTime)
27+#endif
28+
29+
30+--------------------------------------------------------------------------------
31+import Hakyll.Core.Identifier
32+import Hakyll.Core.Provider.Internal
33+import Hakyll.Core.Provider.MetadataCache
34+import Hakyll.Core.Store (Store)
35+import qualified Hakyll.Core.Store as Store
36+
37+
38+--------------------------------------------------------------------------------
39+-- | A resource is modified if it or its metadata has changed
40+resourceModified :: Provider -> Identifier -> IO Bool
41+resourceModified p r
42+ | not exists = return False
43+ | otherwise = do
44+ cache <- readIORef cacheRef
45+ case M.lookup normalized cache of
46+ Just m -> return m
47+ Nothing -> do
48+ -- Check if the actual file was modified, and do a recursive
49+ -- call to check if the metadata file was modified
50+ m <- (||)
51+ <$> fileDigestModified store filePath
52+ <*> resourceModified p (resourceMetadataResource r)
53+ modifyIORef cacheRef (M.insert normalized m)
54+
55+ -- Important! (But ugly)
56+ when m $ resourceInvalidateMetadataCache p r
57+
58+ return m
59+ where
60+ normalized = setVersion Nothing r
61+ exists = resourceExists p r
62+ store = providerStore p
63+ cacheRef = providerModifiedCache p
64+ filePath = resourceFilePath p r
65+
66+
67+--------------------------------------------------------------------------------
68+-- | Utility: Check if a the digest of a file was modified
69+fileDigestModified :: Store -> FilePath -> IO Bool
70+fileDigestModified store fp = do
71+ -- Get the latest seen digest from the store, and calculate the current
72+ -- digest for the
73+ lastDigest <- Store.get store key
74+ newDigest <- fileDigest fp
75+ if Store.Found newDigest == lastDigest
76+ -- All is fine, not modified
77+ then return False
78+ -- Resource modified; store new digest
79+ else do
80+ Store.set store key newDigest
81+ return True
82+ where
83+ key = ["Hakyll.Core.Resource.Provider.fileModified", fp]
84+
85+
86+--------------------------------------------------------------------------------
87+-- | Utility: Retrieve a digest for a given file
88+fileDigest :: FilePath -> IO B.ByteString
89+fileDigest = fmap MD5.hashlazy . BL.readFile
90+
91+
92+--------------------------------------------------------------------------------
93+resourceModificationTime :: Provider -> Identifier -> IO UTCTime
94+resourceModificationTime p i = do
95+#if MIN_VERSION_directory(1,2,0)
96+ getModificationTime $ resourceFilePath p i
97+#else
98+ ct <- toCalendarTime =<< getModificationTime (resourceFilePath p i)
99+ let str = formatCalendarTime defaultTimeLocale "%s" ct
100+ return $ readTime defaultTimeLocale "%s" str
101+#endif
lib/Hakyll/Core/Routes.hs → src/Hakyll/Core/Routes.hsrenamed+21−73
@@ -25,11 +25,9 @@
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 #-}
29{-# LANGUAGE Rank2Types #-}
28{-# LANGUAGE Rank2Types #-}
30module Hakyll.Core.Routes
29module Hakyll.Core.Routes
31− ( UsedMetadata
32− , Routes
30+ ( Routes
33 , runRoutes
31 , runRoutes
34 , idRoute
32 , idRoute
35 , setExtension
33 , setExtension
@@ -37,82 +35,44 @@ module Hakyll.Core.Routes
37 , customRoute
35 , customRoute
38 , constRoute
36 , constRoute
39 , gsubRoute
37 , gsubRoute
40− , metadataRoute
41 , composeRoutes
38 , composeRoutes
42 ) where
39 ) where
43
40
44
41
45--------------------------------------------------------------------------------
42--------------------------------------------------------------------------------
46−#if MIN_VERSION_base(4,9,0)
43+import Data.Monoid (Monoid, mempty, mappend)
47−import Data.Semigroup (Semigroup (..))
44+import Control.Monad (mplus)
48−#endif
49−import System.FilePath (replaceExtension)
45+import System.FilePath (replaceExtension)
50
46
51
47
52--------------------------------------------------------------------------------
48--------------------------------------------------------------------------------
53−import Hakyll.Core.Identifier
49+import Hakyll.Core.Identifier
54−import Hakyll.Core.Identifier.Pattern
50+import Hakyll.Core.Identifier.Pattern
55−import Hakyll.Core.Metadata
56−import Hakyll.Core.Provider
57−import Hakyll.Core.Util.String
51+import Hakyll.Core.Util.String
58−
59−
60−--------------------------------------------------------------------------------
61−-- | When you ran a route, it's useful to know whether or not this used
62−-- metadata. This allows us to do more granular dependency analysis.
63−type UsedMetadata = Bool
64−
65−
66−--------------------------------------------------------------------------------
67−data RoutesRead = RoutesRead
68− { routesProvider :: Provider
69− , routesUnderlying :: Identifier
70− }
71
52
72
53
73--------------------------------------------------------------------------------
54--------------------------------------------------------------------------------
74-- | Type used for a route
55-- | Type used for a route
75−newtype Routes = Routes
76− { unRoutes :: RoutesRead -> Identifier -> IO (Maybe FilePath, UsedMetadata)
56+newtype Routes = Routes {unRoutes :: Identifier -> Maybe FilePath}
77− }
78
57
79
58
80--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
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
93instance Monoid Routes where
60instance Monoid Routes where
94− mempty = Routes $ \_ _ -> return (Nothing, False)
61+ mempty = Routes $ const Nothing
95− mappend (Routes f) (Routes g) = Routes $ \p id' -> do
62+ mappend (Routes f) (Routes g) = Routes $ \id' -> f id' `mplus` g id'
96− (mfp, um) <- f p id'
97− case mfp of
98− Nothing -> g p id'
99− Just _ -> return (mfp, um)
100−#endif
101
63
102
64
103--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
104-- | Apply a route to an identifier
66-- | Apply a route to an identifier
105−runRoutes :: Routes -> Provider -> Identifier
67+runRoutes :: Routes -> Identifier -> Maybe FilePath
106− -> IO (Maybe FilePath, UsedMetadata)
68+runRoutes = unRoutes
107−runRoutes routes provider identifier =
108− unRoutes routes (RoutesRead provider identifier) identifier
109
69
110
70
111--------------------------------------------------------------------------------
71--------------------------------------------------------------------------------
112-- | A route that uses the identifier as filepath. For example, the target with
72-- | A route that uses the identifier as filepath. For example, the target with
113-- ID @foo\/bar@ will be written to the file @foo\/bar@.
73-- ID @foo\/bar@ will be written to the file @foo\/bar@.
114idRoute :: Routes
74idRoute :: Routes
115−idRoute = customRoute toFilePath
75+idRoute = Routes $ Just . toFilePath
116
76
117
77
118--------------------------------------------------------------------------------
78--------------------------------------------------------------------------------
@@ -134,23 +94,23 @@ idRoute = customRoute toFilePath
134--
94--
135-- > Just "posts/the-art-of-trolling.html"
95-- > Just "posts/the-art-of-trolling.html"
136setExtension :: String -> Routes
96setExtension :: String -> Routes
137−setExtension extension = customRoute $
97+setExtension extension = Routes $
138− (`replaceExtension` extension) . toFilePath
98+ fmap (`replaceExtension` extension) . unRoutes idRoute
139
99
140
100
141--------------------------------------------------------------------------------
101--------------------------------------------------------------------------------
142-- | Apply the route if the identifier matches the given pattern, fail
102-- | Apply the route if the identifier matches the given pattern, fail
143-- otherwise
103-- otherwise
144matchRoute :: Pattern -> Routes -> Routes
104matchRoute :: Pattern -> Routes -> Routes
145−matchRoute pattern (Routes route) = Routes $ \p id' ->
105+matchRoute pattern (Routes route) = Routes $ \id' ->
146− if matches pattern id' then route p id' else return (Nothing, False)
106+ if matches pattern id' then route id' else Nothing
147
107
148
108
149--------------------------------------------------------------------------------
109--------------------------------------------------------------------------------
150-- | Create a custom route. This should almost always be used with
110-- | Create a custom route. This should almost always be used with
151-- 'matchRoute'
111-- 'matchRoute'
152customRoute :: (Identifier -> FilePath) -> Routes
112customRoute :: (Identifier -> FilePath) -> Routes
153−customRoute f = Routes $ const $ \id' -> return (Just (f id'), False)
113+customRoute f = Routes $ Just . f
154
114
155
115
156--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
@@ -177,14 +137,6 @@ gsubRoute pattern replacement = customRoute $
177 replaceAll pattern replacement . toFilePath
137 replaceAll pattern replacement . toFilePath
178
138
179
139
180−--------------------------------------------------------------------------------
181−-- | Get access to the metadata in order to determine the route
182−metadataRoute :: (Metadata -> Routes) -> Routes
183−metadataRoute f = Routes $ \r i -> do
184− metadata <- resourceMetadata (routesProvider r) (routesUnderlying r)
185− unRoutes (f metadata) r i
186−
187−
188--------------------------------------------------------------------------------
140--------------------------------------------------------------------------------
189-- | Compose routes so that @f \`composeRoutes\` g@ is more or less equivalent
141-- | Compose routes so that @f \`composeRoutes\` g@ is more or less equivalent
190-- with @g . f@.
142-- with @g . f@.
@@ -202,10 +154,6 @@ metadataRoute f = Routes $ \r i -> do
202composeRoutes :: Routes -- ^ First route to apply
154composeRoutes :: Routes -- ^ First route to apply
203 -> Routes -- ^ Second route to apply
155 -> Routes -- ^ Second route to apply
204 -> Routes -- ^ Resulting route
156 -> Routes -- ^ Resulting route
205−composeRoutes (Routes f) (Routes g) = Routes $ \p i -> do
157+composeRoutes (Routes f) (Routes g) = Routes $ \i -> do
206− (mfp, um) <- f p i
158+ p <- f i
207− case mfp of
208− Nothing -> return (Nothing, um)
209− Just fp -> do
210− (mfp', um') <- g p (fromFilePath fp)
159+ g $ fromFilePath p
211− return (mfp', um || um')
lib/Hakyll/Core/Rules.hs → src/Hakyll/Core/Rules.hsrenamed+11−51
@@ -19,7 +19,6 @@
19module Hakyll.Core.Rules
19module Hakyll.Core.Rules
20 ( Rules
20 ( Rules
21 , match
21 , match
22− , matchMetadata
23 , create
22 , create
24 , version
23 , version
25 , compile
24 , compile
@@ -33,11 +32,13 @@ module Hakyll.Core.Rules
33
32
34
33
35--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
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)
41import qualified Data.Set as S
42import qualified Data.Set as S
42
43
43
44
@@ -62,26 +63,20 @@ import Hakyll.Core.Writable
62--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
63-- | Add a route
64-- | Add a route
64tellRoute :: Routes -> Rules ()
65tellRoute :: Routes -> Rules ()
65−tellRoute route' = Rules $ tell $ RuleSet route' mempty mempty mempty
66+tellRoute route' = Rules $ tell $ RuleSet route' mempty mempty
66
67
67
68
68--------------------------------------------------------------------------------
69--------------------------------------------------------------------------------
69-- | Add a number of compilers
70-- | Add a number of compilers
70tellCompilers :: [(Identifier, Compiler SomeItem)] -> Rules ()
71tellCompilers :: [(Identifier, Compiler SomeItem)] -> Rules ()
71−tellCompilers compilers = Rules $ tell $ RuleSet mempty compilers mempty mempty
72+tellCompilers compilers = Rules $ tell $ RuleSet mempty compilers mempty
72
73
73
74
74--------------------------------------------------------------------------------
75--------------------------------------------------------------------------------
75-- | Add resources
76-- | Add resources
76tellResources :: [Identifier] -> Rules ()
77tellResources :: [Identifier] -> Rules ()
77tellResources resources' = Rules $ tell $
78tellResources resources' = Rules $ tell $
78− RuleSet mempty mempty (S.fromList resources') mempty
79+ RuleSet mempty mempty $ S.fromList resources'
79−
80−
81−--------------------------------------------------------------------------------
82−-- | Add a pattern
83−tellPattern :: Pattern -> Rules ()
84−tellPattern pattern = Rules $ tell $ RuleSet mempty mempty mempty pattern
85
80
86
81
87--------------------------------------------------------------------------------
82--------------------------------------------------------------------------------
@@ -119,26 +114,15 @@ flush = Rules $ do
119
114
120
115
121--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
122−matchInternal :: Pattern -> Rules [Identifier] -> Rules () -> Rules ()
117+match :: Pattern -> Rules () -> Rules ()
123−matchInternal pattern getIDs rules = do
118+match pattern rules = do
124− tellPattern pattern
125 flush
119 flush
126− ids <- getIDs
120+ ids <- getMatches pattern
127 tellResources ids
121 tellResources ids
128 Rules $ local (setMatches ids) $ unRules $ rules >> flush
122 Rules $ local (setMatches ids) $ unRules $ rules >> flush
129 where
123 where
130 setMatches ids env = env {rulesMatches = ids}
124 setMatches ids env = env {rulesMatches = ids}
131
125
132−--------------------------------------------------------------------------------
133−match :: Pattern -> Rules () -> Rules ()
134−match pattern = matchInternal pattern $ getMatches pattern
135−
136−
137−--------------------------------------------------------------------------------
138−matchMetadata :: Pattern -> (Metadata -> Bool) -> Rules () -> Rules ()
139−matchMetadata pattern metadataPred = matchInternal pattern $
140− map fst . filter (metadataPred . snd) <$> getAllMetadata pattern
141−
142
126
143--------------------------------------------------------------------------------
127--------------------------------------------------------------------------------
144create :: [Identifier] -> Rules () -> Rules ()
128create :: [Identifier] -> Rules () -> Rules ()
@@ -187,35 +171,11 @@ preprocess = Rules . liftIO
187-- | Advanced usage: add extra dependencies to compilers. Basically this is
171-- | Advanced usage: add extra dependencies to compilers. Basically this is
188-- needed when you're doing unsafe tricky stuff in the rules monad, but you
172-- needed when you're doing unsafe tricky stuff in the rules monad, but you
189-- still want correct builds.
173-- still want correct builds.
190−--
191−-- A useful utility for this purpose is 'makePatternDependency'.
192rulesExtraDependencies :: [Dependency] -> Rules a -> Rules a
174rulesExtraDependencies :: [Dependency] -> Rules a -> Rules a
193−rulesExtraDependencies deps rules =
175+rulesExtraDependencies deps = Rules . censor addDependencies . unRules
194− -- Note that we add the dependencies seemingly twice here. However, this is
195− -- done so that 'rulesExtraDependencies' works both if we have something
196− -- like:
197− --
198− -- > match "*.css" $ rulesExtraDependencies [foo] $ ...
199− --
200− -- and something like:
201− --
202− -- > rulesExtraDependencies [foo] $ match "*.css" $ ...
203− --
204− -- (1) takes care of the latter and (2) of the former.
205− Rules $ censor fixRuleSet $ do
206− x <- unRules rules
207− fixCompiler
208− return x
209 where
176 where
210− -- (1) Adds the dependencies to the compilers we are yet to create
211− fixCompiler = modify $ \s -> case rulesCompiler s of
212− Nothing -> s
213− Just c -> s
214− { rulesCompiler = Just $ compilerTellDependencies deps >> c
215− }
216−
217− -- (2) Adds the dependencies to the compilers that are already in the ruleset
177+ -- Adds the dependencies to the compilers in the ruleset
218− fixRuleSet ruleSet = ruleSet
178+ addDependencies ruleSet = ruleSet
219 { rulesCompilers =
179 { rulesCompilers =
220 [ (i, compilerTellDependencies deps >> c)
180 [ (i, compilerTellDependencies deps >> c)
221 | (i, c) <- rulesCompilers ruleSet
181 | (i, c) <- rulesCompilers ruleSet
lib/Hakyll/Core/Rules/Internal.hs → src/Hakyll/Core/Rules/Internal.hsrenamed+15−28
@@ -1,5 +1,4 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE Rank2Types #-}
3{-# LANGUAGE Rank2Types #-}
5module Hakyll.Core.Rules.Internal
4module Hakyll.Core.Rules.Internal
@@ -13,13 +12,12 @@ module Hakyll.Core.Rules.Internal
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
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−#if MIN_VERSION_base(4,9,0)
20+import Data.Monoid (Monoid, mappend, mempty)
21−import Data.Semigroup (Semigroup (..))
22−#endif
23import Data.Set (Set)
21import Data.Set (Set)
24
22
25
23
@@ -49,27 +47,14 @@ data RuleSet = RuleSet
49 rulesCompilers :: [(Identifier, Compiler SomeItem)]
47 rulesCompilers :: [(Identifier, Compiler SomeItem)]
50 , -- | A set of the actually used files
48 , -- | A set of the actually used files
51 rulesResources :: Set Identifier
49 rulesResources :: Set Identifier
52− , -- | A pattern we can use to check if a file *would* be used. This is
53− -- needed for the preview server.
54− rulesPattern :: Pattern
55 }
50 }
56
51
57
52
58--------------------------------------------------------------------------------
53--------------------------------------------------------------------------------
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
68instance Monoid RuleSet where
54instance Monoid RuleSet where
69− mempty = RuleSet mempty mempty mempty mempty
55+ mempty = RuleSet mempty mempty mempty
70− mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
56+ mappend (RuleSet r1 c1 s1) (RuleSet r2 c2 s2) =
71− RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
57+ RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2)
72−#endif
73
58
74
59
75--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
@@ -107,17 +92,19 @@ instance MonadMetadata Rules where
107runRules :: Rules a -> Provider -> IO RuleSet
92runRules :: Rules a -> Provider -> IO RuleSet
108runRules rules provider = do
93runRules rules provider = do
109 (_, _, ruleSet) <- runRWST (unRules rules) env emptyRulesState
94 (_, _, ruleSet) <- runRWST (unRules rules) env emptyRulesState
110−
111− -- Ensure compiler uniqueness
112− let ruleSet' = ruleSet
113− { rulesCompilers = M.toList $
114− M.fromListWith (flip const) (rulesCompilers ruleSet)
115− }
116−
117− return ruleSet'
95+ return $ nubCompilers ruleSet
118 where
96 where
119 env = RulesRead
97 env = RulesRead
120 { rulesProvider = provider
98 { rulesProvider = provider
121 , rulesMatches = []
99 , rulesMatches = []
122 , rulesVersion = Nothing
100 , rulesVersion = Nothing
123 }
101 }
102+
103+
104+--------------------------------------------------------------------------------
105+-- | Remove duplicate compilers from the 'RuleSet'. When two compilers match an
106+-- item, we prefer the first one
107+nubCompilers :: RuleSet -> RuleSet
108+nubCompilers set = set {rulesCompilers = nubCompilers' (rulesCompilers set)}
109+ where
110+ nubCompilers' = M.toList . M.fromListWith (flip const)
lib/Hakyll/Core/Runtime.hs → src/Hakyll/Core/Runtime.hsrenamed+27−53
@@ -5,15 +5,16 @@ module Hakyll.Core.Runtime
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Control.Monad (unless)
8+import Control.Applicative ((<$>))
9+import Control.Monad (filterM, unless)
9−import Control.Monad.Except (ExceptT, runExceptT, throwError)
10+import Control.Monad.Error (ErrorT, runErrorT, throwError)
10import Control.Monad.Reader (ask)
11import Control.Monad.Reader (ask)
11import Control.Monad.RWS (RWST, runRWST)
12import Control.Monad.RWS (RWST, runRWST)
12import Control.Monad.State (get, modify)
13import Control.Monad.State (get, modify)
13import Control.Monad.Trans (liftIO)
14import Control.Monad.Trans (liftIO)
14−import Data.List (intercalate)
15import Data.Map (Map)
15import Data.Map (Map)
16import qualified Data.Map as M
16import qualified Data.Map as M
17+import Data.Monoid (mempty)
17import Data.Set (Set)
18import Data.Set (Set)
18import qualified Data.Set as S
19import qualified Data.Set as S
19import System.Exit (ExitCode (..))
20import System.Exit (ExitCode (..))
@@ -28,7 +29,7 @@ import Hakyll.Core.Dependencies
28import Hakyll.Core.Identifier
29import Hakyll.Core.Identifier
29import Hakyll.Core.Item
30import Hakyll.Core.Item
30import Hakyll.Core.Item.SomeItem
31import Hakyll.Core.Item.SomeItem
31−import Hakyll.Core.Logger (Logger)
32+import Hakyll.Core.Logger (Logger, Verbosity)
32import qualified Hakyll.Core.Logger as Logger
33import qualified Hakyll.Core.Logger as Logger
33import Hakyll.Core.Provider
34import Hakyll.Core.Provider
34import Hakyll.Core.Routes
35import Hakyll.Core.Routes
@@ -40,9 +41,10 @@ import Hakyll.Core.Writable
40
41
41
42
42--------------------------------------------------------------------------------
43--------------------------------------------------------------------------------
43−run :: Configuration -> Logger -> Rules a -> IO (ExitCode, RuleSet)
44+run :: Configuration -> Verbosity -> Rules a -> IO (ExitCode, RuleSet)
44−run config logger rules = do
45+run config verbosity rules = do
45 -- Initialization
46 -- Initialization
47+ logger <- Logger.new verbosity
46 Logger.header logger "Initialising..."
48 Logger.header logger "Initialising..."
47 Logger.message logger "Creating store..."
49 Logger.message logger "Creating store..."
48 store <- Store.new (inMemoryCache config) $ storeDirectory config
50 store <- Store.new (inMemoryCache config) $ storeDirectory config
@@ -68,14 +70,13 @@ run config logger rules = do
68 , runtimeUniverse = M.fromList compilers
70 , runtimeUniverse = M.fromList compilers
69 }
71 }
70 state = RuntimeState
72 state = RuntimeState
71− { runtimeDone = S.empty
73+ { runtimeDone = S.empty
72− , runtimeSnapshots = S.empty
73− , runtimeTodo = M.empty
74+ , runtimeTodo = M.empty
74− , runtimeFacts = oldFacts
75+ , runtimeFacts = oldFacts
75 }
76 }
76
77
77 -- Run the program and fetch the resulting state
78 -- Run the program and fetch the resulting state
78− result <- runExceptT $ runRWST build read' state
79+ result <- runErrorT $ runRWST build read' state
79 case result of
80 case result of
80 Left e -> do
81 Left e -> do
81 Logger.error logger e
82 Logger.error logger e
@@ -107,15 +108,14 @@ data RuntimeRead = RuntimeRead
107
108
108--------------------------------------------------------------------------------
109--------------------------------------------------------------------------------
109data RuntimeState = RuntimeState
110data RuntimeState = RuntimeState
110− { runtimeDone :: Set Identifier
111+ { runtimeDone :: Set Identifier
111− , runtimeSnapshots :: Set (Identifier, Snapshot)
112− , runtimeTodo :: Map Identifier (Compiler SomeItem)
112+ , runtimeTodo :: Map Identifier (Compiler SomeItem)
113− , runtimeFacts :: DependencyFacts
113+ , runtimeFacts :: DependencyFacts
114 }
114 }
115
115
116
116
117--------------------------------------------------------------------------------
117--------------------------------------------------------------------------------
118−type Runtime a = RWST RuntimeRead () RuntimeState (ExceptT String IO) a
118+type Runtime a = RWST RuntimeRead () RuntimeState (ErrorT String IO) a
119
119
120
120
121--------------------------------------------------------------------------------
121--------------------------------------------------------------------------------
@@ -139,9 +139,8 @@ scheduleOutOfDate = do
139 todo <- runtimeTodo <$> get
139 todo <- runtimeTodo <$> get
140
140
141 let identifiers = M.keys universe
141 let identifiers = M.keys universe
142− modified = S.fromList $ flip filter identifiers $
142+ modified <- fmap S.fromList $ flip filterM identifiers $
143− resourceModified provider
143+ liftIO . resourceModified provider
144−
145 let (ood, facts', msgs) = outOfDate identifiers modified facts
144 let (ood, facts', msgs) = outOfDate identifiers modified facts
146 todo' = M.filterWithKey
145 todo' = M.filterWithKey
147 (\id' _ -> id' `S.member` ood) universe
146 (\id' _ -> id' `S.member` ood) universe
@@ -172,9 +171,7 @@ pickAndChase = do
172--------------------------------------------------------------------------------
171--------------------------------------------------------------------------------
173chase :: [Identifier] -> Identifier -> Runtime ()
172chase :: [Identifier] -> Identifier -> Runtime ()
174chase trail id'
173chase trail id'
175− | id' `elem` trail = throwError $ "Hakyll.Core.Runtime.chase: " ++
174+ | id' `elem` trail = return () -- Cycle detected!
176− "Dependency cycle detected: " ++ intercalate " depends on "
177− (map show $ dropWhile (/= id') (reverse trail) ++ [id'])
178 | otherwise = do
175 | otherwise = do
179 logger <- runtimeLogger <$> ask
176 logger <- runtimeLogger <$> ask
180 todo <- runtimeTodo <$> get
177 todo <- runtimeTodo <$> get
@@ -199,19 +196,7 @@ chase trail id'
199 result <- liftIO $ runCompiler compiler read'
196 result <- liftIO $ runCompiler compiler read'
200 case result of
197 case result of
201 -- Rethrow error
198 -- Rethrow error
202− CompilerError e -> throwError $ case compilerErrorMessages e of
199+ CompilerError e -> throwError e
203− [] -> "Compiler failed but no info given, try running with -v?"
204− es -> intercalate "; " es
205−
206− -- Signal that a snapshot was saved ->
207− CompilerSnapshot snapshot c -> do
208− -- Update info. The next 'chase' will pick us again at some
209− -- point so we can continue then.
210− modify $ \s -> s
211− { runtimeSnapshots =
212− S.insert (id', snapshot) (runtimeSnapshots s)
213− , runtimeTodo = M.insert id' c (runtimeTodo s)
214− }
215
200
216 -- Huge success
201 -- Huge success
217 CompilerDone (SomeItem item) cwrite -> do
202 CompilerDone (SomeItem item) cwrite -> do
@@ -230,11 +215,10 @@ chase trail id'
230 "(you probably want to call makeItem to solve this problem)"
215 "(you probably want to call makeItem to solve this problem)"
231
216
232 -- Write if necessary
217 -- Write if necessary
233− (mroute, _) <- liftIO $ runRoutes routes provider id'
218+ case runRoutes routes id' of
234− case mroute of
235− Nothing -> return ()
219+ Nothing -> return ()
236− Just route -> do
220+ Just url -> do
237− let path = destinationDirectory config </> route
221+ let path = destinationDirectory config </> url
238 liftIO $ makeDirectories path
222 liftIO $ makeDirectories path
239 liftIO $ write path item
223 liftIO $ write path item
240 Logger.debug logger $ "Routed to " ++ path
224 Logger.debug logger $ "Routed to " ++ path
@@ -252,16 +236,7 @@ chase trail id'
252 -- Try something else first
236 -- Try something else first
253 CompilerRequire dep c -> do
237 CompilerRequire dep c -> do
254 -- Update the compiler so we don't execute it twice
238 -- Update the compiler so we don't execute it twice
255− let (depId, depSnapshot) = dep
239+ depDone <- (dep `S.member`) . runtimeDone <$> get
256− done <- runtimeDone <$> get
257− snapshots <- runtimeSnapshots <$> get
258−
259− -- Done if we either completed the entire item (runtimeDone) or
260− -- if we previously saved the snapshot (runtimeSnapshots).
261− let depDone =
262− depId `S.member` done ||
263− (depId, depSnapshot) `S.member` snapshots
264−
265 modify $ \s -> s
240 modify $ \s -> s
266 { runtimeTodo = M.insert id'
241 { runtimeTodo = M.insert id'
267 (if depDone then c else compilerResult result)
242 (if depDone then c else compilerResult result)
@@ -270,7 +245,6 @@ chase trail id'
270
245
271 -- If the required item is already compiled, continue, or, start
246 -- If the required item is already compiled, continue, or, start
272 -- chasing that
247 -- chasing that
273− Logger.debug logger $ "Require " ++ show depId ++
248+ Logger.debug logger $ "Require " ++ show dep ++ ": " ++
274− " (snapshot " ++ depSnapshot ++ "): " ++
275 (if depDone then "OK" else "chasing")
249 (if depDone then "OK" else "chasing")
276− if depDone then chase trail id' else chase (id' : trail) depId
250+ if depDone then chase trail id' else chase (id' : trail) dep
lib/Hakyll/Core/Store.hs → src/Hakyll/Core/Store.hsrenamed+29−72
@@ -5,34 +5,29 @@
5module Hakyll.Core.Store
5module Hakyll.Core.Store
6 ( Store
6 ( Store
7 , Result (..)
7 , Result (..)
8− , toMaybe
9 , new
8 , new
10 , set
9 , set
11 , get
10 , get
12− , isMember
13 , delete
11 , delete
14 , hash
12 , hash
15 ) where
13 ) where
16
14
17
15
18--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
17+import Control.Applicative ((<$>))
18+import Control.Exception (IOException, handle)
19−import qualified Crypto.Hash.MD5 as MD5
19+import qualified Crypto.Hash.MD5 as MD5
20−import Data.Binary (Binary, decode, encodeFile)
20+import Data.Binary (Binary, decodeFile, encodeFile)
21−import qualified Data.ByteString as B
21+import qualified Data.ByteString as B
22−import qualified Data.ByteString.Lazy as BL
22+import qualified Data.Cache.LRU.IO as Lru
23−import qualified Data.Cache.LRU.IO as Lru
24−import Data.List (intercalate)
23+import Data.List (intercalate)
25−import Data.Maybe (isJust)
26−import qualified Data.Text as T
24+import qualified Data.Text as T
27−import qualified Data.Text.Encoding as T
25+import qualified Data.Text.Encoding as T
28−import Data.Typeable (TypeRep, Typeable, cast, typeOf)
26+import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29−import Numeric (showHex)
30−import System.Directory (createDirectoryIfMissing)
27+import System.Directory (createDirectoryIfMissing)
31−import System.Directory (doesFileExist, removeFile)
28+import System.Directory (doesFileExist, removeFile)
32−import System.FilePath ((</>))
29+import System.FilePath ((</>))
33−import System.IO (IOMode (..), hClose, openFile)
30+import Text.Printf (printf)
34−import System.IO.Error (catchIOError, ioeSetFileName,
35− ioeSetLocation, modifyIOError)
36
31
37
32
38--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
@@ -49,11 +44,6 @@ data Store = Store
49 }
44 }
50
45
51
46
52−--------------------------------------------------------------------------------
53−instance Show Store where
54− show _ = "<Store>"
55−
56−
57--------------------------------------------------------------------------------
47--------------------------------------------------------------------------------
58-- | Result of a store query
48-- | Result of a store query
59data Result a
49data Result a
@@ -63,13 +53,6 @@ data Result a
63 deriving (Show, Eq)
53 deriving (Show, Eq)
64
54
65
55
66−--------------------------------------------------------------------------------
67−-- | Convert result to 'Maybe'
68−toMaybe :: Result a -> Maybe a
69−toMaybe (Found x) = Just x
70−toMaybe _ = Nothing
71−
72−
73--------------------------------------------------------------------------------
56--------------------------------------------------------------------------------
74-- | Initialize the store
57-- | Initialize the store
75new :: Bool -- ^ Use in-memory caching
58new :: Bool -- ^ Use in-memory caching
@@ -85,14 +68,6 @@ new inMemory directory = do
85 where
68 where
86 csize = Just 500
69 csize = Just 500
87
70
88−--------------------------------------------------------------------------------
89−withStore :: Store -> String -> (String -> FilePath -> IO a) -> [String] -> IO a
90−withStore store loc run identifier = modifyIOError handle $ run key path
91− where
92− key = hash identifier
93− path = storeDirectory store </> key
94− handle e = e `ioeSetFileName` (path ++ " for " ++ intercalate "/" identifier)
95− `ioeSetLocation` ("Store." ++ loc)
96
71
97--------------------------------------------------------------------------------
72--------------------------------------------------------------------------------
98-- | Auxiliary: add an item to the in-memory cache
73-- | Auxiliary: add an item to the in-memory cache
@@ -115,12 +90,6 @@ cacheLookup (Store _ (Just lru)) key = do
115 Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
90 Nothing -> WrongType (typeOf (undefined :: a)) (typeOf x)
116
91
117
92
118−--------------------------------------------------------------------------------
119−cacheIsMember :: Store -> String -> IO Bool
120−cacheIsMember (Store _ Nothing) _ = return False
121−cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru
122−
123−
124--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
125-- | Auxiliary: delete an item from the in-memory cache
94-- | Auxiliary: delete an item from the in-memory cache
126cacheDelete :: Store -> String -> IO ()
95cacheDelete :: Store -> String -> IO ()
@@ -133,16 +102,17 @@ cacheDelete (Store _ (Just lru)) key = do
133--------------------------------------------------------------------------------
102--------------------------------------------------------------------------------
134-- | Store an item
103-- | Store an item
135set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
104set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
136−set store identifier value = withStore store "set" (\key path -> do
105+set store identifier value = do
137− encodeFile path value
106+ encodeFile (storeDirectory store </> key) value
138 cacheInsert store key value
107 cacheInsert store key value
108+ where
139− ) identifier
109+ key = hash identifier
140
110
141
111
142--------------------------------------------------------------------------------
112--------------------------------------------------------------------------------
143-- | Load an item
113-- | Load an item
144get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
114get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
145−get store = withStore store "get" $ \key path -> do
115+get store identifier = do
146 -- First check the in-memory map
116 -- First check the in-memory map
147 ref <- cacheLookup store key
117 ref <- cacheLookup store key
148 case ref of
118 case ref of
@@ -154,47 +124,34 @@ get store = withStore store "get" $ \key path -> do
154 then return NotFound
124 then return NotFound
155 -- Found in the filesystem
125 -- Found in the filesystem
156 else do
126 else do
157− v <- decodeClose path
127+ v <- decodeFile path
158 cacheInsert store key v
128 cacheInsert store key v
159 return $ Found v
129 return $ Found v
160 -- Found in the in-memory map (or wrong type), just return
130 -- Found in the in-memory map (or wrong type), just return
161 s -> return s
131 s -> return s
162 where
132 where
163− -- 'decodeFile' from Data.Binary which closes the file ASAP
133+ key = hash identifier
164− decodeClose path = do
134+ path = storeDirectory store </> key
165− h <- openFile path ReadMode
166− lbs <- BL.hGetContents h
167− BL.length lbs `seq` hClose h
168− return $ decode lbs
169−
170−
171−--------------------------------------------------------------------------------
172−-- | Strict function
173−isMember :: Store -> [String] -> IO Bool
174−isMember store = withStore store "isMember" $ \key path -> do
175− inCache <- cacheIsMember store key
176− if inCache then return True else doesFileExist path
177
135
178
136
179--------------------------------------------------------------------------------
137--------------------------------------------------------------------------------
180-- | Delete an item
138-- | Delete an item
181delete :: Store -> [String] -> IO ()
139delete :: Store -> [String] -> IO ()
182−delete store = withStore store "delete" $ \key path -> do
140+delete store identifier = do
183 cacheDelete store key
141 cacheDelete store key
184− deleteFile path
142+ deleteFile $ storeDirectory store </> key
143+ where
144+ key = hash identifier
185
145
186
146
187--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
188-- | Delete a file unless it doesn't exist...
148-- | Delete a file unless it doesn't exist...
189deleteFile :: FilePath -> IO ()
149deleteFile :: FilePath -> IO ()
190−deleteFile = (`catchIOError` \_ -> return ()) . removeFile
150+deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
191
151
192
152
193--------------------------------------------------------------------------------
153--------------------------------------------------------------------------------
194-- | Mostly meant for internal usage
154-- | Mostly meant for internal usage
195hash :: [String] -> String
155hash :: [String] -> String
156+hash = concatMap (printf "%02x") . B.unpack .
196−hash = toHex . B.unpack . MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
157+ 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.hsnew file+105
@@ -0,0 +1,105 @@
1+--------------------------------------------------------------------------------
2+-- | A Compiler that supports unix filters.
3+module Hakyll.Core.UnixFilter
4+ ( unixFilter
5+ , unixFilterLBS
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Concurrent (forkIO)
11+import Data.ByteString.Lazy (ByteString)
12+import qualified Data.ByteString.Lazy as LB
13+import System.IO (Handle, hClose, hGetContents, hPutStr,
14+ hSetEncoding, localeEncoding)
15+import System.Process
16+
17+
18+--------------------------------------------------------------------------------
19+import Hakyll.Core.Compiler
20+
21+
22+--------------------------------------------------------------------------------
23+-- | Use a unix filter as compiler. For example, we could use the 'rev' program
24+-- as a compiler.
25+--
26+-- > rev :: Compiler String
27+-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
28+--
29+-- A more realistic example: one can use this to call, for example, the sass
30+-- compiler on CSS files. More information about sass can be found here:
31+--
32+-- <http://sass-lang.com/>
33+--
34+-- The code is fairly straightforward, given that we use @.scss@ for sass:
35+--
36+-- > match "style.scss" $ do
37+-- > route $ setExtension "css"
38+-- > compile $ getResourceString >>=
39+-- > withItemBody (unixFilter "sass" ["-s", "--scss"]) >>=
40+-- > return . fmap compressCss
41+unixFilter :: String -- ^ Program name
42+ -> [String] -- ^ Program args
43+ -> String -- ^ Program input
44+ -> Compiler String -- ^ Program output
45+unixFilter = unixFilterWith writer reader
46+ where
47+ writer handle input = do
48+ hSetEncoding handle localeEncoding
49+ hPutStr handle input
50+ reader handle = do
51+ hSetEncoding handle localeEncoding
52+ hGetContents handle
53+
54+
55+--------------------------------------------------------------------------------
56+-- | Variant of 'unixFilter' that should be used for binary files
57+--
58+-- > match "music.wav" $ do
59+-- > route $ setExtension "ogg"
60+-- > compile $ getResourceLBS >>= withItemBody (unixFilter "oggenc" ["-"])
61+unixFilterLBS :: String -- ^ Program name
62+ -> [String] -- ^ Program args
63+ -> ByteString -- ^ Program input
64+ -> Compiler ByteString -- ^ Program output
65+unixFilterLBS = unixFilterWith LB.hPutStr LB.hGetContents
66+
67+
68+--------------------------------------------------------------------------------
69+-- | Overloaded compiler
70+unixFilterWith :: (Handle -> i -> IO ()) -- ^ Writer
71+ -> (Handle -> IO o) -- ^ Reader
72+ -> String -- ^ Program name
73+ -> [String] -- ^ Program args
74+ -> i -- ^ Program input
75+ -> Compiler o -- ^ Program output
76+unixFilterWith writer reader programName args input = do
77+ debugCompiler ("Executing external program " ++ programName)
78+ unsafeCompiler $ unixFilterIO writer reader programName args input
79+
80+
81+--------------------------------------------------------------------------------
82+-- | Internally used function
83+unixFilterIO :: (Handle -> i -> IO ())
84+ -> (Handle -> IO o)
85+ -> String
86+ -> [String]
87+ -> i
88+ -> IO o
89+unixFilterIO writer reader programName args input = do
90+ let process = (proc programName args)
91+ { std_in = CreatePipe
92+ , std_out = CreatePipe
93+ , close_fds = True
94+ }
95+
96+ (Just stdinWriteHandle, Just stdoutReadHandle, _, _) <-
97+ createProcess process
98+
99+ -- Write the input to the child pipe
100+ _ <- forkIO $ do
101+ writer stdinWriteHandle input
102+ hClose stdinWriteHandle
103+
104+ -- Receive the output from the child
105+ reader stdoutReadHandle
lib/Hakyll/Core/Util/File.hs → src/Hakyll/Core/Util/File.hsrenamed+8−11
@@ -8,7 +8,8 @@ module Hakyll.Core.Util.File
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import Control.Monad (filterM, forM, when)
11+import Control.Applicative ((<$>))
12+import Control.Monad (forM, when)
12import System.Directory (createDirectoryIfMissing,
13import System.Directory (createDirectoryIfMissing,
13 doesDirectoryExist, getDirectoryContents,
14 doesDirectoryExist, getDirectoryContents,
14 removeDirectoryRecursive)
15 removeDirectoryRecursive)
@@ -24,21 +25,17 @@ makeDirectories = createDirectoryIfMissing True . takeDirectory
24
25
25--------------------------------------------------------------------------------
26--------------------------------------------------------------------------------
26-- | Get all contents of a directory.
27-- | Get all contents of a directory.
27−getRecursiveContents :: (FilePath -> IO Bool) -- ^ Ignore this file/directory
28+getRecursiveContents :: FilePath -- ^ Directory to search
28− -> FilePath -- ^ Directory to search
29− -> IO [FilePath] -- ^ List of files found
29+ -> IO [FilePath] -- ^ List of files found
30−getRecursiveContents ignore top = go ""
30+getRecursiveContents top = go ""
31 where
31 where
32− isProper x
32+ isProper = (`notElem` [".", ".."])
33− | x `elem` [".", ".."] = return False
34− | otherwise = not <$> ignore x
35−
36− go dir = do
33+ go dir = do
37 dirExists <- doesDirectoryExist (top </> dir)
34 dirExists <- doesDirectoryExist (top </> dir)
38 if not dirExists
35 if not dirExists
39 then return []
36 then return []
40 else do
37 else do
41− names <- filterM isProper =<< getDirectoryContents (top </> dir)
38+ names <- filter isProper <$> getDirectoryContents (top </> dir)
42 paths <- forM names $ \name -> do
39 paths <- forM names $ \name -> do
43 let rel = dir </> name
40 let rel = dir </> name
44 isDirectory <- doesDirectoryExist (top </> rel)
41 isDirectory <- doesDirectoryExist (top </> rel)
lib/Hakyll/Core/Util/String.hs → src/Hakyll/Core/Util/String.hsrenamed+6−36
@@ -1,33 +1,27 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−--------------------------------------------------------------------------------
3-- | Miscellaneous string manipulation functions.
1-- | Miscellaneous string manipulation functions.
2+--
4module Hakyll.Core.Util.String
3module Hakyll.Core.Util.String
5 ( trim
4 ( trim
6 , replaceAll
5 , replaceAll
7 , splitAll
6 , splitAll
8− , needlePrefix
9 ) where
7 ) where
10
8
11−
12−--------------------------------------------------------------------------------
13import Data.Char (isSpace)
9import Data.Char (isSpace)
14−import Data.List (isPrefixOf)
15import Data.Maybe (listToMaybe)
10import Data.Maybe (listToMaybe)
16−import Text.Regex.TDFA ((=~~))
17
11
12+import Text.Regex.TDFA ((=~~))
18
13
19−--------------------------------------------------------------------------------
20-- | Trim a string (drop spaces, tabs and newlines at both sides).
14-- | Trim a string (drop spaces, tabs and newlines at both sides).
15+--
21trim :: String -> String
16trim :: String -> String
22trim = reverse . trim' . reverse . trim'
17trim = reverse . trim' . reverse . trim'
23 where
18 where
24 trim' = dropWhile isSpace
19 trim' = dropWhile isSpace
25
20
26−
27−--------------------------------------------------------------------------------
28-- | A simple (but inefficient) regex replace funcion
21-- | A simple (but inefficient) regex replace funcion
22+--
29replaceAll :: String -- ^ Pattern
23replaceAll :: String -- ^ Pattern
30− -> (String -> String) -- ^ Replacement (called on match)
24+ -> (String -> String) -- ^ Replacement (called on capture)
31 -> String -- ^ Source string
25 -> String -- ^ Source string
32 -> String -- ^ Result
26 -> String -- ^ Result
33replaceAll pattern f source = replaceAll' source
27replaceAll pattern f source = replaceAll' source
@@ -39,10 +33,9 @@ replaceAll pattern f source = replaceAll' source
39 (capture, after) = splitAt l tmp
33 (capture, after) = splitAt l tmp
40 in before ++ f capture ++ replaceAll' after
34 in before ++ f capture ++ replaceAll' after
41
35
42−
43−--------------------------------------------------------------------------------
44-- | A simple regex split function. The resulting list will contain no empty
36-- | A simple regex split function. The resulting list will contain no empty
45-- strings.
37-- strings.
38+--
46splitAll :: String -- ^ Pattern
39splitAll :: String -- ^ Pattern
47 -> String -- ^ String to split
40 -> String -- ^ String to split
48 -> [String] -- ^ Result
41 -> [String] -- ^ Result
@@ -53,26 +46,3 @@ splitAll pattern = filter (not . null) . splitAll'
53 Just (o, l) ->
46 Just (o, l) ->
54 let (before, tmp) = splitAt o src
47 let (before, tmp) = splitAt o src
55 in before : splitAll' (drop l tmp)
48 in before : splitAll' (drop l tmp)
56−
57−
58−
59−--------------------------------------------------------------------------------
60−-- | Find the first instance of needle (must be non-empty) in haystack. We
61−-- return the prefix of haystack before needle is matched.
62−--
63−-- Examples:
64−--
65−-- > needlePrefix "cd" "abcde" = "ab"
66−--
67−-- > needlePrefix "ab" "abc" = ""
68−--
69−-- > needlePrefix "ab" "xxab" = "xx"
70−--
71−-- > needlePrefix "a" "xx" = "xx"
72−needlePrefix :: String -> String -> Maybe String
73−needlePrefix needle haystack = go [] haystack
74− where
75− go _ [] = Nothing
76− go acc xss@(x:xs)
77− | needle `isPrefixOf` xss = Just $ reverse acc
78− | otherwise = go (x : acc) xs
lib/Hakyll/Core/Writable.hs → src/Hakyll/Core/Writable.hsrenamed

The content is the same.

src/Hakyll/Init.hsnew file+37
@@ -0,0 +1,37 @@
1+--------------------------------------------------------------------------------
2+module Main
3+ ( main
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
8+import Control.Monad (forM_)
9+import System.Directory (copyFile)
10+import System.Environment (getArgs, getProgName)
11+import System.Exit (exitFailure)
12+import System.FilePath ((</>))
13+
14+
15+--------------------------------------------------------------------------------
16+import Hakyll.Core.Util.File
17+import Paths_hakyll
18+
19+
20+--------------------------------------------------------------------------------
21+main :: IO ()
22+main = do
23+ progName <- getProgName
24+ args <- getArgs
25+ srcDir <- getDataFileName "example"
26+ files <- getRecursiveContents srcDir
27+
28+ case args of
29+ [dstDir] -> forM_ files $ \file -> do
30+ let dst = dstDir </> file
31+ src = srcDir </> file
32+ putStrLn $ "Creating " ++ dst
33+ makeDirectories dst
34+ copyFile src dst
35+ _ -> do
36+ putStrLn $ "Usage: " ++ progName ++ " <directory>"
37+ exitFailure
src/Hakyll/Main.hsnew file+106
@@ -0,0 +1,106 @@
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+
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
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
45+ Check _ _ -> Commands.check conf verbosity' check'
46+ Clean _ -> Commands.clean conf
47+ Deploy _ -> Commands.deploy conf
48+ Help _ -> showHelp
49+ Preview _ p -> Commands.preview conf verbosity' rules p
50+ Rebuild _ -> Commands.rebuild conf verbosity' rules
51+ Server _ _ -> Commands.server conf (port args')
52+
53+
54+--------------------------------------------------------------------------------
55+-- | Show usage information.
56+showHelp :: IO ()
57+showHelp = print $ CA.helpText [] CA.HelpFormatOne $ cmdArgsMode hakyllArgs
58+
59+
60+--------------------------------------------------------------------------------
61+data HakyllArgs
62+ = Build {verbose :: Bool}
63+ | Check {verbose :: Bool, internal_links :: Bool}
64+ | Clean {verbose :: Bool}
65+ | Deploy {verbose :: Bool}
66+ | Help {verbose :: Bool}
67+ | Preview {verbose :: Bool, port :: Int}
68+ | Rebuild {verbose :: Bool}
69+ | Server {verbose :: Bool, port :: Int}
70+ deriving (Data, Typeable, Show)
71+
72+
73+--------------------------------------------------------------------------------
74+hakyllArgs :: HakyllArgs
75+hakyllArgs = modes
76+ [ (Build $ verboseFlag def) &= help "Generate the site"
77+ , (Check (verboseFlag def) (False &= help "Check internal links only")) &=
78+ help "Validate the site output"
79+ , (Clean $ verboseFlag def) &= help "Clean up and remove cache"
80+ , (Deploy $ verboseFlag def) &= help "Upload/deploy your site"
81+ , (Help $ verboseFlag def) &= help "Show this message" &= auto
82+ , (Preview (verboseFlag def) (portFlag 8000)) &=
83+ help "Start a preview server and autocompile on changes"
84+ , (Rebuild $ verboseFlag def) &= help "Clean and build again"
85+ , (Server (verboseFlag def) (portFlag 8000)) &=
86+ help "Start a preview server"
87+ ] &= help "Hakyll static site compiler" &= program progName
88+
89+
90+--------------------------------------------------------------------------------
91+verboseFlag :: Data a => a -> a
92+verboseFlag x = x &= help "Run in verbose mode"
93+{-# INLINE verboseFlag #-}
94+
95+
96+--------------------------------------------------------------------------------
97+portFlag :: Data a => a -> a
98+portFlag x = x &= help "Port to listen on"
99+{-# INLINE portFlag #-}
100+
101+
102+--------------------------------------------------------------------------------
103+-- | This is necessary because not everyone calls their program the same...
104+progName :: String
105+progName = unsafePerformIO getProgName
106+{-# NOINLINE progName #-}
src/Hakyll/Preview/Poll.hsnew file+48
@@ -0,0 +1,48 @@
1+--------------------------------------------------------------------------------
2+-- | Interval-based implementation of preview polling
3+{-# LANGUAGE CPP #-}
4+module Hakyll.Preview.Poll
5+ ( previewPoll
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Applicative ((<$>))
11+import Control.Concurrent (threadDelay)
12+import Control.Monad (filterM)
13+#if MIN_VERSION_directory(1,2,0)
14+import Data.Time (getCurrentTime)
15+#else
16+import System.Time (getClockTime)
17+#endif
18+import System.Directory (doesFileExist, getModificationTime)
19+
20+
21+--------------------------------------------------------------------------------
22+import Hakyll.Core.Configuration
23+
24+
25+--------------------------------------------------------------------------------
26+-- | A preview thread that periodically recompiles the site.
27+previewPoll :: Configuration -- ^ Configuration
28+ -> IO [FilePath] -- ^ Updating action
29+ -> IO () -- ^ Can block forever
30+previewPoll _ update = do
31+#if MIN_VERSION_directory(1,2,0)
32+ time <- getCurrentTime
33+#else
34+ time <- getClockTime
35+#endif
36+ loop time =<< update
37+ where
38+ delay = 1000000
39+ loop time files = do
40+ threadDelay delay
41+ files' <- filterM doesFileExist files
42+ filesTime <- case files' of
43+ [] -> return time
44+ _ -> maximum <$> mapM getModificationTime files'
45+
46+ if filesTime > time || files' /= files
47+ then loop filesTime =<< update
48+ else loop time files'
src/Hakyll/Preview/Server.hsnew file+43
@@ -0,0 +1,43 @@
1+--------------------------------------------------------------------------------
2+-- | Implements a basic static file server for previewing options
3+{-# LANGUAGE OverloadedStrings #-}
4+module Hakyll.Preview.Server
5+ ( staticServer
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Monad.Trans (liftIO)
11+import qualified Snap.Core as Snap
12+import qualified Snap.Http.Server as Snap
13+import qualified Snap.Util.FileServe as Snap
14+
15+
16+--------------------------------------------------------------------------------
17+-- | Serve a given directory
18+static :: FilePath -- ^ Directory to serve
19+ -> (FilePath -> IO ()) -- ^ Pre-serve hook
20+ -> Snap.Snap ()
21+static directory preServe =
22+ Snap.serveDirectoryWith directoryConfig directory
23+ where
24+ directoryConfig :: Snap.DirectoryConfig Snap.Snap
25+ directoryConfig = Snap.fancyDirectoryConfig
26+ { Snap.preServeHook = liftIO . preServe
27+ }
28+
29+
30+--------------------------------------------------------------------------------
31+-- | Main method, runs a static server in the given directory
32+staticServer :: FilePath -- ^ Directory to serve
33+ -> (FilePath -> IO ()) -- ^ Pre-serve hook
34+ -> Int -- ^ Port to listen on
35+ -> IO () -- ^ Blocks forever
36+staticServer directory preServe port =
37+ Snap.httpServe config $ static directory preServe
38+ where
39+ -- Snap server config
40+ config = Snap.setPort port
41+ $ Snap.setAccessLog Snap.ConfigNoLog
42+ $ Snap.setErrorLog Snap.ConfigNoLog
43+ $ Snap.emptyConfig
src/Hakyll/Web/CompressCss.hsnew file+60
@@ -0,0 +1,60 @@
1+--------------------------------------------------------------------------------
2+-- | Module used for CSS compression. The compression is currently in a simple
3+-- state, but would typically reduce the number of bytes by about 25%.
4+module Hakyll.Web.CompressCss
5+ ( compressCssCompiler
6+ , compressCss
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>))
12+import Data.Char (isSpace)
13+import Data.List (isPrefixOf)
14+
15+
16+--------------------------------------------------------------------------------
17+import Hakyll.Core.Compiler
18+import Hakyll.Core.Item
19+import Hakyll.Core.Util.String
20+
21+
22+--------------------------------------------------------------------------------
23+-- | Compiler form of 'compressCss'
24+compressCssCompiler :: Compiler (Item String)
25+compressCssCompiler = fmap compressCss <$> getResourceString
26+
27+
28+--------------------------------------------------------------------------------
29+-- | Compress CSS to speed up your site.
30+compressCss :: String -> String
31+compressCss = compressSeparators . stripComments . compressWhitespace
32+
33+
34+--------------------------------------------------------------------------------
35+-- | Compresses certain forms of separators.
36+compressSeparators :: String -> String
37+compressSeparators =
38+ replaceAll "; *}" (const "}") .
39+ replaceAll " *([{};:]) *" (take 1 . dropWhile isSpace) .
40+ replaceAll ";+" (const ";")
41+
42+
43+--------------------------------------------------------------------------------
44+-- | Compresses all whitespace.
45+compressWhitespace :: String -> String
46+compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
47+
48+
49+--------------------------------------------------------------------------------
50+-- | Function that strips CSS comments away.
51+stripComments :: String -> String
52+stripComments [] = []
53+stripComments str
54+ | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
55+ | otherwise = head str : stripComments (drop 1 str)
56+ where
57+ eatComments str'
58+ | null str' = []
59+ | isPrefixOf "*/" str' = drop 2 str'
60+ | otherwise = eatComments $ drop 1 str'
lib/Hakyll/Web/Feed.hs → src/Hakyll/Web/Feed.hsrenamed+27−71
@@ -1,6 +1,3 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE TemplateHaskell #-}
3−
4--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
5-- | A Module that allows easy rendering of RSS feeds.
2-- | A Module that allows easy rendering of RSS feeds.
6--
3--
@@ -23,40 +20,26 @@ module Hakyll.Web.Feed
23 ( FeedConfiguration (..)
20 ( FeedConfiguration (..)
24 , renderRss
21 , renderRss
25 , renderAtom
22 , renderAtom
26− , renderRssWithTemplates
27− , renderAtomWithTemplates
28 ) where
23 ) where
29
24
30
25
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
33import Hakyll.Core.Item
34import 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+import Hakyll.Web.Template.Read
38
39
39
40
40--------------------------------------------------------------------------------
41--------------------------------------------------------------------------------
41−import Data.FileEmbed (makeRelativeToProject)
42+import Paths_hakyll
42−
43−
44−--------------------------------------------------------------------------------
45−rssTemplate :: Template
46−rssTemplate =
47− $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate)
48−
49−rssItemTemplate :: Template
50−rssItemTemplate =
51− $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate)
52−
53−atomTemplate :: Template
54−atomTemplate =
55− $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate)
56−
57−atomItemTemplate :: Template
58−atomItemTemplate =
59− $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate)
60
43
61
44
62--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
@@ -77,27 +60,25 @@ data FeedConfiguration = FeedConfiguration
77
60
78--------------------------------------------------------------------------------
61--------------------------------------------------------------------------------
79-- | Abstract function to render any feed.
62-- | Abstract function to render any feed.
80−renderFeed :: Template -- ^ Default feed template
63+renderFeed :: FilePath -- ^ Feed template
81− -> Template -- ^ Default item template
64+ -> FilePath -- ^ Item template
82 -> FeedConfiguration -- ^ Feed configuration
65 -> FeedConfiguration -- ^ Feed configuration
83 -> Context String -- ^ Context for the items
66 -> Context String -- ^ Context for the items
84 -> [Item String] -- ^ Input items
67 -> [Item String] -- ^ Input items
85 -> Compiler (Item String) -- ^ Resulting item
68 -> Compiler (Item String) -- ^ Resulting item
86−renderFeed feedTpl itemTpl config itemContext items = do
69+renderFeed feedPath itemPath config itemContext items = do
87− protectedItems <- mapM (applyFilter protectCDATA) items
70+ feedTpl <- compilerUnsafeIO $ loadTemplate feedPath
71+ itemTpl <- compilerUnsafeIO $ loadTemplate itemPath
72+
88− body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
73+ body <- makeItem =<< applyTemplateList itemTpl itemContext' items
89 applyTemplate feedTpl feedContext body
74 applyTemplate feedTpl feedContext body
90 where
75 where
91− applyFilter :: (Monad m,Functor f) => (String -> String) -> f String -> m (f String)
76+ -- Auxiliary: load a template from a datafile
92− applyFilter tr str = return $ fmap tr str
77+ loadTemplate = fmap readTemplate . readFile <=< getDataFileName
93− protectCDATA :: String -> String
94− protectCDATA = replaceAll "]]>" (const "]]&gt;")
95
78
96 itemContext' = mconcat
79 itemContext' = mconcat
97− [ itemContext
98− , constField "root" (feedRoot config)
80+ [ constField "root" (feedRoot config)
99− , constField "authorName" (feedAuthorName config)
81+ , itemContext
100− , constField "authorEmail" (feedAuthorEmail config)
101 ]
82 ]
102
83
103 feedContext = mconcat
84 feedContext = mconcat
@@ -116,36 +97,7 @@ renderFeed feedTpl itemTpl config itemContext items = do
116 -- recent.
97 -- recent.
117 updatedField = field "updated" $ \_ -> case items of
98 updatedField = field "updated" $ \_ -> case items of
118 [] -> return "Unknown"
99 [] -> return "Unknown"
119− (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
100+ (x : _) -> unContext itemContext' "updated" x
120− StringField s -> return s
121− _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
122−
123−--------------------------------------------------------------------------------
124−-- | Render an RSS feed using given templates with a number of items.
125−renderRssWithTemplates ::
126− Template -- ^ Feed template
127− -> Template -- ^ Item template
128− -> FeedConfiguration -- ^ Feed configuration
129− -> Context String -- ^ Item context
130− -> [Item String] -- ^ Feed items
131− -> Compiler (Item String) -- ^ Resulting feed
132−renderRssWithTemplates feedTemplate itemTemplate config context = renderFeed
133− feedTemplate itemTemplate config
134− (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
135−
136−
137−--------------------------------------------------------------------------------
138−-- | Render an Atom feed using given templates with a number of items.
139−renderAtomWithTemplates ::
140− Template -- ^ Feed template
141− -> Template -- ^ Item template
142− -> FeedConfiguration -- ^ Feed configuration
143− -> Context String -- ^ Item context
144− -> [Item String] -- ^ Feed items
145− -> Compiler (Item String) -- ^ Resulting feed
146−renderAtomWithTemplates feedTemplate itemTemplate config context = renderFeed
147− feedTemplate itemTemplate config
148− (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
149
101
150
102
151--------------------------------------------------------------------------------
103--------------------------------------------------------------------------------
@@ -154,7 +106,9 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
154 -> Context String -- ^ Item context
106 -> Context String -- ^ Item context
155 -> [Item String] -- ^ Feed items
107 -> [Item String] -- ^ Feed items
156 -> Compiler (Item String) -- ^ Resulting feed
108 -> Compiler (Item String) -- ^ Resulting feed
157−renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
109+renderRss config context = renderFeed
110+ "templates/rss.xml" "templates/rss-item.xml" config
111+ (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
158
112
159
113
160--------------------------------------------------------------------------------
114--------------------------------------------------------------------------------
@@ -163,11 +117,13 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
163 -> Context String -- ^ Item context
117 -> Context String -- ^ Item context
164 -> [Item String] -- ^ Feed items
118 -> [Item String] -- ^ Feed items
165 -> Compiler (Item String) -- ^ Resulting feed
119 -> Compiler (Item String) -- ^ Resulting feed
166−renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
120+renderAtom config context = renderFeed
121+ "templates/atom.xml" "templates/atom-item.xml" config
122+ (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
167
123
168
124
169--------------------------------------------------------------------------------
125--------------------------------------------------------------------------------
170-- | Copies @$updated$@ from @$published$@ if it is not already set.
126-- | Copies @$updated$@ from @$published$@ if it is not already set.
171makeItemContext :: String -> Context a -> Context a
127makeItemContext :: String -> Context a -> Context a
172makeItemContext fmt context = mconcat
128makeItemContext fmt context = mconcat
173− [context, dateField "published" fmt, dateField "updated" fmt]
129+ [dateField "published" fmt, context, dateField "updated" fmt]
lib/Hakyll/Web/Html.hs → src/Hakyll/Web/Html.hsrenamed+10−43
@@ -3,7 +3,6 @@
3module Hakyll.Web.Html
3module Hakyll.Web.Html
4 ( -- * Generic
4 ( -- * Generic
5 withTags
5 withTags
6− , withTagList
7
6
8 -- * Headers
7 -- * Headers
9 , demoteHeaders
8 , demoteHeaders
@@ -25,23 +24,18 @@ module Hakyll.Web.Html
25import Data.Char (digitToInt, intToDigit,
24import Data.Char (digitToInt, intToDigit,
26 isDigit, toLower)
25 isDigit, toLower)
27import Data.List (isPrefixOf)
26import Data.List (isPrefixOf)
28−import qualified Data.Set as S
29−import System.FilePath.Posix (joinPath, splitPath,
27+import System.FilePath (joinPath, splitPath,
30 takeDirectory)
28 takeDirectory)
31import Text.Blaze.Html (toHtml)
29import Text.Blaze.Html (toHtml)
32import Text.Blaze.Html.Renderer.String (renderHtml)
30import Text.Blaze.Html.Renderer.String (renderHtml)
33import qualified Text.HTML.TagSoup as TS
31import qualified Text.HTML.TagSoup as TS
34−import Network.URI (isUnreserved, escapeURIString)
35
32
36
33
37--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
38-- | Map over all tags in the document
35-- | Map over all tags in the document
39withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
36withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
40−withTags = withTagList . map
37+withTags f = renderTags' . map f . TS.parseTags
41
38
42−-- | Map over all tags (as list) in the document
43−withTagList :: ([TS.Tag String] -> [TS.Tag String]) -> String -> String
44−withTagList f = renderTags' . f . parseTags'
45
39
46--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
47-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
41-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
@@ -59,7 +53,7 @@ demoteHeaders = withTags $ \tag -> case tag of
59
53
60--------------------------------------------------------------------------------
54--------------------------------------------------------------------------------
61isUrlAttribute :: String -> Bool
55isUrlAttribute :: String -> Bool
62−isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
56+isUrlAttribute = (`elem` ["src", "href"])
63
57
64
58
65--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
@@ -77,29 +71,12 @@ withUrls f = withTags tag
77 attr (k, v) = (k, if isUrlAttribute k then f v else v)
71 attr (k, v) = (k, if isUrlAttribute k then f v else v)
78
72
79
73
80−--------------------------------------------------------------------------------
81−-- | Customized TagSoup renderer. The default TagSoup renderer escape CSS
74+-- | Customized TagSoup renderer. (The default TagSoup renderer escape CSS
82−-- within style tags, and doesn't properly minimize.
75+-- within style tags.)
83renderTags' :: [TS.Tag String] -> String
76renderTags' :: [TS.Tag String] -> String
84−renderTags' = TS.renderTagsOptions TS.RenderOptions
77+renderTags' = TS.renderTagsOptions TS.renderOptions
85 { TS.optRawTag = (`elem` ["script", "style"]) . map toLower
78 { TS.optRawTag = (`elem` ["script", "style"]) . map toLower
86− , TS.optMinimize = (`S.member` minimize) . map toLower
79+ , TS.optMinimize = (`elem` ["br", "img"])
87− , TS.optEscape = id
88− }
89− where
90− -- A list of elements which must be minimized
91− minimize = S.fromList
92− [ "area", "br", "col", "embed", "hr", "img", "input", "meta", "link"
93− , "param"
94− ]
95−
96−
97−--------------------------------------------------------------------------------
98−-- | Customized TagSoup parser: do not decode any entities.
99−parseTags' :: String -> [TS.Tag String]
100−parseTags' = TS.parseTagsOptions (TS.parseOptions :: TS.ParseOptions String)
101− { TS.optEntityData = \(str, b) -> [TS.TagText $ "&" ++ str ++ [';' | b]]
102− , TS.optEntityAttrib = \(str, b) -> ("&" ++ str ++ [';' | b], [])
103 }
80 }
104
81
105
82
@@ -113,18 +90,9 @@ parseTags' = TS.parseTagsOptions (TS.parseOptions :: TS.ParseOptions String)
113-- Result:
90-- Result:
114--
91--
115-- > "/foo/bar.html"
92-- > "/foo/bar.html"
116−--
117−-- This also sanitizes the URL, e.g. converting spaces into '%20'
118toUrl :: FilePath -> String
93toUrl :: FilePath -> String
119−toUrl url = case url of
94+toUrl ('/' : xs) = '/' : xs
120− ('/' : xs) -> '/' : sanitize xs
95+toUrl url = '/' : url
121− xs -> '/' : sanitize xs
122− where
123− -- Everything but unreserved characters should be escaped as we are
124− -- sanitising the path therefore reserved characters which have a
125− -- meaning in URI does not appear. Special casing for `/`, because it has
126− -- a special meaning in FilePath as well as in URI.
127− sanitize = escapeURIString (\c -> c == '/' || isUnreserved c)
128
96
129
97
130--------------------------------------------------------------------------------
98--------------------------------------------------------------------------------
@@ -138,14 +106,13 @@ toSiteRoot = emptyException . joinPath . map parent
138 emptyException x = x
106 emptyException x = x
139 relevant "." = False
107 relevant "." = False
140 relevant "/" = False
108 relevant "/" = False
141− relevant "./" = False
142 relevant _ = True
109 relevant _ = True
143
110
144
111
145--------------------------------------------------------------------------------
112--------------------------------------------------------------------------------
146-- | Check if an URL links to an external HTTP(S) source
113-- | Check if an URL links to an external HTTP(S) source
147isExternal :: String -> Bool
114isExternal :: String -> Bool
148−isExternal url = any (flip isPrefixOf url) ["http://", "https://", "//"]
115+isExternal url = any (flip isPrefixOf url) ["http://", "https://"]
149
116
150
117
151--------------------------------------------------------------------------------
118--------------------------------------------------------------------------------
lib/Hakyll/Web/Html/RelativizeUrls.hs → src/Hakyll/Web/Html/RelativizeUrls.hsrenamed

The content is the same.

src/Hakyll/Web/Pandoc.hsnew file+131
@@ -0,0 +1,131 @@
1+--------------------------------------------------------------------------------
2+-- | Module exporting convenient pandoc bindings
3+module Hakyll.Web.Pandoc
4+ ( -- * The basic building blocks
5+ readPandoc
6+ , readPandocWith
7+ , writePandoc
8+ , writePandocWith
9+ , renderPandoc
10+ , renderPandocWith
11+
12+ -- * Derived compilers
13+ , pandocCompiler
14+ , pandocCompilerWith
15+ , pandocCompilerWithTransform
16+
17+ -- * Default options
18+ , defaultHakyllParserState
19+ , defaultHakyllWriterOptions
20+ ) where
21+
22+
23+--------------------------------------------------------------------------------
24+import Control.Applicative ((<$>))
25+import Text.Pandoc
26+
27+
28+--------------------------------------------------------------------------------
29+import Hakyll.Core.Compiler
30+import Hakyll.Core.Item
31+import Hakyll.Web.Pandoc.FileType
32+
33+
34+--------------------------------------------------------------------------------
35+-- | Read a string using pandoc, with the default options
36+readPandoc :: Item String -- ^ String to read
37+ -> Item Pandoc -- ^ Resulting document
38+readPandoc = readPandocWith defaultHakyllParserState
39+
40+
41+--------------------------------------------------------------------------------
42+-- | Read a string using pandoc, with the supplied options
43+readPandocWith :: ParserState -- ^ Parser options
44+ -> Item String -- ^ String to read
45+ -> Item Pandoc -- ^ Resulting document
46+readPandocWith state item = fmap (reader state (itemFileType item)) item
47+ where
48+ reader s t = case t of
49+ Html -> readHtml s
50+ LaTeX -> readLaTeX s
51+ LiterateHaskell t' -> reader s {stateLiterateHaskell = True} t'
52+ Markdown -> readMarkdown s
53+ Rst -> readRST s
54+ Textile -> readTextile s
55+ _ -> error $
56+ "Hakyll.Web.readPandocWith: I don't know how to read a file of the " ++
57+ "type " ++ show t ++ " for: " ++ show (itemIdentifier item)
58+
59+
60+--------------------------------------------------------------------------------
61+-- | Write a document (as HTML) using pandoc, with the default options
62+writePandoc :: Item Pandoc -- ^ Document to write
63+ -> Item String -- ^ Resulting HTML
64+writePandoc = writePandocWith defaultHakyllWriterOptions
65+
66+
67+--------------------------------------------------------------------------------
68+-- | Write a document (as HTML) using pandoc, with the supplied options
69+writePandocWith :: WriterOptions -- ^ Writer options for pandoc
70+ -> Item Pandoc -- ^ Document to write
71+ -> Item String -- ^ Resulting HTML
72+writePandocWith options = fmap $ writeHtmlString options
73+
74+
75+--------------------------------------------------------------------------------
76+-- | Render the resource using pandoc
77+renderPandoc :: Item String -> Item String
78+renderPandoc =
79+ renderPandocWith defaultHakyllParserState defaultHakyllWriterOptions
80+
81+
82+--------------------------------------------------------------------------------
83+-- | Render the resource using pandoc
84+renderPandocWith :: ParserState -> WriterOptions -> Item String -> Item String
85+renderPandocWith state options = writePandocWith options . readPandocWith state
86+
87+
88+--------------------------------------------------------------------------------
89+-- | Read a page render using pandoc
90+pandocCompiler :: Compiler (Item String)
91+pandocCompiler =
92+ pandocCompilerWith defaultHakyllParserState defaultHakyllWriterOptions
93+
94+
95+--------------------------------------------------------------------------------
96+-- | A version of 'pandocCompiler' which allows you to specify your own pandoc
97+-- options
98+pandocCompilerWith :: ParserState -> WriterOptions -> Compiler (Item String)
99+pandocCompilerWith state options = pandocCompilerWithTransform state options id
100+
101+
102+--------------------------------------------------------------------------------
103+-- | An extension of 'pandocCompilerWith' which allows you to specify a custom
104+-- pandoc transformation for the content
105+pandocCompilerWithTransform :: ParserState -> WriterOptions
106+ -> (Pandoc -> Pandoc)
107+ -> Compiler (Item String)
108+pandocCompilerWithTransform state options f = cached cacheName $
109+ writePandocWith options . fmap f . readPandocWith state <$> getResourceBody
110+ where
111+ cacheName = "Hakyll.Web.Page.pageCompilerWithPandoc"
112+
113+
114+--------------------------------------------------------------------------------
115+-- | The default reader options for pandoc parsing in hakyll
116+defaultHakyllParserState :: ParserState
117+defaultHakyllParserState = defaultParserState
118+ { -- The following option causes pandoc to read smart typography, a nice
119+ -- and free bonus.
120+ stateSmart = True
121+ }
122+
123+
124+--------------------------------------------------------------------------------
125+-- | The default writer options for pandoc rendering in hakyll
126+defaultHakyllWriterOptions :: WriterOptions
127+defaultHakyllWriterOptions = defaultWriterOptions
128+ { -- This option causes literate haskell to be written using '>' marks in
129+ -- html, which I think is a good default.
130+ writerLiterateHaskell = True
131+ }
lib/Hakyll/Web/Pandoc/Biblio.hs → src/Hakyll/Web/Pandoc/Biblio.hsrenamed+23−47
@@ -4,11 +4,9 @@
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 'readPandocBiblio'. This function also
7+-- refer to these files when you use 'pageReadPandocBiblio'. This function also
8−-- takes the reader options for completeness -- you can use
8+-- takes a parser state for completeness -- you can use
9−-- 'defaultHakyllReaderOptions' if you're unsure.
9+-- 'defaultHakyllParserState' if you're unsure.
10−-- 'pandocBiblioCompiler' is a convenience wrapper which works like 'pandocCompiler',
11−-- but also takes paths to compiled bibliography and csl files.
12{-# LANGUAGE Arrows #-}
10{-# LANGUAGE Arrows #-}
13{-# LANGUAGE DeriveDataTypeable #-}
11{-# LANGUAGE DeriveDataTypeable #-}
14{-# LANGUAGE GeneralizedNewtypeDeriving #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -18,26 +16,24 @@ module Hakyll.Web.Pandoc.Biblio
18 , Biblio (..)
16 , Biblio (..)
19 , biblioCompiler
17 , biblioCompiler
20 , readPandocBiblio
18 , readPandocBiblio
21− , pandocBiblioCompiler
22 ) where
19 ) where
23
20
24
21
25--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
26−import Control.Monad (liftM, replicateM)
23+import Control.Applicative ((<$>))
27−import Data.Binary (Binary (..))
24+import Data.Binary (Binary (..))
28−import Data.Typeable (Typeable)
25+import Data.Typeable (Typeable)
26+import qualified Text.CSL as CSL
27+import Text.Pandoc (Pandoc, ParserState (..))
28+import Text.Pandoc.Biblio (processBiblio)
29+
30+
31+--------------------------------------------------------------------------------
29import Hakyll.Core.Compiler
32import Hakyll.Core.Compiler
30−import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Identifier
33import Hakyll.Core.Identifier
32import Hakyll.Core.Item
34import Hakyll.Core.Item
33−import Hakyll.Core.Provider
34import Hakyll.Core.Writable
35import Hakyll.Core.Writable
35import Hakyll.Web.Pandoc
36import Hakyll.Web.Pandoc
36−import Hakyll.Web.Pandoc.Binary ()
37−import qualified Text.CSL as CSL
38−import Text.CSL.Pandoc (processCites)
39−import Text.Pandoc (Pandoc, ReaderOptions (..),
40− enableExtension, Extension (..))
41
37
42
38
43--------------------------------------------------------------------------------
39--------------------------------------------------------------------------------
@@ -70,10 +66,8 @@ newtype Biblio = Biblio [CSL.Reference]
70--------------------------------------------------------------------------------
66--------------------------------------------------------------------------------
71instance Binary Biblio where
67instance Binary Biblio where
72 -- Ugly.
68 -- Ugly.
73− get = do
69+ get = Biblio . read <$> get
74− len <- get
75− Biblio <$> replicateM len get
76− put (Biblio rs) = put (length rs) >> mapM_ put rs
70+ put (Biblio rs) = put $ show rs
77
71
78
72
79--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
@@ -85,42 +79,24 @@ instance Writable Biblio where
85--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
86biblioCompiler :: Compiler (Item Biblio)
80biblioCompiler :: Compiler (Item Biblio)
87biblioCompiler = do
81biblioCompiler = do
88− filePath <- getResourceFilePath
82+ filePath <- toFilePath <$> getUnderlying
89− makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile idpred filePath)
83+ makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile filePath)
90− where
91− -- This is a filter on citations. We include all citations.
92− idpred = const True
93
84
94
85
95--------------------------------------------------------------------------------
86--------------------------------------------------------------------------------
96−readPandocBiblio :: ReaderOptions
87+readPandocBiblio :: ParserState
97 -> Item CSL
88 -> Item CSL
98 -> Item Biblio
89 -> Item Biblio
99 -> (Item String)
90 -> (Item String)
100 -> Compiler (Item Pandoc)
91 -> Compiler (Item Pandoc)
101−readPandocBiblio ropt csl biblio item = do
92+readPandocBiblio state csl biblio item = do
102− -- Parse CSL file, if given
103− provider <- compilerProvider <$> compilerAsk
104− style <- unsafeCompiler $
105− CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
106−
107 -- We need to know the citation keys, add then *before* actually parsing the
93 -- We need to know the citation keys, add then *before* actually parsing the
108 -- actual page. If we don't do this, pandoc won't even consider them
94 -- actual page. If we don't do this, pandoc won't even consider them
109 -- citations!
95 -- citations!
110 let Biblio refs = itemBody biblio
96 let Biblio refs = itemBody biblio
97+ cits = map CSL.refId refs
98+ state' = state {stateCitations = stateCitations state ++ cits}
111− pandoc <- itemBody <$> readPandocWith ropt item
99+ pandoc = itemBody $ readPandocWith state' item
112− let pandoc' = processCites style refs pandoc
100+ cslPath = toFilePath $ itemIdentifier csl
113−
101+ pandoc' <- unsafeCompiler $ processBiblio cslPath Nothing refs pandoc
114 return $ fmap (const pandoc') item
102 return $ fmap (const pandoc') item
115−
116−--------------------------------------------------------------------------------
117−pandocBiblioCompiler :: String -> String -> Compiler (Item String)
118−pandocBiblioCompiler cslFileName bibFileName = do
119− csl <- load $ fromFilePath cslFileName
120− bib <- load $ fromFilePath bibFileName
121− liftM writePandoc
122− (getResourceBody >>= readPandocBiblio ropt csl bib)
123− where ropt = defaultHakyllReaderOptions
124− { -- The following option enables citation rendering
125− readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
126− }
lib/Hakyll/Web/Pandoc/FileType.hs → src/Hakyll/Web/Pandoc/FileType.hsrenamed+21−30
@@ -8,7 +8,7 @@ module Hakyll.Web.Pandoc.FileType
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import System.FilePath (splitExtension)
11+import System.FilePath (takeExtension)
12
12
13
13
14--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
@@ -22,12 +22,10 @@ import Hakyll.Core.Item
22data FileType
22data FileType
23 = Binary
23 = Binary
24 | Css
24 | Css
25− | DocBook
26 | Html
25 | Html
27 | LaTeX
26 | LaTeX
28 | LiterateHaskell FileType
27 | LiterateHaskell FileType
29 | Markdown
28 | Markdown
30− | MediaWiki
31 | OrgMode
29 | OrgMode
32 | PlainText
30 | PlainText
33 | Rst
31 | Rst
@@ -38,34 +36,27 @@ data FileType
38--------------------------------------------------------------------------------
36--------------------------------------------------------------------------------
39-- | Get the file type for a certain file. The type is determined by extension.
37-- | Get the file type for a certain file. The type is determined by extension.
40fileType :: FilePath -> FileType
38fileType :: FilePath -> FileType
41−fileType = uncurry fileType' . splitExtension
39+fileType = fileType' . takeExtension
42 where
40 where
43− fileType' _ ".css" = Css
41+ fileType' ".css" = Css
44− fileType' _ ".dbk" = DocBook
42+ fileType' ".htm" = Html
45− fileType' _ ".htm" = Html
46− fileType' _ ".html" = Html
43+ fileType' ".html" = Html
47− fileType' f ".lhs" = LiterateHaskell $ case fileType f of
44+ fileType' ".lhs" = LiterateHaskell Markdown
48− -- If no extension is given, default to Markdown + LiterateHaskell
49− Binary -> Markdown
50− -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
51− x -> x
52− fileType' _ ".markdown" = Markdown
45+ fileType' ".markdown" = Markdown
53− fileType' _ ".mediawiki" = MediaWiki
54− fileType' _ ".md" = Markdown
46+ fileType' ".md" = Markdown
55− fileType' _ ".mdn" = Markdown
47+ fileType' ".mdn" = Markdown
56− fileType' _ ".mdown" = Markdown
48+ fileType' ".mdown" = Markdown
57− fileType' _ ".mdwn" = Markdown
49+ fileType' ".mdwn" = Markdown
58− fileType' _ ".mkd" = Markdown
50+ fileType' ".mkd" = Markdown
59− fileType' _ ".mkdwn" = Markdown
51+ fileType' ".mkdwn" = Markdown
60− fileType' _ ".org" = OrgMode
52+ fileType' ".org" = OrgMode
61− fileType' _ ".page" = Markdown
53+ fileType' ".page" = Markdown
62− fileType' _ ".rst" = Rst
54+ fileType' ".rst" = Rst
63− fileType' _ ".tex" = LaTeX
55+ fileType' ".tex" = LaTeX
64− fileType' _ ".text" = PlainText
56+ fileType' ".text" = PlainText
65− fileType' _ ".textile" = Textile
57+ fileType' ".textile" = Textile
66− fileType' _ ".txt" = PlainText
58+ fileType' ".txt" = PlainText
67− fileType' _ ".wiki" = MediaWiki
68− fileType' _ _ = Binary -- Treat unknown files as binary
59+ fileType' _ = Binary -- Treat unknown files as binary
69
60
70
61
71--------------------------------------------------------------------------------
62--------------------------------------------------------------------------------
lib/Hakyll/Web/Tags.hs → src/Hakyll/Web/Tags.hsrenamed+43−116
@@ -16,7 +16,7 @@
16-- > its populations have been damaged greatly by Nuclear...
16-- > its populations have been damaged greatly by Nuclear...
17--
17--
18-- To use categories, use the 'buildCategories' function. Categories are
18-- To use categories, use the 'buildCategories' function. Categories are
19−-- determined by the directory a page is in, for example, the post
19+-- determined by the direcetory a page is in, for example, the post
20--
20--
21-- > posts/coding/2010-01-28-hakyll-categories.markdown
21-- > posts/coding/2010-01-28-hakyll-categories.markdown
22--
22--
@@ -41,21 +41,15 @@
41{-# LANGUAGE GeneralizedNewtypeDeriving #-}
41{-# LANGUAGE GeneralizedNewtypeDeriving #-}
42{-# LANGUAGE OverloadedStrings #-}
42{-# LANGUAGE OverloadedStrings #-}
43module Hakyll.Web.Tags
43module Hakyll.Web.Tags
44− ( Tags (..)
44+ ( Tags
45 , getTags
45 , getTags
46− , getCategory
47 , buildTagsWith
46 , buildTagsWith
48 , buildTags
47 , buildTags
49 , buildCategories
48 , buildCategories
50 , tagsRules
49 , tagsRules
51− , renderTags
52 , renderTagCloud
50 , renderTagCloud
53− , renderTagCloudWith
54− , tagCloudField
55− , tagCloudFieldWith
56 , renderTagList
51 , renderTagList
57 , tagsField
52 , tagsField
58− , tagsFieldWith
59 , categoryField
53 , categoryField
60 , sortTagsBy
54 , sortTagsBy
61 , caseInsensitiveTags
55 , caseInsensitiveTags
@@ -64,14 +58,14 @@ module Hakyll.Web.Tags
64
58
65--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
66import Control.Arrow ((&&&))
60import Control.Arrow ((&&&))
67−import Control.Monad (foldM, forM, forM_, mplus)
61+import Control.Monad (foldM, forM, forM_)
68import Data.Char (toLower)
62import Data.Char (toLower)
69import Data.List (intercalate, intersperse,
63import Data.List (intercalate, intersperse,
70 sortBy)
64 sortBy)
71import qualified Data.Map as M
65import qualified Data.Map as M
72import Data.Maybe (catMaybes, fromMaybe)
66import Data.Maybe (catMaybes, fromMaybe)
67+import Data.Monoid (mconcat)
73import Data.Ord (comparing)
68import Data.Ord (comparing)
74−import qualified Data.Set as S
75import System.FilePath (takeBaseName, takeDirectory)
69import System.FilePath (takeBaseName, takeDirectory)
76import Text.Blaze.Html (toHtml, toValue, (!))
70import Text.Blaze.Html (toHtml, toValue, (!))
77import Text.Blaze.Html.Renderer.String (renderHtml)
71import Text.Blaze.Html.Renderer.String (renderHtml)
@@ -88,8 +82,8 @@ import Hakyll.Core.Item
88import Hakyll.Core.Metadata
82import Hakyll.Core.Metadata
89import Hakyll.Core.Rules
83import Hakyll.Core.Rules
90import Hakyll.Core.Util.String
84import Hakyll.Core.Util.String
91−import Hakyll.Web.Html
92import Hakyll.Web.Template.Context
85import Hakyll.Web.Template.Context
86+import Hakyll.Web.Html
93
87
94
88
95--------------------------------------------------------------------------------
89--------------------------------------------------------------------------------
@@ -98,22 +92,19 @@ data Tags = Tags
98 { tagsMap :: [(String, [Identifier])]
92 { tagsMap :: [(String, [Identifier])]
99 , tagsMakeId :: String -> Identifier
93 , tagsMakeId :: String -> Identifier
100 , tagsDependency :: Dependency
94 , tagsDependency :: Dependency
101− }
95+ } deriving (Show)
102
96
103
97
104--------------------------------------------------------------------------------
98--------------------------------------------------------------------------------
105-- | Obtain tags from a page in the default way: parse them from the @tags@
99-- | Obtain tags from a page in the default way: parse them from the @tags@
106−-- metadata field. This can either be a list or a comma-separated string.
100+-- metadata field.
107getTags :: MonadMetadata m => Identifier -> m [String]
101getTags :: MonadMetadata m => Identifier -> m [String]
108getTags identifier = do
102getTags identifier = do
109 metadata <- getMetadata identifier
103 metadata <- getMetadata identifier
110− return $ fromMaybe [] $
111− (lookupStringList "tags" metadata) `mplus`
112− (map trim . splitAll "," <$> lookupString "tags" metadata)
104+ return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata
113−
114
105
115--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
116−-- | Obtain category from a page.
107+-- | Obtain categories from a page.
117getCategory :: MonadMetadata m => Identifier -> m [String]
108getCategory :: MonadMetadata m => Identifier -> m [String]
118getCategory = return . return . takeBaseName . takeDirectory . toFilePath
109getCategory = return . return . takeBaseName . takeDirectory . toFilePath
119
110
@@ -128,8 +119,7 @@ buildTagsWith :: MonadMetadata m
128buildTagsWith f pattern makeId = do
119buildTagsWith f pattern makeId = do
129 ids <- getMatches pattern
120 ids <- getMatches pattern
130 tagMap <- foldM addTags M.empty ids
121 tagMap <- foldM addTags M.empty ids
131− let set' = S.fromList ids
132− return $ Tags (M.toList tagMap) makeId (PatternDependency pattern set')
122+ return $ Tags (M.toList tagMap) makeId (PatternDependency pattern ids)
133 where
123 where
134 -- Create a tag map for one page
124 -- Create a tag map for one page
135 addTags tagMap id' = do
125 addTags tagMap id' = do
@@ -153,13 +143,13 @@ buildCategories = buildTagsWith getCategory
153tagsRules :: Tags -> (String -> Pattern -> Rules ()) -> Rules ()
143tagsRules :: Tags -> (String -> Pattern -> Rules ()) -> Rules ()
154tagsRules tags rules =
144tagsRules tags rules =
155 forM_ (tagsMap tags) $ \(tag, identifiers) ->
145 forM_ (tagsMap tags) $ \(tag, identifiers) ->
156− rulesExtraDependencies [tagsDependency tags] $
157− create [tagsMakeId tags tag] $
146+ create [tagsMakeId tags tag] $
147+ rulesExtraDependencies [tagsDependency tags] $
158 rules tag $ fromList identifiers
148 rules tag $ fromList identifiers
159
149
160
150
161--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
162−-- | Render tags in HTML (the flexible higher-order function)
152+-- | Render tags in HTML
163renderTags :: (String -> String -> Int -> Int -> Int -> String)
153renderTags :: (String -> String -> Int -> Int -> Int -> String)
164 -- ^ Produce a tag item: tag, url, count, min count, max count
154 -- ^ Produce a tag item: tag, url, count, min count, max count
165 -> ([String] -> String)
155 -> ([String] -> String)
@@ -193,6 +183,7 @@ renderTags makeHtml concatHtml tags = do
193
183
194--------------------------------------------------------------------------------
184--------------------------------------------------------------------------------
195-- | Render a tag cloud in HTML
185-- | Render a tag cloud in HTML
186+-- TODO: Maybe produce a Context here
196renderTagCloud :: Double
187renderTagCloud :: Double
197 -- ^ Smallest font size, in percent
188 -- ^ Smallest font size, in percent
198 -> Double
189 -> Double
@@ -201,73 +192,19 @@ renderTagCloud :: Double
201 -- ^ Input tags
192 -- ^ Input tags
202 -> Compiler String
193 -> Compiler String
203 -- ^ Rendered cloud
194 -- ^ Rendered cloud
204−renderTagCloud = renderTagCloudWith makeLink (intercalate " ")
195+renderTagCloud minSize maxSize = renderTags makeLink (intercalate " ")
205 where
196 where
206− makeLink minSize maxSize tag url count min' max' =
197+ makeLink tag url count min' max' = renderHtml $
198+ H.a ! A.style (toValue $ "font-size: " ++ size count min' max')
199+ ! A.href (toValue url)
200+ $ toHtml tag
201+
207− -- Show the relative size of one 'count' in percent
202+ -- Show the relative size of one 'count' in percent
203+ size count min' max' =
208− let diff = 1 + fromIntegral max' - fromIntegral min'
204+ let diff = 1 + fromIntegral max' - fromIntegral min'
209 relative = (fromIntegral count - fromIntegral min') / diff
205 relative = (fromIntegral count - fromIntegral min') / diff
210− size = floor $ minSize + relative * (maxSize - minSize) :: Int
206+ size' = floor $ minSize + relative * (maxSize - minSize)
211− in renderHtml $
207+ in show (size' :: Int) ++ "%"
212− H.a ! A.style (toValue $ "font-size: " ++ show size ++ "%")
213− ! A.href (toValue url)
214− $ toHtml tag
215−
216−
217−--------------------------------------------------------------------------------
218−-- | Render a tag cloud in HTML
219−renderTagCloudWith :: (Double -> Double ->
220− String -> String -> Int -> Int -> Int -> String)
221− -- ^ Render a single tag link
222− -> ([String] -> String)
223− -- ^ Concatenate links
224− -> Double
225− -- ^ Smallest font size, in percent
226− -> Double
227− -- ^ Biggest font size, in percent
228− -> Tags
229− -- ^ Input tags
230− -> Compiler String
231− -- ^ Rendered cloud
232−renderTagCloudWith makeLink cat minSize maxSize =
233− renderTags (makeLink minSize maxSize) cat
234−
235−
236−--------------------------------------------------------------------------------
237−-- | Render a tag cloud in HTML as a context
238−tagCloudField :: String
239− -- ^ Destination key
240− -> Double
241− -- ^ Smallest font size, in percent
242− -> Double
243− -- ^ Biggest font size, in percent
244− -> Tags
245− -- ^ Input tags
246− -> Context a
247− -- ^ Context
248−tagCloudField key minSize maxSize tags =
249− field key $ \_ -> renderTagCloud minSize maxSize tags
250−
251−
252−--------------------------------------------------------------------------------
253−-- | Render a tag cloud in HTML as a context
254−tagCloudFieldWith :: String
255− -- ^ Destination key
256− -> (Double -> Double ->
257− String -> String -> Int -> Int -> Int -> String)
258− -- ^ Render a single tag link
259− -> ([String] -> String)
260− -- ^ Concatenate links
261− -> Double
262− -- ^ Smallest font size, in percent
263− -> Double
264− -- ^ Biggest font size, in percent
265− -> Tags
266− -- ^ Input tags
267− -> Context a
268− -- ^ Context
269−tagCloudFieldWith key makeLink cat minSize maxSize tags =
270− field key $ \_ -> renderTagCloudWith makeLink cat minSize maxSize tags
271
208
272
209
273--------------------------------------------------------------------------------
210--------------------------------------------------------------------------------
@@ -281,27 +218,27 @@ renderTagList = renderTags makeLink (intercalate ", ")
281
218
282
219
283--------------------------------------------------------------------------------
220--------------------------------------------------------------------------------
284−-- | Render tags with links with custom functions to get tags and to
221+-- | Render tags with links with custom function to get tags. It is typically
285−-- render links
222+-- together with 'getTags' like this:
223+--
224+-- > renderTagsFieldWith (customFunction . getTags)
225+-- > "tags" (fromCapture "tags/*")
286−tagsFieldWith :: (Identifier -> Compiler [String])
226+tagsFieldWith :: (Identifier -> Compiler [String]) -- ^ Get the tags
287− -- ^ Get the tags
288− -> (String -> (Maybe FilePath) -> Maybe H.Html)
289− -- ^ Render link for one tag
290− -> ([H.Html] -> H.Html)
291− -- ^ Concatenate tag links
292− -> String
293− -- ^ Destination field
227+ -> String -- ^ Destination key
294− -> Tags
295− -- ^ Tags structure
228+ -> Tags -- ^ Tags structure
296− -> Context a
297− -- ^ Resulting context
229+ -> Context a -- ^ Resulting context
298−tagsFieldWith getTags' renderLink cat key tags = field key $ \item -> do
230+tagsFieldWith getTags' key tags = field key $ \item -> do
299 tags' <- getTags' $ itemIdentifier item
231 tags' <- getTags' $ itemIdentifier item
300 links <- forM tags' $ \tag -> do
232 links <- forM tags' $ \tag -> do
301 route' <- getRoute $ tagsMakeId tags tag
233 route' <- getRoute $ tagsMakeId tags tag
302 return $ renderLink tag route'
234 return $ renderLink tag route'
303
235
304− return $ renderHtml $ cat $ catMaybes $ links
236+ return $ renderHtml $ mconcat $ intersperse ", " $ catMaybes $ links
237+ where
238+ -- Render one tag link
239+ renderLink _ Nothing = Nothing
240+ renderLink tag (Just filePath) = Just $
241+ H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag
305
242
306
243
307--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
@@ -309,8 +246,7 @@ tagsFieldWith getTags' renderLink cat key tags = field key $ \item -> do
309tagsField :: String -- ^ Destination key
246tagsField :: String -- ^ Destination key
310 -> Tags -- ^ Tags
247 -> Tags -- ^ Tags
311 -> Context a -- ^ Context
248 -> Context a -- ^ Context
312−tagsField =
313− tagsFieldWith getTags simpleRenderLink (mconcat . intersperse ", ")
249+tagsField = tagsFieldWith getTags
314
250
315
251
316--------------------------------------------------------------------------------
252--------------------------------------------------------------------------------
@@ -318,16 +254,7 @@ tagsField =
318categoryField :: String -- ^ Destination key
254categoryField :: String -- ^ Destination key
319 -> Tags -- ^ Tags
255 -> Tags -- ^ Tags
320 -> Context a -- ^ Context
256 -> Context a -- ^ Context
321−categoryField =
257+categoryField = tagsFieldWith getCategory
322− tagsFieldWith getCategory simpleRenderLink (mconcat . intersperse ", ")
323−
324−
325−--------------------------------------------------------------------------------
326−-- | Render one tag link
327−simpleRenderLink :: String -> (Maybe FilePath) -> Maybe H.Html
328−simpleRenderLink _ Nothing = Nothing
329−simpleRenderLink tag (Just filePath) =
330− Just $ H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag
331
258
332
259
333--------------------------------------------------------------------------------
260--------------------------------------------------------------------------------
src/Hakyll/Web/Template.hsnew file+122
@@ -0,0 +1,122 @@
1+-- | This module provides means for reading and applying 'Template's.
2+--
3+-- Templates are tools to convert items into a string. They are perfectly suited
4+-- for laying out your site.
5+--
6+-- Let's look at an example template:
7+--
8+-- > <html>
9+-- > <head>
10+-- > <title>My crazy homepage - $title$</title>
11+-- > </head>
12+-- > <body>
13+-- > <div id="header">
14+-- > <h1>My crazy homepage - $title$</h1>
15+-- > </div>
16+-- > <div id="content">
17+-- > $body$
18+-- > </div>
19+-- > <div id="footer">
20+-- > By reading this you agree that I now own your soul
21+-- > </div>
22+-- > </body>
23+-- > </html>
24+--
25+-- As you can see, the format is very simple -- @$key$@ is used to render the
26+-- @$key$@ field from the page, everything else is literally copied. If you want
27+-- to literally insert @\"$key$\"@ into your page (for example, when you're
28+-- writing a Hakyll tutorial) you can use
29+--
30+-- > <p>
31+-- > A literal $$key$$.
32+-- > </p>
33+--
34+-- Because of it's simplicity, these templates can be used for more than HTML:
35+-- you could make, for example, CSS or JS templates as well.
36+module Hakyll.Web.Template
37+ ( Template
38+ , templateCompiler
39+ , applyTemplate
40+ , loadAndApplyTemplate
41+ , applyAsTemplate
42+ , applyTemplateWith
43+ ) where
44+
45+
46+--------------------------------------------------------------------------------
47+import Control.Monad (forM, liftM)
48+import Data.Monoid (mappend)
49+import Prelude hiding (id)
50+
51+
52+--------------------------------------------------------------------------------
53+import Hakyll.Core.Compiler
54+import Hakyll.Core.Identifier
55+import Hakyll.Core.Item
56+import Hakyll.Web.Template.Context
57+import Hakyll.Web.Template.Internal
58+import Hakyll.Web.Template.Read
59+
60+
61+--------------------------------------------------------------------------------
62+-- | Read a template.
63+templateCompiler :: Compiler (Item Template)
64+templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
65+ item <- getResourceString
66+ return $ fmap readTemplate item
67+
68+
69+--------------------------------------------------------------------------------
70+applyTemplate :: Template -- ^ Template
71+ -> Context a -- ^ Context
72+ -> Item a -- ^ Page
73+ -> Compiler (Item String) -- ^ Resulting item
74+applyTemplate tpl context item = do
75+ -- Appending missingField gives better error messages
76+ let context' k x = unContext (context `mappend` missingField) k x
77+ body <- applyTemplateWith context' tpl item
78+ return $ itemSetBody body item
79+
80+
81+--------------------------------------------------------------------------------
82+-- | The following pattern is so common:
83+--
84+-- > tpl <- loadBody "templates/foo.html"
85+-- > someCompiler
86+-- > >>= applyTemplate tpl context
87+--
88+-- That we have a single function which does this:
89+--
90+-- > someCompiler
91+-- > >>= loadAndApplyTemplate "templates/foo.html" context
92+loadAndApplyTemplate :: Identifier -- ^ Template identifier
93+ -> Context a -- ^ Context
94+ -> Item a -- ^ Page
95+ -> Compiler (Item String) -- ^ Resulting item
96+loadAndApplyTemplate identifier context item = do
97+ tpl <- loadBody identifier
98+ applyTemplate tpl context item
99+
100+
101+--------------------------------------------------------------------------------
102+-- | It is also possible that you want to substitute @$key$@s within the body of
103+-- an item. This function does that by interpreting the item body as a template,
104+-- and then applying it to itself.
105+applyAsTemplate :: Context String -- ^ Context
106+ -> Item String -- ^ Item and template
107+ -> Compiler (Item String) -- ^ Resulting item
108+applyAsTemplate context item =
109+ let tpl = readTemplate $ itemBody item
110+ in applyTemplate tpl context item
111+
112+
113+--------------------------------------------------------------------------------
114+-- | Overloaded apply template function to work in an arbitrary Monad.
115+applyTemplateWith :: Monad m
116+ => (String -> a -> m String)
117+ -> Template -> a -> m String
118+applyTemplateWith context tpl x = liftM concat $
119+ forM (unTemplate tpl) $ \e -> case e of
120+ Chunk c -> return c
121+ Escaped -> return "$"
122+ Key k -> context k x
src/Hakyll/Web/Template/Context.hsnew file+215
@@ -0,0 +1,215 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Web.Template.Context
3+ ( Context (..)
4+ , mapContext
5+ , field
6+ , constField
7+ , functionField
8+
9+ , defaultContext
10+ , bodyField
11+ , metadataField
12+ , urlField
13+ , pathField
14+ , titleField
15+ , dateField
16+ , dateFieldWith
17+ , modificationTimeField
18+ , modificationTimeFieldWith
19+ , missingField
20+ ) where
21+
22+
23+--------------------------------------------------------------------------------
24+import Control.Applicative (Alternative (..), (<$>))
25+import Control.Monad (msum)
26+import Data.List (intercalate)
27+import qualified Data.Map as M
28+import Data.Monoid (Monoid (..))
29+import Data.Time.Clock (UTCTime (..))
30+import Data.Time.Format (formatTime, parseTime)
31+import System.FilePath (takeBaseName, takeFileName)
32+import System.Locale (TimeLocale, defaultTimeLocale)
33+
34+
35+--------------------------------------------------------------------------------
36+import Hakyll.Core.Compiler
37+import Hakyll.Core.Compiler.Internal
38+import Hakyll.Core.Identifier
39+import Hakyll.Core.Item
40+import Hakyll.Core.Metadata
41+import Hakyll.Core.Provider
42+import Hakyll.Core.Util.String (splitAll)
43+import Hakyll.Web.Html
44+
45+
46+--------------------------------------------------------------------------------
47+newtype Context a = Context
48+ { unContext :: String -> Item a -> Compiler String
49+ }
50+
51+
52+--------------------------------------------------------------------------------
53+instance Monoid (Context a) where
54+ mempty = missingField
55+ mappend (Context f) (Context g) = Context $ \k i -> f k i <|> g k i
56+
57+
58+--------------------------------------------------------------------------------
59+mapContext :: (String -> String) -> Context a -> Context a
60+mapContext f (Context g) = Context $ \k i -> f <$> g k i
61+
62+
63+--------------------------------------------------------------------------------
64+field :: String -> (Item a -> Compiler String) -> Context a
65+field key value = Context $ \k i -> if k == key then value i else empty
66+
67+
68+--------------------------------------------------------------------------------
69+constField :: String -> String -> Context a
70+constField key = field key . const . return
71+
72+
73+--------------------------------------------------------------------------------
74+functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
75+functionField name value = Context $ \k i -> case words k of
76+ [] -> empty
77+ (n : args)
78+ | n == name -> value args i
79+ | otherwise -> empty
80+
81+
82+--------------------------------------------------------------------------------
83+defaultContext :: Context String
84+defaultContext =
85+ bodyField "body" `mappend`
86+ metadataField `mappend`
87+ urlField "url" `mappend`
88+ pathField "path" `mappend`
89+ titleField "title" `mappend`
90+ missingField
91+
92+
93+--------------------------------------------------------------------------------
94+bodyField :: String -> Context String
95+bodyField key = field key $ return . itemBody
96+
97+
98+--------------------------------------------------------------------------------
99+-- | Map any field to its metadata value, if present
100+metadataField :: Context String
101+metadataField = Context $ \k i -> do
102+ metadata <- getMetadata $ itemIdentifier i
103+ maybe empty return $ M.lookup k metadata
104+
105+
106+--------------------------------------------------------------------------------
107+-- | Absolute url to the resulting item
108+urlField :: String -> Context a
109+urlField key = field key $
110+ fmap (maybe empty toUrl) . getRoute . itemIdentifier
111+
112+
113+--------------------------------------------------------------------------------
114+-- | Filepath of the underlying file of the item
115+pathField :: String -> Context a
116+pathField key = field key $ return . toFilePath . itemIdentifier
117+
118+
119+--------------------------------------------------------------------------------
120+-- | This title field takes the basename of the underlying file by default
121+titleField :: String -> Context a
122+titleField key = mapContext takeBaseName $ pathField key
123+
124+
125+--------------------------------------------------------------------------------
126+-- | When the metadata has a field called @published@ in one of the
127+-- following formats then this function can render the date.
128+--
129+-- * @Sun, 01 Feb 2000 13:00:00 UT@ (RSS date format)
130+--
131+-- * @2000-02-01T13:00:00Z@ (Atom date format)
132+--
133+-- * @February 1, 2000 1:00 PM@ (PM is usually uppercase)
134+--
135+-- * @February 1, 2000@ (assumes 12:00 AM for the time)
136+--
137+-- Alternatively, when the metadata has a field called @path@ in a
138+-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
139+-- and no @published@ metadata field set, this function can render
140+-- the date.
141+--
142+-- > renderDateField "date" "%B %e, %Y" "Date unknown"
143+--
144+-- Will render something like @January 32, 2010@.
145+--
146+dateField :: String -- ^ Key in which the rendered date should be placed
147+ -> String -- ^ Format to use on the date
148+ -> Context a -- ^ Resulting context
149+dateField = dateFieldWith defaultTimeLocale
150+
151+
152+--------------------------------------------------------------------------------
153+-- | This is an extended version of 'dateField' that allows you to
154+-- specify a time locale that is used for outputting the date. For more
155+-- details, see 'dateField'.
156+dateFieldWith :: TimeLocale -- ^ Output time locale
157+ -> String -- ^ Destination key
158+ -> String -- ^ Format to use on the date
159+ -> Context a -- ^ Resulting context
160+dateFieldWith locale key format = field key $ \i -> do
161+ time <- getUTC locale $ itemIdentifier i
162+ return $ formatTime locale format time
163+
164+
165+--------------------------------------------------------------------------------
166+-- | Parser to try to extract and parse the time from the @published@
167+-- field or from the filename. See 'renderDateField' for more information.
168+getUTC :: TimeLocale -- ^ Output time locale
169+ -> Identifier -- ^ Input page
170+ -> Compiler UTCTime -- ^ Parsed UTCTime
171+getUTC locale id' = do
172+ metadata <- getMetadata id'
173+ let tryField k fmt = M.lookup k metadata >>= parseTime' fmt
174+ fn = takeFileName $ toFilePath id'
175+
176+ maybe empty return $ msum $
177+ [tryField "published" fmt | fmt <- formats] ++
178+ [tryField "date" fmt | fmt <- formats] ++
179+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fn]
180+ where
181+ parseTime' = parseTime locale
182+ formats =
183+ [ "%a, %d %b %Y %H:%M:%S UT"
184+ , "%Y-%m-%dT%H:%M:%SZ"
185+ , "%Y-%m-%d %H:%M:%S"
186+ , "%Y-%m-%d"
187+ , "%B %e, %Y %l:%M %p"
188+ , "%B %e, %Y"
189+ ]
190+
191+
192+--------------------------------------------------------------------------------
193+modificationTimeField :: String -- ^ Key
194+ -> String -- ^ Format
195+ -> Context a -- ^ Resuting context
196+modificationTimeField = modificationTimeFieldWith defaultTimeLocale
197+
198+
199+--------------------------------------------------------------------------------
200+modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
201+ -> String -- ^ Key
202+ -> String -- ^ Format
203+ -> Context a -- ^ Resulting context
204+modificationTimeFieldWith locale key fmt = field key $ \i -> do
205+ provider <- compilerProvider <$> compilerAsk
206+ mtime <- compilerUnsafeIO $
207+ resourceModificationTime provider $ itemIdentifier i
208+ return $ formatTime locale fmt mtime
209+
210+
211+--------------------------------------------------------------------------------
212+missingField :: Context a
213+missingField = Context $ \k i -> compilerThrow $
214+ "Missing field $" ++ k ++ "$ in context for item " ++
215+ show (itemIdentifier i)
src/Hakyll/Web/Template/Internal.hsnew file+45
@@ -0,0 +1,45 @@
1+-- | Module containing the template data structure
2+--
3+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
4+module Hakyll.Web.Template.Internal
5+ ( Template (..)
6+ , TemplateElement (..)
7+ ) where
8+
9+import Control.Applicative ((<$>))
10+
11+import Data.Binary (Binary, get, getWord8, put, putWord8)
12+import Data.Typeable (Typeable)
13+
14+import Hakyll.Core.Writable
15+
16+-- | Datatype used for template substitutions.
17+--
18+newtype Template = Template
19+ { unTemplate :: [TemplateElement]
20+ }
21+ deriving (Show, Eq, Binary, Typeable)
22+
23+instance Writable Template where
24+ -- Writing a template is impossible
25+ write _ _ = return ()
26+
27+-- | Elements of a template.
28+--
29+data TemplateElement
30+ = Chunk String
31+ | Key String
32+ | Escaped
33+ deriving (Show, Eq, Typeable)
34+
35+instance Binary TemplateElement where
36+ put (Chunk string) = putWord8 0 >> put string
37+ put (Key key) = putWord8 1 >> put key
38+ put (Escaped) = putWord8 2
39+
40+ get = getWord8 >>= \tag -> case tag of
41+ 0 -> Chunk <$> get
42+ 1 -> Key <$> get
43+ 2 -> return Escaped
44+ _ -> error $ "Hakyll.Web.Template.Internal: "
45+ ++ "Error reading cached template"
src/Hakyll/Web/Template/List.hsnew file+53
@@ -0,0 +1,53 @@
1+--------------------------------------------------------------------------------
2+-- | Provides an easy way to combine several items in a list. The applications
3+-- are obvious:
4+--
5+-- * A post list on a blog
6+--
7+-- * An image list in a gallery
8+--
9+-- * A sitemap
10+module Hakyll.Web.Template.List
11+ ( applyTemplateList
12+ , chronological
13+ , recentFirst
14+ ) where
15+
16+
17+--------------------------------------------------------------------------------
18+import Data.List (sortBy)
19+import Data.Ord (comparing)
20+import System.FilePath (takeBaseName)
21+
22+
23+--------------------------------------------------------------------------------
24+import Hakyll.Core.Compiler
25+import Hakyll.Core.Identifier
26+import Hakyll.Core.Item
27+import Hakyll.Web.Template
28+import Hakyll.Web.Template.Context
29+
30+
31+--------------------------------------------------------------------------------
32+-- | Set a field of a page to a listing of pages
33+applyTemplateList :: Template
34+ -> Context a
35+ -> [Item a]
36+ -> Compiler String
37+applyTemplateList tpl context items = do
38+ items' <- mapM (applyTemplate tpl context) items
39+ return $ concat $ map itemBody items'
40+
41+
42+--------------------------------------------------------------------------------
43+-- | Sort pages chronologically. This function assumes that the pages have a
44+-- @year-month-day-title.extension@ naming scheme -- as is the convention in
45+-- Hakyll.
46+chronological :: [Item a] -> [Item a]
47+chronological = sortBy $ comparing $ takeBaseName . toFilePath . itemIdentifier
48+
49+
50+--------------------------------------------------------------------------------
51+-- | The reverse of 'chronological'
52+recentFirst :: [Item a] -> [Item a]
53+recentFirst = reverse . chronological
src/Hakyll/Web/Template/Read.hsnew file+41
@@ -0,0 +1,41 @@
1+--------------------------------------------------------------------------------
2+-- | Read templates in Hakyll's native format
3+module Hakyll.Web.Template.Read
4+ ( readTemplate
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Data.List (isPrefixOf)
10+
11+
12+--------------------------------------------------------------------------------
13+import Hakyll.Web.Template.Internal
14+
15+
16+--------------------------------------------------------------------------------
17+-- | Construct a @Template@ from a string.
18+readTemplate :: String -> Template
19+readTemplate = Template . readTemplate'
20+ where
21+ readTemplate' [] = []
22+ readTemplate' string
23+ | "$$" `isPrefixOf` string =
24+ Escaped : readTemplate' (drop 2 string)
25+ | "$" `isPrefixOf` string =
26+ case readKey (drop 1 string) of
27+ Just (key, rest) -> Key key : readTemplate' rest
28+ Nothing -> Chunk "$" : readTemplate' (drop 1 string)
29+ | otherwise =
30+ let (chunk, rest) = break (== '$') string
31+ in Chunk chunk : readTemplate' rest
32+
33+ -- Parse an key into (key, rest) if it's valid, and return
34+ -- Nothing otherwise
35+ readKey string =
36+ let (key, rest) = span validKeyChar string
37+ in if not (null key) && "$" `isPrefixOf` rest
38+ then Just (key, drop 1 rest)
39+ else Nothing
40+
41+ validKeyChar x = x `notElem` ['$', '\n', '\r']
src/Init.hsdeleted−123
@@ -1,123 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Main
3− ( main
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import Control.Arrow (first)
9−import Control.Monad (forM, forM_)
10−import Data.Char (isAlphaNum, isNumber)
11−import Data.List (foldl', intercalate, isPrefixOf)
12−import Data.Version (Version (..))
13−import System.Directory (canonicalizePath, copyFile,
14− doesFileExist)
15−import System.Environment (getArgs, getProgName)
16−import System.Exit (exitFailure)
17−import System.FilePath (splitDirectories, (</>))
18−
19−
20−--------------------------------------------------------------------------------
21−import Hakyll.Core.Util.File
22−import Paths_hakyll
23−
24−
25−--------------------------------------------------------------------------------
26−import Prelude
27−
28−
29−--------------------------------------------------------------------------------
30−main :: IO ()
31−main = do
32− progName <- getProgName
33− args <- getArgs
34− srcDir <- getDataFileName "example"
35− files <- getRecursiveContents (const $ return False) srcDir
36−
37− case args of
38− -- When the argument begins with hyphens, it's more likely that the user
39− -- intends to attempt some arguments like ("--help", "-h", "--version", etc.)
40− -- rather than create directory with that name.
41− -- If dstDir begins with hyphens, the guard will prevent it from creating
42− -- directory with that name so we can fall to the second alternative
43− -- which prints a usage info for user.
44− [dstDir] | not ("-" `isPrefixOf` dstDir) ->
45− createFiles False srcDir files dstDir
46− ["-f", dstDir] ->
47− createFiles True srcDir files dstDir
48− _ -> do
49− putStrLn $ "Usage: " ++ progName ++ "[-f] <directory>"
50− exitFailure
51−
52− where
53− createFiles force srcDir files dstDir = do
54− name <- makeName dstDir
55− let cabalPath = dstDir </> name ++ ".cabal"
56−
57− diff <- if force then return []
58− else existingFiles dstDir (cabalPath : files)
59−
60− case diff of
61− [] -> do
62− forM_ files $ \file -> do
63− let dst = dstDir </> file
64− src = srcDir </> file
65− putStrLn $ "Creating " ++ dst
66− makeDirectories dst
67− copyFile src dst
68−
69− putStrLn $ "Creating " ++ cabalPath
70− createCabal cabalPath name
71− fs -> do
72− putStrLn $ "The following files will be overwritten:"
73− mapM_ putStrLn fs
74− putStrLn $ "Use -f to overwrite them"
75− exitFailure
76−
77−existingFiles :: FilePath -> [FilePath] -> IO [FilePath]
78−existingFiles dstDir files = fmap concat $ forM files $ \file -> do
79− let dst = dstDir </> file
80− exists <- doesFileExist dst
81− return $ if exists then [dst] else []
82−
83−-- | Figure out a good cabal package name from the given (existing) directory
84−-- name
85−makeName :: FilePath -> IO String
86−makeName dstDir = do
87− canonical <- canonicalizePath dstDir
88− return $ case safeLast (splitDirectories canonical) of
89− Nothing -> fallbackName
90− Just "/" -> fallbackName
91− Just x -> repair (fallbackName ++) id x
92− where
93− -- Package name repair code comes from
94− -- cabal-install.Distribution.Client.Init.Heuristics
95− repair invalid valid x = case dropWhile (not . isAlphaNum) x of
96− "" -> repairComponent ""
97− x' -> let (c, r) = first repairComponent $ break (not . isAlphaNum) x'
98− in c ++ repairRest r
99− where repairComponent c | all isNumber c = invalid c
100− | otherwise = valid c
101− repairRest = repair id ('-' :)
102− fallbackName = "site"
103−
104− safeLast = foldl' (\_ x -> Just x) Nothing
105−
106−createCabal :: FilePath -> String -> IO ()
107−createCabal path name =
108− writeFile path $ unlines [
109− "name: " ++ name
110− , "version: 0.1.0.0"
111− , "build-type: Simple"
112− , "cabal-version: >= 1.10"
113− , ""
114− , "executable site"
115− , " main-is: site.hs"
116− , " build-depends: base == 4.*"
117− , " , hakyll == " ++ version' ++ ".*"
118− , " ghc-options: -threaded"
119− , " default-language: Haskell2010"
120− ]
121− where
122− -- Major hakyll version
123− version' = intercalate "." . take 2 . map show $ versionBranch version
stack.yamldeleted−33
@@ -1,33 +0,0 @@
1−resolver: lts-13.29
2−save-hackage-creds: false
3−
4−flags:
5− hakyll:
6− previewServer: true
7− watchServer: true
8− checkExternal: true
9− usePandoc: true
10− buildWebsite: true
11−
12−packages:
13− - '.'
14−
15−nix:
16− enable: false
17− packages:
18− - zlib
19−
20−build:
21− haddock: true
22− haddock-hyperlink-source: true
23− haddock-deps: false
24−
25−extra-deps:
26−- 'cmark-gfm-0.2.0'
27−- 'hslua-module-system-0.2.1'
28−- 'ipynb-0.1'
29−- 'lrucache-1.2.0.1'
30−- 'pandoc-2.7.3'
31−- 'pandoc-citeproc-0.16.2'
32−- 'skylighting-0.8.2'
33−- 'skylighting-core-0.8.2'
test.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−import Control.Monad (forM)
3−import qualified Crypto.Hash.SHA256 as SHA256
4−import qualified Data.ByteString.Base16 as Base16
5−import qualified Data.ByteString.Char8 as BS8
6−import qualified Data.ByteString.Lazy as BSL
7−import Data.Map (Map)
8−import qualified Data.Map as Map
9−import Hakyll
10−import System.FilePath ((</>))
11−
12−type FileHashes = Map Identifier String
13−
14−mkFileHashes :: FilePath -> IO FileHashes
15−mkFileHashes dir = do
16− allFiles <- getRecursiveContents (\_ -> return False) dir
17− fmap Map.fromList $ forM allFiles $ \path0 -> do
18− let path1 = dir </> path0
19− !h <- hash path1
20− return (fromFilePath path1, h)
21− where
22− hash :: FilePath -> IO String
23− hash fp = do
24− !h <- SHA256.hashlazy <$> BSL.readFile fp
25− return $! BS8.unpack $! Base16.encode h
26−
27−main :: IO ()
28−main = hakyll $ do
29− fileHashes <- preprocess (mkFileHashes "images")
30− undefined
tests/Hakyll/Core/Dependencies/Tests.hs+4−5
@@ -9,8 +9,8 @@ module Hakyll.Core.Dependencies.Tests
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.Tasty (TestTree, testGroup)
12+import Test.Framework (Test, testGroup)
13−import Test.Tasty.HUnit (Assertion, (@=?))
13+import Test.HUnit (Assertion, (@=?))
14
14
15
15
16--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ import TestSuite.Util
20
20
21
21
22--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23−tests :: TestTree
23+tests :: Test
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
@@ -38,8 +38,7 @@ oldFacts = M.fromList
38 , ("posts/02.md",
38 , ("posts/02.md",
39 [])
39 [])
40 , ("index.md",
40 , ("index.md",
41− [ PatternDependency "posts/*"
41+ [ PatternDependency "posts/*" ["posts/01.md", "posts/02.md"]
42− (S.fromList ["posts/01.md", "posts/02.md"])
43 , IdentifierDependency "posts/01.md"
42 , IdentifierDependency "posts/01.md"
44 , IdentifierDependency "posts/02.md"
43 , IdentifierDependency "posts/02.md"
45 ])
44 ])
tests/Hakyll/Core/Identifier/Tests.hs+5−6
@@ -6,8 +6,8 @@ module Hakyll.Core.Identifier.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
9+import Test.Framework (Test, testGroup)
10−import Test.Tasty.HUnit ((@=?))
10+import Test.HUnit ((@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ import TestSuite.Util
17
17
18
18
19--------------------------------------------------------------------------------
19--------------------------------------------------------------------------------
20−tests :: TestTree
20+tests :: Test
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 :: [TestTree]
28+captureTests :: [Test]
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,12 +42,11 @@ 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"
46 ]
45 ]
47
46
48
47
49--------------------------------------------------------------------------------
48--------------------------------------------------------------------------------
50−matchesTests :: [TestTree]
49+matchesTests :: [Test]
51matchesTests = fromAssertions "matches"
50matchesTests = fromAssertions "matches"
52 [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
51 [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
53 , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
52 , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
tests/Hakyll/Core/Provider/Metadata/Tests.hsdeleted−62
@@ -1,62 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Core.Provider.Metadata.Tests
3− ( tests
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import qualified Data.HashMap.Strict as HMS
9−import qualified Data.Text as T
10−import qualified Data.Yaml as Yaml
11−import Hakyll.Core.Metadata
12−import Hakyll.Core.Provider.Metadata
13−import Test.Tasty (TestTree, testGroup)
14−import Test.Tasty.HUnit (Assertion, assertFailure, (@=?))
15−import TestSuite.Util
16−
17−
18−--------------------------------------------------------------------------------
19−tests :: TestTree
20−tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
21− fromAssertions "page" [testPage01, testPage02]
22−
23−
24−
25−--------------------------------------------------------------------------------
26−testPage01 :: Assertion
27−testPage01 =
28− (meta [("foo", "bar")], "qux\n") `expectRight` parsePage
29− "---\n\
30− \foo: bar\n\
31− \---\n\
32− \qux\n"
33−
34−
35−--------------------------------------------------------------------------------
36−testPage02 :: Assertion
37−testPage02 =
38− (meta [("description", descr)], "Hello I am dog\n") `expectRight`
39− parsePage
40− "---\n\
41− \description: A long description that would look better if it\n\
42− \ spanned multiple lines and was indented\n\
43− \---\n\
44− \Hello I am dog\n"
45− where
46− descr :: String
47− descr =
48− "A long description that would look better if it \
49− \spanned multiple lines and was indented"
50−
51−
52−--------------------------------------------------------------------------------
53−meta :: Yaml.ToJSON a => [(String, a)] -> Metadata
54−meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
55−
56−
57−--------------------------------------------------------------------------------
58−-- | This is useful when the 'Left' side of 'Either' doesn't have an 'Eq'
59−-- instance.
60−expectRight :: (Eq b, Show a, Show b) => b -> Either a b -> Assertion
61−expectRight _ (Left err) = assertFailure (show err)
62−expectRight expected (Right res) = expected @=? res
tests/Hakyll/Core/Provider/Tests.hs+12−9
@@ -6,15 +6,19 @@ module Hakyll.Core.Provider.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Hakyll.Core.Metadata
9+import qualified Data.Map as M
10+import Test.Framework (Test, testGroup)
11+import Test.Framework.Providers.HUnit (testCase)
12+import Test.HUnit (Assertion, assert, (@=?))
13+
14+
15+--------------------------------------------------------------------------------
10import Hakyll.Core.Provider
16import Hakyll.Core.Provider
11−import Test.Tasty (TestTree, testGroup)
12−import Test.Tasty.HUnit (Assertion, testCase, (@=?))
13import TestSuite.Util
17import TestSuite.Util
14
18
15
19
16--------------------------------------------------------------------------------
20--------------------------------------------------------------------------------
17−tests :: TestTree
21+tests :: Test
18tests = testGroup "Hakyll.Core.Provider.Tests"
22tests = testGroup "Hakyll.Core.Provider.Tests"
19 [ testCase "case01" case01
23 [ testCase "case01" case01
20 ]
24 ]
@@ -25,12 +29,11 @@ case01 :: Assertion
25case01 = do
29case01 = do
26 store <- newTestStore
30 store <- newTestStore
27 provider <- newTestProvider store
31 provider <- newTestProvider store
28− True @=? resourceExists provider "example.md"
32+ assert $ resourceExists provider "example.md"
29
33
30 metadata <- resourceMetadata provider "example.md"
34 metadata <- resourceMetadata provider "example.md"
31− Just "An example" @=? lookupString "title" metadata
35+ Just "An example" @=? M.lookup "title" metadata
32− Just "External data" @=? lookupString "external" metadata
36+ Just "External data" @=? M.lookup "external" metadata
33
37
34 doesntExist <- resourceMetadata provider "doesntexist.md"
38 doesntExist <- resourceMetadata provider "doesntexist.md"
35− mempty @=? doesntExist
39+ M.empty @=? doesntExist
36− cleanTestEnv
tests/Hakyll/Core/Routes/Tests.hs+18−31
@@ -6,45 +6,32 @@ module Hakyll.Core.Routes.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Data.Maybe (fromMaybe)
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit ((@=?))
11+
12+
13+--------------------------------------------------------------------------------
10import Hakyll.Core.Identifier
14import Hakyll.Core.Identifier
11−import Hakyll.Core.Metadata
12import Hakyll.Core.Routes
15import Hakyll.Core.Routes
13−import System.FilePath ((</>))
14−import Test.Tasty (TestTree, testGroup)
15−import Test.Tasty.HUnit (Assertion, (@=?))
16import TestSuite.Util
16import TestSuite.Util
17
17
18
18
19--------------------------------------------------------------------------------
19--------------------------------------------------------------------------------
20−tests :: TestTree
20+tests :: Test
21tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
21tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
22− [ testRoutes "foo.html" (setExtension "html") "foo"
22+ [ Just "foo.html" @=? runRoutes (setExtension "html") "foo"
23− , testRoutes "foo.html" (setExtension ".html") "foo"
23+ , Just "foo.html" @=? runRoutes (setExtension ".html") "foo"
24− , testRoutes "foo.html" (setExtension "html") "foo.markdown"
24+ , Just "foo.html" @=? runRoutes (setExtension "html") "foo.markdown"
25− , testRoutes "foo.html" (setExtension ".html") "foo.markdown"
25+ , Just "foo.html" @=? runRoutes (setExtension ".html") "foo.markdown"
26−
27− , testRoutes "neve ro ddo reven"
28− (customRoute (reverse . toFilePath )) "never odd or even"
29
26
30− , testRoutes "foo" (constRoute "foo") "bar"
27+ , Just "neve ro ddo reven" @=?
28+ runRoutes (customRoute (reverse . toFilePath )) "never odd or even"
31
29
32− , testRoutes "tags/bar.xml" (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
30+ , Just "foo" @=? runRoutes (constRoute "foo") "bar"
33− , testRoutes "tags/bar.xml"
34− (gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml")
35− "tags/rss/bar"
36
31
37− , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
32+ , Just "tags/bar.xml" @=?
38− fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
33+ runRoutes (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
39− "example.md"
34+ , Just "tags/bar.xml" @=?
35+ runRoutes (gsubRoute "rss/" (const "") `composeRoutes`
36+ setExtension "xml") "tags/rss/bar"
40 ]
37 ]
41−
42−
43−--------------------------------------------------------------------------------
44−testRoutes :: FilePath -> Routes -> Identifier -> Assertion
45−testRoutes expected r id' = do
46− store <- newTestStore
47− provider <- newTestProvider store
48− (route, _) <- runRoutes r provider id'
49− Just expected @=? route
50− cleanTestEnv
tests/Hakyll/Core/Rules/Tests.hs+30−41
@@ -9,72 +9,66 @@ 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.Framework.Providers.HUnit (testCase)
14+import Test.HUnit (Assertion, assert, (@=?))
15+
16+
17+--------------------------------------------------------------------------------
12import Hakyll.Core.Compiler
18import Hakyll.Core.Compiler
13import Hakyll.Core.File
19import Hakyll.Core.File
14import Hakyll.Core.Identifier
20import Hakyll.Core.Identifier
15import Hakyll.Core.Identifier.Pattern
21import Hakyll.Core.Identifier.Pattern
16−import Hakyll.Core.Metadata
17import Hakyll.Core.Routes
22import Hakyll.Core.Routes
18import Hakyll.Core.Rules
23import Hakyll.Core.Rules
19import Hakyll.Core.Rules.Internal
24import Hakyll.Core.Rules.Internal
20−import System.FilePath ((</>))
25+import Hakyll.Web.Pandoc
21−import Test.Tasty (TestTree, testGroup)
22−import Test.Tasty.HUnit (Assertion, (@=?))
23import TestSuite.Util
26import TestSuite.Util
24
27
25
28
26--------------------------------------------------------------------------------
29--------------------------------------------------------------------------------
27−tests :: TestTree
30+tests :: Test
28−tests = testGroup "Hakyll.Core.Rules.Tests" $ fromAssertions "runRules"
31+tests = testGroup "Hakyll.Core.Rules.Tests"
29− [case01]
32+ [ testCase "runRules" rulesTest
33+ ]
30
34
31
35
32--------------------------------------------------------------------------------
36--------------------------------------------------------------------------------
33−case01 :: Assertion
37+rulesTest :: Assertion
34−case01 = do
38+rulesTest = do
35 ioref <- newIORef False
39 ioref <- newIORef False
36 store <- newTestStore
40 store <- newTestStore
37 provider <- newTestProvider store
41 provider <- newTestProvider store
38− ruleSet <- runRules (rules01 ioref) provider
42+ ruleSet <- runRules (rules ioref) provider
39− let identifiers = S.fromList $ map fst $ rulesCompilers ruleSet
43+ let identifiers = S.fromList $ map fst $ rulesCompilers ruleSet
40− routes = rulesRoutes ruleSet
44+ routes = rulesRoutes ruleSet
41− checkRoute ex i =
42− runRoutes routes provider i >>= \(r, _) -> Just ex @=? r
43
45
44 -- Test that we have some identifiers and that the routes work out
46 -- Test that we have some identifiers and that the routes work out
45− S.fromList expected @=? identifiers
47+ assert $ all (`S.member` identifiers) expected
46− checkRoute "example.html" "example.md"
48+ Just "example.html" @=? runRoutes routes "example.md"
47− checkRoute "example.md" (sv "raw" "example.md")
49+ Just "example.md" @=? runRoutes routes (sv "raw" "example.md")
48− checkRoute "example.md" (sv "nav" "example.md")
50+ Just "example.md" @=? runRoutes routes (sv "nav" "example.md")
49− checkRoute "example.mv1" (sv "mv1" "example.md")
51+ Just "example.mv1" @=? runRoutes routes (sv "mv1" "example.md")
50− checkRoute "example.mv2" (sv "mv2" "example.md")
52+ Just "example.mv2" @=? runRoutes routes (sv "mv2" "example.md")
51− checkRoute "food/example.md" (sv "metadataMatch" "example.md")
52− readIORef ioref >>= (True @=?)
53+ readIORef ioref >>= assert
53− cleanTestEnv
54 where
54 where
55 sv g = setVersion (Just g)
55 sv g = setVersion (Just g)
56 expected =
56 expected =
57− [ "example.md"
58− , sv "raw" "example.md"
59− , sv "metadataMatch" "example.md"
60− , sv "nav" "example.md"
61− , sv "mv1" "example.md"
57+ [ "example.md"
62− , sv "mv2" "example.md"
63−
64− , "russian.md"
58+ , "russian.md"
59+ , sv "raw" "example.md"
65 , sv "raw" "russian.md"
60 , sv "raw" "russian.md"
66− , sv "mv1" "russian.md"
61+ , sv "nav" "example.md"
67− , sv "mv2" "russian.md"
68 ]
62 ]
69
63
70
64
71--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
72−rules01 :: IORef Bool -> Rules ()
66+rules :: IORef Bool -> Rules ()
73−rules01 ioref = do
67+rules ioref = do
74 -- Compile some posts
68 -- Compile some posts
75 match "*.md" $ do
69 match "*.md" $ do
76 route $ setExtension "html"
70 route $ setExtension "html"
77− compile copyFileCompiler
71+ compile pandocCompiler
78
72
79 -- Yeah. I don't know how else to test this stuff?
73 -- Yeah. I don't know how else to test this stuff?
80 preprocess $ writeIORef ioref True
74 preprocess $ writeIORef ioref True
@@ -84,11 +78,6 @@ rules01 ioref = do
84 route idRoute
78 route idRoute
85 compile getResourceString
79 compile getResourceString
86
80
87− version "metadataMatch" $
88− matchMetadata "*.md" (\md -> lookupString "subblog" md == Just "food") $ do
89− route $ customRoute $ \id' -> "food" </> toFilePath id'
90− compile getResourceString
91−
92 -- Regression test
81 -- Regression test
93 version "nav" $ match (fromList ["example.md"]) $ do
82 version "nav" $ match (fromList ["example.md"]) $ do
94 route idRoute
83 route idRoute
tests/Hakyll/Core/Runtime/Tests.hs+6−52
@@ -6,10 +6,9 @@ module Hakyll.Core.Runtime.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import qualified Data.ByteString as B
10import System.FilePath ((</>))
9import System.FilePath ((</>))
11−import Test.Tasty (TestTree, testGroup)
10+import Test.Framework (Test, testGroup)
12−import Test.Tasty.HUnit (Assertion, (@?=))
11+import Test.HUnit (Assertion, (@?=))
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
@@ -20,35 +19,20 @@ import TestSuite.Util
20
19
21
20
22--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
23−tests :: TestTree
22+tests :: Test
24−tests = testGroup "Hakyll.Core.Runtime.Tests" $
23+tests = testGroup "Hakyll.Core.Runtime.Tests" $ fromAssertions "run" [case01]
25− fromAssertions "run" [case01, case02]
26
24
27
25
28--------------------------------------------------------------------------------
26--------------------------------------------------------------------------------
29case01 :: Assertion
27case01 :: Assertion
30case01 = do
28case01 = do
31− logger <- Logger.new Logger.Error
32− _ <- run testConfiguration logger $ do
29+ _ <- run testConfiguration Logger.Error $ do
33− match "images/*" $ do
34− route idRoute
35− compile copyFileCompiler
36−
37 match "*.md" $ do
30 match "*.md" $ do
38 route $ setExtension "html"
31 route $ setExtension "html"
39 compile $ do
32 compile $ do
40 getResourceBody
33 getResourceBody
41 >>= saveSnapshot "raw"
34 >>= saveSnapshot "raw"
42− >>= renderParagraphs
35+ >>= return . renderPandoc
43−
44− match (fromList ["partial.html", "partial-helper.html"]) $
45− compile templateCompiler
46− create ["partial.html.out"] $ do
47− route idRoute
48− compile $ do
49− example <- loadSnapshotBody "example.md" "raw"
50− makeItem example
51− >>= loadAndApplyTemplate "partial.html" defaultContext
52
36
53 create ["bodies.txt"] $ do
37 create ["bodies.txt"] $ do
54 route idRoute
38 route idRoute
@@ -56,12 +40,6 @@ case01 = do
56 items <- loadAllSnapshots "*.md" "raw"
40 items <- loadAllSnapshots "*.md" "raw"
57 makeItem $ concat $ map itemBody (items :: [Item String])
41 makeItem $ concat $ map itemBody (items :: [Item String])
58
42
59− favicon <- B.readFile $
60− providerDirectory testConfiguration </> "images/favicon.ico"
61− favicon' <- B.readFile $
62− destinationDirectory testConfiguration </> "images/favicon.ico"
63− favicon @?= favicon'
64−
65 example <- readFile $
43 example <- readFile $
66 destinationDirectory testConfiguration </> "example.html"
44 destinationDirectory testConfiguration </> "example.html"
67 lines example @?= ["<p>This is an example.</p>"]
45 lines example @?= ["<p>This is an example.</p>"]
@@ -69,28 +47,4 @@ case01 = do
69 bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
47 bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
70 head (lines bodies) @?= "This is an example."
48 head (lines bodies) @?= "This is an example."
71
49
72− partial <- readFile $ providerDirectory testConfiguration </> "partial.html.out"
73− partial' <- readFile $ destinationDirectory testConfiguration </> "partial.html.out"
74− partial @?= partial'
75−
76− cleanTestEnv
77−
78−
79−--------------------------------------------------------------------------------
80−case02 :: Assertion
81−case02 = do
82− logger <- Logger.new Logger.Error
83− _ <- run testConfiguration logger $ do
84− match "images/favicon.ico" $ do
85− route $ gsubRoute "images/" (const "")
86− compile $ makeItem ("Test" :: String)
87−
88− match "images/**" $ do
89− route idRoute
90− compile copyFileCompiler
91−
92− favicon <- readFile $
93− destinationDirectory testConfiguration </> "favicon.ico"
94− favicon @?= "Test"
95−
96 cleanTestEnv
50 cleanTestEnv
tests/Hakyll/Core/Store/Tests.hs+14−28
@@ -6,27 +6,26 @@ 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
12−import Test.Tasty (TestTree, testGroup)
10+import Test.Framework (Test, testGroup)
13−import Test.Tasty.HUnit (testCase)
11+import Test.Framework.Providers.HUnit (testCase)
14−import qualified Test.Tasty.HUnit as H
12+import Test.Framework.Providers.QuickCheck2 (testProperty)
15−import Test.Tasty.QuickCheck (testProperty)
13+import qualified Test.HUnit as H
14+import qualified Test.QuickCheck as Q
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 :: TestTree
24+tests :: Test
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
28 , testCase "WrongType get . set" wrongType
28 , testCase "WrongType get . set" wrongType
29− , testCase "isMembertest . set" isMembertest
30 ]
29 ]
31
30
32
31
@@ -63,21 +62,8 @@ wrongType = do
63 -- Store a string and try to fetch an int
62 -- Store a string and try to fetch an int
64 Store.set store ["foo", "bar"] ("qux" :: String)
63 Store.set store ["foo", "bar"] ("qux" :: String)
65 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
64 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
66− case value of
65+ H.assert $ case value of
67− Store.WrongType e t -> do
66+ Store.WrongType e t ->
68− typeOf (undefined :: Int) H.@=? e
67+ e == typeOf (undefined :: Int) &&
69− typeOf (undefined :: String) H.@=? t
68+ t == typeOf (undefined :: String)
70− _ -> H.assertFailure "Expecting WrongType"
71− cleanTestEnv
72−
73−
74−--------------------------------------------------------------------------------
75−isMembertest :: H.Assertion
76−isMembertest = do
77− store <- newTestStore
78− Store.set store ["foo", "bar"] ("qux" :: String)
79− good <- Store.isMember store ["foo", "bar"]
80− bad <- Store.isMember store ["foo", "baz"]
81− True H.@=? good
82− False H.@=? bad
69+ _ -> False
83− cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hs+8−35
@@ -6,61 +6,34 @@ module Hakyll.Core.UnixFilter.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
9+import Test.Framework (Test, testGroup)
10−import Test.Tasty.HUnit (testCase)
10+import Test.Framework.Providers.HUnit (testCase)
11−import qualified Test.Tasty.HUnit as H
11+import qualified Test.HUnit as H
12
12
13
13
14--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
15import Hakyll.Core.Compiler
15import Hakyll.Core.Compiler
16−import Hakyll.Core.Identifier
17import Hakyll.Core.Item
16import Hakyll.Core.Item
18import Hakyll.Core.UnixFilter
17import Hakyll.Core.UnixFilter
19import TestSuite.Util
18import TestSuite.Util
20
19
21
20
22--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
23−tests :: TestTree
22+tests :: Test
24tests = testGroup "Hakyll.Core.UnixFilter.Tests"
23tests = testGroup "Hakyll.Core.UnixFilter.Tests"
25− [ testCase "unixFilter rev" unixFilterRev
24+ [ testCase "unixFilter rev" unixFilterRev
26− , testCase "unixFilter false" unixFilterFalse
27− , testCase "unixFilter error" unixFilterError
28 ]
25 ]
29
26
30−testMarkdown :: Identifier
31−testMarkdown = "russian.md"
32
27
33--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
34unixFilterRev :: H.Assertion
29unixFilterRev :: H.Assertion
35unixFilterRev = do
30unixFilterRev = do
36 store <- newTestStore
31 store <- newTestStore
37 provider <- newTestProvider store
32 provider <- newTestProvider store
38− output <- testCompilerDone store provider testMarkdown compiler
33+ output <- testCompilerDone store provider "russian.md" compiler
39− expected <- testCompilerDone store provider testMarkdown getResourceString
34+ expected <- testCompilerDone store provider "russian.md" getResourceString
40− rev (itemBody expected) H.@=? lines (itemBody output)
35+ H.assert $ rev (itemBody expected) == lines (itemBody output)
41 cleanTestEnv
36 cleanTestEnv
42 where
37 where
43 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
38 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
44 rev = map reverse . lines
39 rev = map reverse . lines
45−
46−
47−--------------------------------------------------------------------------------
48−unixFilterFalse :: H.Assertion
49−unixFilterFalse = do
50− store <- newTestStore
51− provider <- newTestProvider store
52− testCompilerError store provider testMarkdown compiler "exit code"
53− cleanTestEnv
54− where
55− compiler = getResourceString >>= withItemBody (unixFilter "false" [])
56−
57−
58−--------------------------------------------------------------------------------
59−unixFilterError :: H.Assertion
60−unixFilterError = do
61− store <- newTestStore
62− provider <- newTestProvider store
63− testCompilerError store provider testMarkdown compiler "option"
64− cleanTestEnv
65− where
66− compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])
tests/Hakyll/Core/Util/String/Tests.hs+3−13
@@ -5,8 +5,8 @@ module Hakyll.Core.Util.String.Tests
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Test.Tasty (TestTree, testGroup)
8+import Test.Framework (Test, testGroup)
9−import Test.Tasty.HUnit ((@=?))
9+import Test.HUnit ((@=?))
10
10
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
@@ -15,7 +15,7 @@ import TestSuite.Util
15
15
16
16
17--------------------------------------------------------------------------------
17--------------------------------------------------------------------------------
18−tests :: TestTree
18+tests :: Test
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 "
@@ -28,16 +28,6 @@ tests = testGroup "Hakyll.Core.Util.String.Tests" $ concat
28 , fromAssertions "splitAll"
28 , fromAssertions "splitAll"
29 [ ["λ", "∀x.x", "hi"] @=? splitAll ", *" "λ, ∀x.x, hi"
29 [ ["λ", "∀x.x", "hi"] @=? splitAll ", *" "λ, ∀x.x, hi"
30 ]
30 ]
31−
32− , fromAssertions "needlePrefix"
33− [ Just "ab" @=? needlePrefix "cd" "abcde"
34− , Just "xx" @=? needlePrefix "ab" "xxab"
35− , Nothing @=? needlePrefix "a" "xx"
36− , Just "x" @=? needlePrefix "ab" "xabxab"
37− , Just "" @=? needlePrefix "ab" "abc"
38− , Just "" @=? needlePrefix "ab" "abab"
39− , Nothing @=? needlePrefix "" ""
40− ]
41 ]
31 ]
42
32
43 where
33 where
tests/Hakyll/Web/CompressCss/Tests.hsdeleted−64
@@ -1,64 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Web.CompressCss.Tests
3− ( tests
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import Test.Tasty (TestTree, testGroup)
9−import Test.Tasty.HUnit ((@=?))
10−
11−
12−--------------------------------------------------------------------------------
13−import Hakyll.Web.CompressCss
14−import TestSuite.Util
15−
16−
17−--------------------------------------------------------------------------------
18−tests :: TestTree
19−tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
20− [ fromAssertions "compressCss"
21− [
22− -- compress whitespace
23− "something something" @=?
24− compressCss " something \n\t\r something "
25− -- do not compress whitespace in string tokens
26− , "abc \" \t\n\r \" xyz" @=?
27− compressCss "abc \" \t\n\r \" xyz"
28− , "abc ' \t\n\r ' xyz" @=?
29− compressCss "abc ' \t\n\r ' xyz"
30−
31− -- strip comments
32− , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
33− -- don't strip comments inside string tokens
34− , "before \"/* abc { } ;; \n\t\r */\" after"
35− @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
36−
37− -- compress separators
38− , "}" @=? compressCss "; }"
39− , ";{};" @=? compressCss " ; { } ; "
40− , "text," @=? compressCss "text , "
41− , "a>b" @=? compressCss "a > b"
42− , "a+b" @=? compressCss "a + b"
43− , "a!b" @=? compressCss "a ! b"
44− -- compress calc()
45− , "calc(1px + 100%/(5 + 3) - (3px + 2px)*5)" @=? compressCss "calc( 1px + 100% / ( 5 + 3) - calc( 3px + 2px ) * 5 )"
46− -- compress whitespace even after this curly brace
47− , "}" @=? compressCss "; } "
48− -- but do not compress separators inside string tokens
49− , "\" { } ; , \"" @=? compressCss "\" { } ; , \""
50− -- don't compress separators at the start or end of string tokens
51− , "\" }\"" @=? compressCss "\" }\""
52− , "\"{ \"" @=? compressCss "\"{ \""
53− -- don't get irritated by the wrong token delimiter
54− , "\" ' \"" @=? compressCss "\" ' \""
55− , "' \" '" @=? compressCss "' \" '"
56− -- don't compress whitespace in the middle of a string
57− , "abc '{ '" @=? compressCss "abc '{ '"
58− , "abc \"{ \"" @=? compressCss "abc \"{ \""
59− -- compress whitespace after colons (but not before)
60− , "abc :xyz" @=? compressCss "abc : xyz"
61− -- compress multiple semicolons
62− , ";" @=? compressCss ";;;;;;;"
63− ]
64− ]
tests/Hakyll/Web/Html/RelativizeUrls/Tests.hs+3−7
@@ -6,9 +6,8 @@ module Hakyll.Web.Html.RelativizeUrls.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
9+import Test.Framework (Test, testGroup)
10−import Test.Tasty.HUnit ((@=?))
10+import Test.HUnit ((@=?))
11−
12
11
13--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
14import Hakyll.Web.Html.RelativizeUrls
13import Hakyll.Web.Html.RelativizeUrls
@@ -16,16 +15,13 @@ import TestSuite.Util
16
15
17
16
18--------------------------------------------------------------------------------
17--------------------------------------------------------------------------------
19−tests :: TestTree
18+tests :: Test
20tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
19tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
21 fromAssertions "relativizeUrls"
20 fromAssertions "relativizeUrls"
22 [ "<a href=\"../foo\">bar</a>" @=?
21 [ "<a href=\"../foo\">bar</a>" @=?
23 relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
22 relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
24 , "<img src=\"../../images/lolcat.png\" />" @=?
23 , "<img src=\"../../images/lolcat.png\" />" @=?
25 relativizeUrlsWith "../.." "<img src=\"/images/lolcat.png\" />"
24 relativizeUrlsWith "../.." "<img src=\"/images/lolcat.png\" />"
26− , "<video poster=\"../../images/lolcat.png\"></video>" @=?
27− relativizeUrlsWith "../.."
28− "<video poster=\"/images/lolcat.png\"></video>"
29 , "<a href=\"http://haskell.org\">Haskell</a>" @=?
25 , "<a href=\"http://haskell.org\">Haskell</a>" @=?
30 relativizeUrlsWith "../.."
26 relativizeUrlsWith "../.."
31 "<a href=\"http://haskell.org\">Haskell</a>"
27 "<a href=\"http://haskell.org\">Haskell</a>"
tests/Hakyll/Web/Html/Tests.hs+11−24
@@ -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.Tasty (TestTree, testGroup)
9+import Test.Framework (Test, testGroup)
10−import Test.Tasty.HUnit ((@=?))
10+import Test.HUnit (assert, (@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import TestSuite.Util
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−tests :: TestTree
19+tests :: Test
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>" @=?
@@ -36,22 +36,12 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
36 withUrls id "<code>&lt;stdio&gt;</code>"
36 withUrls id "<code>&lt;stdio&gt;</code>"
37 , "<style>body > p { line-height: 1.3 }</style>" @=?
37 , "<style>body > p { line-height: 1.3 }</style>" @=?
38 withUrls id "<style>body > p { line-height: 1.3 }</style>"
38 withUrls id "<style>body > p { line-height: 1.3 }</style>"
39−
40− -- Test minimizing elements
41− , "<meta bar=\"foo\" />" @=?
42− withUrls id "<meta bar=\"foo\" />"
43 ]
39 ]
44
40
45 , fromAssertions "toUrl"
41 , fromAssertions "toUrl"
46− [ "/foo/bar.html" @=? toUrl "foo/bar.html"
42+ [ "/foo/bar.html" @=? toUrl "foo/bar.html"
47− , "/" @=? toUrl "/"
43+ , "/" @=? toUrl "/"
48− , "/funny-pics.html" @=? toUrl "/funny-pics.html"
44+ , "/funny-pics.html" @=? toUrl "/funny-pics.html"
49− , "/funny%20pics.html" @=? toUrl "funny pics.html"
50− -- Test various reserved characters (RFC 3986, section 2.2)
51− , "/%21%2A%27%28%29%3B%3A%40%26.html" @=? toUrl "/!*'();:@&.html"
52− , "/%3D%2B%24%2C/%3F%23%5B%5D.html" @=? toUrl "=+$,/?#[].html"
53− -- Test various characters that are nor reserved, nor unreserved.
54− , "/%E3%81%82%F0%9D%90%87%E2%88%80" @=? toUrl "\12354\119815\8704"
55 ]
45 ]
56
46
57 , fromAssertions "toSiteRoot"
47 , fromAssertions "toSiteRoot"
@@ -59,16 +49,13 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
59 , "." @=? toSiteRoot "index.html"
49 , "." @=? toSiteRoot "index.html"
60 , "." @=? toSiteRoot "/index.html"
50 , "." @=? toSiteRoot "/index.html"
61 , "../.." @=? toSiteRoot "foo/bar/qux"
51 , "../.." @=? toSiteRoot "foo/bar/qux"
62− , ".." @=? toSiteRoot "./foo/bar.html"
63− , ".." @=? toSiteRoot "/foo/./bar.html"
64 ]
52 ]
65
53
66 , fromAssertions "isExternal"
54 , fromAssertions "isExternal"
67− [ True @=? isExternal "http://reddit.com"
55+ [ assert (isExternal "http://reddit.com")
68− , True @=? isExternal "https://mail.google.com"
56+ , assert (isExternal "https://mail.google.com")
69− , True @=? isExternal "//ajax.googleapis.com"
70− , False @=? isExternal "../header.png"
57+ , assert (not (isExternal "../header.png"))
71− , False @=? isExternal "/foo/index.html"
58+ , assert (not (isExternal "/foo/index.html"))
72 ]
59 ]
73
60
74 , fromAssertions "stripTags"
61 , fromAssertions "stripTags"
tests/Hakyll/Web/Pandoc/FileType/Tests.hsdeleted−26
@@ -1,26 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE OverloadedStrings #-}
3−module Hakyll.Web.Pandoc.FileType.Tests
4− ( tests
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit ((@=?))
11−
12−
13−--------------------------------------------------------------------------------
14−import Hakyll.Web.Pandoc.FileType
15−import TestSuite.Util
16−
17−
18−--------------------------------------------------------------------------------
19−tests :: TestTree
20−tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
21− fromAssertions "fileType"
22− [ Markdown @=? fileType "index.md"
23− , Rst @=? fileType "about/foo.rst"
24− , LiterateHaskell Markdown @=? fileType "posts/bananas.lhs"
25− , LiterateHaskell LaTeX @=? fileType "posts/bananas.tex.lhs"
26− ]
tests/Hakyll/Web/Template/Context/Tests.hs+7−48
@@ -6,26 +6,24 @@ module Hakyll.Web.Template.Context.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
9+import Test.Framework (Test, testGroup)
10+import Test.Framework.Providers.HUnit (testCase)
10−import Test.Tasty.HUnit (Assertion, testCase, (@=?))
11+import Test.HUnit (Assertion, (@=?))
11
12
12
13
13--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
14import Hakyll.Core.Compiler
15import Hakyll.Core.Compiler
15import Hakyll.Core.Identifier
16import 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
21import TestSuite.Util
20import TestSuite.Util
22
21
23
22
24--------------------------------------------------------------------------------
23--------------------------------------------------------------------------------
25−tests :: TestTree
24+tests :: Test
26−tests = testGroup "Hakyll.Web.Template.Context.Tests"
25+tests = testGroup "Hakyll.Core.Template.Context.Tests"
27 [ testCase "testDateField" testDateField
26 [ testCase "testDateField" testDateField
28− , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
29 ]
27 ]
30
28
31
29
@@ -44,15 +42,6 @@ testDateField = do
44 dateField "date" "%B %e, %Y"
42 dateField "date" "%B %e, %Y"
45 date2 @=? "August 26, 2010"
43 date2 @=? "August 26, 2010"
46
44
47− date3 <- testContextDone store provider
48− "posts/2018-09-26.md" "date" $
49− dateField "date" "%B %e, %Y"
50− date3 @=? "September 26, 2018"
51−
52− date4 <- testContextDone store provider
53− "posts/2019/05/10/tomorrow.md" "date" $
54− dateField "date" "%B %e, %Y"
55− date4 @=? "May 10, 2019"
56 cleanTestEnv
45 cleanTestEnv
57
46
58
47
@@ -62,34 +51,4 @@ testContextDone :: Store -> Provider -> Identifier -> String
62testContextDone store provider identifier key context =
51testContextDone store provider identifier key context =
63 testCompilerDone store provider identifier $ do
52 testCompilerDone store provider identifier $ do
64 item <- getResourceBody
53 item <- getResourceBody
65− cf <- unContext context key [] item
54+ unContext context key item
66− case cf of
67− StringField str -> return str
68− _ -> error $
69− "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
70− "expected StringField"
71−
72−--------------------------------------------------------------------------------
73−
74−testOuerLoopContextAccess :: Assertion
75−testOuerLoopContextAccess = do
76− store <- newTestStore
77− provider <- newTestProvider store
78− test store provider ctx "baz"
79− test store provider (ctx' <> ctx) "not baz"
80− test store provider (ctx <> ctx') "baz"
81−
82− cleanTestEnv
83− where
84− tpl = readTemplate "$for(foo)$$for(bar)$$qux$$endfor$$endfor$"
85− ctx = mconcat [
86− field "qux" $ const $ return "baz"
87− , listField "foo" (listField "bar" mempty $ return [mockItem])
88− $ return [mockItem]
89− ]
90− ctx' = field "qux" $ const $ return "not baz"
91− mockItem = Item "" ()
92− test store provider context str = do
93− str' <- testCompilerDone store provider ""
94− $ applyTemplate tpl context mockItem
95− str @=? itemBody str'
tests/Hakyll/Web/Template/Tests.hs+17−122
@@ -1,156 +1,51 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE TemplateHaskell #-}
4module Hakyll.Web.Template.Tests
3module Hakyll.Web.Template.Tests
5 ( tests
4 ( tests
6 ) where
5 ) where
7
6
8
7
9--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9+import Data.Monoid (mconcat)
10−import Test.Tasty (TestTree, testGroup)
10+import Test.Framework (Test, testGroup)
11−import Test.Tasty.HUnit (Assertion, assertBool, testCase,
11+import Test.Framework.Providers.HUnit (testCase)
12− (@=?), (@?=))
12+import Test.HUnit (Assertion, (@=?))
13
13
14−import Data.Either (isLeft)
15
14
16--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
17−import Hakyll.Core.Compiler
18−import Hakyll.Core.Identifier
19import Hakyll.Core.Item
16import Hakyll.Core.Item
20import Hakyll.Core.Provider
17import Hakyll.Core.Provider
18+import Hakyll.Web.Pandoc
21import Hakyll.Web.Template
19import Hakyll.Web.Template
22import Hakyll.Web.Template.Context
20import Hakyll.Web.Template.Context
23−import Hakyll.Web.Template.Internal
24−import Hakyll.Web.Template.List
25import TestSuite.Util
21import TestSuite.Util
26
22
27
23
28--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
29−tests :: TestTree
25+tests :: Test
30−tests = testGroup "Hakyll.Web.Template.Tests" $ concat
26+tests = testGroup "Hakyll.Core.Template.Tests"
31− [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
27+ [ testCase "case01" case01
32− , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
33− , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
34− , testCase "applyJoinTemplateList" testApplyJoinTemplateList
35− ]
36−
37− , fromAssertions "parseTemplate"
38− [ Right [Chunk "Hello ", Expr (Call "guest" [])]
39− @=? parse "Hello $guest()$"
40− , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
41− @=? parse "$if(a(\"bar\"))$foo$endif$"
42− -- 'If' trim check.
43− , Right [ TrimL
44− , If (Ident (TemplateKey "body"))
45− [ TrimR
46− , Chunk "\n"
47− , Expr (Ident (TemplateKey "body"))
48− , Chunk "\n"
49− , TrimL
50− ]
51− (Just [ TrimR
52− , Chunk "\n"
53− , Expr (Ident (TemplateKey "body"))
54− , Chunk "\n"
55− , TrimL
56− ])
57− , TrimR
58− ]
59− @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
60− -- 'For' trim check.
61− , Right [ TrimL
62− , For (Ident (TemplateKey "authors"))
63− [TrimR, Chunk "\n body \n", TrimL]
64− Nothing
65− , TrimR
66− ]
67− @=? parse "$-for(authors)-$\n body \n$-endfor-$"
68− -- 'Partial' trim check.
69− , Right [ TrimL
70− , Partial (StringLiteral "path")
71− , TrimR
72− ]
73− @=? parse "$-partial(\"path\")-$"
74− -- 'Expr' trim check.
75− , Right [ TrimL
76− , Expr (Ident (TemplateKey "foo"))
77− , TrimR
78− ]
79− @=? parse "$-foo-$"
80− -- fail on incomplete template.
81− , assertBool "did not yield error" $ isLeft $
82− parse "a$b"
83− -- fail on mismatched template syntax.
84− , assertBool "did not fail to parse" $ isLeft $
85− parse "$for(xs)$\n <p>foo</p>\n$endif$"
86− ]
87−
88− , [testCase "embeddedTemplate" testEmbeddedTemplate]
89 ]
28 ]
90− where
91− parse = parseTemplateElemsFile ""
92
29
93
30
94--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
95−test :: (Identifier, Identifier, Identifier) -> Assertion
32+case01 :: Assertion
96−test (outf, tplf, itemf) = do
33+case01 = do
97 store <- newTestStore
34 store <- newTestStore
98 provider <- newTestProvider store
35 provider <- newTestProvider store
99
36
100− out <- resourceString provider outf
37+ out <- resourceString provider "template.html.out"
101− tpl <- testCompilerDone store provider tplf templateBodyCompiler
38+ tpl <- testCompilerDone store provider "template.html" $
39+ templateCompiler
102− item <- testCompilerDone store provider itemf $
40+ item <- testCompilerDone store provider "example.md" $
103− getResourceBody
104− >>= renderParagraphs
105− >>= applyTemplate (itemBody tpl) testContext
41+ pandocCompiler >>= applyTemplate (itemBody tpl) testContext
106
42
107 out @=? itemBody item
43 out @=? itemBody item
108− cleanTestEnv
109
44
110
45
111--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
112testContext :: Context String
47testContext :: Context String
113testContext = mconcat
48testContext = mconcat
114− [ defaultContext
115− , listField "authors" (bodyField "name") $ do
116− n1 <- makeItem "Jan"
117− n2 <- makeItem "Piet"
118− return [n1, n2]
119− , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
49+ [ functionField "echo" (\args _ -> return $ unwords args)
50+ , defaultContext
120 ]
51 ]
121−
122−
123−--------------------------------------------------------------------------------
124−testApplyJoinTemplateList :: Assertion
125−testApplyJoinTemplateList = do
126− store <- newTestStore
127− provider <- newTestProvider store
128− tpl <- testCompilerDone store provider "tpl" $
129− compileTemplateItem (Item "tpl" "<b>$body$</b>")
130− str <- testCompilerDone store provider "item3" $
131− applyJoinTemplateList ", " tpl defaultContext [i1, i2]
132−
133− str @?= "<b>Hello</b>, <b>World</b>"
134− cleanTestEnv
135− where
136− i1 = Item "item1" "Hello"
137− i2 = Item "item2" "World"
138−
139−
140−--------------------------------------------------------------------------------
141−embeddedTemplate :: Template
142−embeddedTemplate = $(embedTemplate "tests/data/embed.html")
143−
144−--------------------------------------------------------------------------------
145−testEmbeddedTemplate :: Assertion
146−testEmbeddedTemplate = do
147− store <- newTestStore
148− provider <- newTestProvider store
149− str <- testCompilerDone store provider "item3" $
150− applyTemplate embeddedTemplate defaultContext item
151−
152− itemBody str @?= "<p>Hello, world</p>\n"
153− cleanTestEnv
154− where
155− item = Item "item1" "Hello, world"
156−
tests/TestSuite.hs+2−15
@@ -1,18 +1,16 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3module Main
2module Main
4 ( main
3 ( main
5 ) where
4 ) where
6
5
7
6
8--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
9−import Test.Tasty (defaultMain, testGroup)
8+import Test.Framework (defaultMain)
10
9
11
10
12--------------------------------------------------------------------------------
11--------------------------------------------------------------------------------
13import qualified Hakyll.Core.Dependencies.Tests
12import qualified Hakyll.Core.Dependencies.Tests
14import qualified Hakyll.Core.Identifier.Tests
13import qualified Hakyll.Core.Identifier.Tests
15−import qualified Hakyll.Core.Provider.Metadata.Tests
16import qualified Hakyll.Core.Provider.Tests
14import qualified Hakyll.Core.Provider.Tests
17import qualified Hakyll.Core.Routes.Tests
15import qualified Hakyll.Core.Routes.Tests
18import qualified Hakyll.Core.Rules.Tests
16import qualified Hakyll.Core.Rules.Tests
@@ -20,23 +18,17 @@ import qualified Hakyll.Core.Runtime.Tests
20import qualified Hakyll.Core.Store.Tests
18import qualified Hakyll.Core.Store.Tests
21import qualified Hakyll.Core.UnixFilter.Tests
19import qualified Hakyll.Core.UnixFilter.Tests
22import qualified Hakyll.Core.Util.String.Tests
20import qualified Hakyll.Core.Util.String.Tests
23−import qualified Hakyll.Web.CompressCss.Tests
24import qualified Hakyll.Web.Html.RelativizeUrls.Tests
21import qualified Hakyll.Web.Html.RelativizeUrls.Tests
25import qualified Hakyll.Web.Html.Tests
22import qualified Hakyll.Web.Html.Tests
26−#ifdef USE_PANDOC
27−import qualified Hakyll.Web.Pandoc.FileType.Tests
28−#endif
29import qualified Hakyll.Web.Template.Context.Tests
23import qualified Hakyll.Web.Template.Context.Tests
30import qualified Hakyll.Web.Template.Tests
24import qualified Hakyll.Web.Template.Tests
31−import qualified Hakyll.Web.Tags.Tests
32
25
33
26
34--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
35main :: IO ()
28main :: IO ()
36−main = defaultMain $ testGroup "Hakyll"
29+main = defaultMain
37 [ Hakyll.Core.Dependencies.Tests.tests
30 [ Hakyll.Core.Dependencies.Tests.tests
38 , Hakyll.Core.Identifier.Tests.tests
31 , Hakyll.Core.Identifier.Tests.tests
39− , Hakyll.Core.Provider.Metadata.Tests.tests
40 , Hakyll.Core.Provider.Tests.tests
32 , Hakyll.Core.Provider.Tests.tests
41 , Hakyll.Core.Routes.Tests.tests
33 , Hakyll.Core.Routes.Tests.tests
42 , Hakyll.Core.Rules.Tests.tests
34 , Hakyll.Core.Rules.Tests.tests
@@ -44,13 +36,8 @@ main = defaultMain $ testGroup "Hakyll"
44 , Hakyll.Core.Store.Tests.tests
36 , Hakyll.Core.Store.Tests.tests
45 , Hakyll.Core.UnixFilter.Tests.tests
37 , Hakyll.Core.UnixFilter.Tests.tests
46 , Hakyll.Core.Util.String.Tests.tests
38 , Hakyll.Core.Util.String.Tests.tests
47− , Hakyll.Web.CompressCss.Tests.tests
48 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
39 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
49 , Hakyll.Web.Html.Tests.tests
40 , Hakyll.Web.Html.Tests.tests
50−#ifdef USE_PANDOC
51− , Hakyll.Web.Pandoc.FileType.Tests.tests
52−#endif
53− , Hakyll.Web.Tags.Tests.tests
54 , Hakyll.Web.Template.Context.Tests.tests
41 , Hakyll.Web.Template.Context.Tests.tests
55 , Hakyll.Web.Template.Tests.tests
42 , Hakyll.Web.Template.Tests.tests
56 ]
43 ]
tests/TestSuite/Util.hs+15−37
@@ -6,38 +6,35 @@ module TestSuite.Util
6 , newTestProvider
6 , newTestProvider
7 , testCompiler
7 , testCompiler
8 , testCompilerDone
8 , testCompilerDone
9− , testCompilerError
10 , testConfiguration
9 , testConfiguration
11 , cleanTestEnv
10 , cleanTestEnv
12− , renderParagraphs
13 ) where
11 ) where
14
12
15
13
16--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
17−import Data.List (intercalate, isInfixOf)
18−import Data.Monoid (mempty)
15+import Data.Monoid (mempty)
19−import qualified Data.Set as S
16+import qualified Data.Set as S
20−import Test.Tasty
17+import Test.Framework
21−import Test.Tasty.HUnit
18+import Test.Framework.Providers.HUnit
19+import Test.HUnit hiding (Test)
22−import Text.Printf (printf)
20+import Text.Printf (printf)
23
21
24
22
25--------------------------------------------------------------------------------
23--------------------------------------------------------------------------------
26import Hakyll.Core.Compiler.Internal
24import Hakyll.Core.Compiler.Internal
27import Hakyll.Core.Configuration
25import Hakyll.Core.Configuration
28import Hakyll.Core.Identifier
26import Hakyll.Core.Identifier
29−import qualified Hakyll.Core.Logger as Logger
27+import qualified Hakyll.Core.Logger as Logger
30import Hakyll.Core.Provider
28import Hakyll.Core.Provider
31−import Hakyll.Core.Store (Store)
29+import Hakyll.Core.Store (Store)
32−import qualified Hakyll.Core.Store as Store
30+import qualified Hakyll.Core.Store as Store
33import Hakyll.Core.Util.File
31import Hakyll.Core.Util.File
34−import Hakyll.Core.Item
35
32
36
33
37--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
38fromAssertions :: String -- ^ Name
35fromAssertions :: String -- ^ Name
39 -> [Assertion] -- ^ Cases
36 -> [Assertion] -- ^ Cases
40− -> [TestTree] -- ^ Result tests
37+ -> [Test] -- ^ Result tests
41fromAssertions name =
38fromAssertions name =
42 zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
39 zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
43
40
@@ -49,7 +46,7 @@ newTestStore = Store.new True $ storeDirectory testConfiguration
49
46
50--------------------------------------------------------------------------------
47--------------------------------------------------------------------------------
51newTestProvider :: Store -> IO Provider
48newTestProvider :: Store -> IO Provider
52−newTestProvider store = newProvider store (const $ return False) $
49+newTestProvider store = newProvider store (const False) $
53 providerDirectory testConfiguration
50 providerDirectory testConfiguration
54
51
55
52
@@ -79,23 +76,14 @@ testCompilerDone store provider underlying compiler = do
79 result <- testCompiler store provider underlying compiler
76 result <- testCompiler store provider underlying compiler
80 case result of
77 case result of
81 CompilerDone x _ -> return x
78 CompilerDone x _ -> return x
82− CompilerError e -> fail $
79+ CompilerError e -> error $
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
80 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
84− " threw: " ++ intercalate "; " (compilerErrorMessages e)
81+ " threw: " ++ e
85− CompilerRequire i _ -> fail $
82+ CompilerRequire i _ -> error $
86 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
87 " requires: " ++ show i
84 " requires: " ++ show i
88− CompilerSnapshot _ _ -> fail
89− "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
90
85
91−testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()
86+
92−testCompilerError store provider underlying compiler expectedMessage = do
93− result <- testCompiler store provider underlying compiler
94− case result of
95− CompilerError e ->
96− any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?
97− "Expecting '" ++ expectedMessage ++ "' error"
98− _ -> assertFailure "Expecting CompilerError"
99
87
100--------------------------------------------------------------------------------
88--------------------------------------------------------------------------------
101testConfiguration :: Configuration
89testConfiguration :: Configuration
@@ -113,13 +101,3 @@ cleanTestEnv = do
113 removeDirectory $ destinationDirectory testConfiguration
101 removeDirectory $ destinationDirectory testConfiguration
114 removeDirectory $ storeDirectory testConfiguration
102 removeDirectory $ storeDirectory testConfiguration
115 removeDirectory $ tmpDirectory testConfiguration
103 removeDirectory $ tmpDirectory testConfiguration
116−
117−
118−--------------------------------------------------------------------------------
119−-- | like 'Hakyll.Web.Pandoc.renderPandoc'
120−-- | but allowing to test without the @usePandoc@ flag
121−renderParagraphs :: Item String -> Compiler (Item String)
122−renderParagraphs = withItemBody (return
123− . intercalate "\n" -- no trailing line
124− . map (("<p>"++) . (++"</p>"))
125− . lines)
tests/data/embed.htmldeleted−1
@@ -1 +0,0 @@
1−<p>$body$</p>
tests/data/example.md.metadata−3
@@ -1,5 +1,2 @@
1external: External data
1external: External data
2date: 2012-10-22 14:35:24
2date: 2012-10-22 14:35:24
3−subblog: food
4−intfield: 42
5−numfield: 3.14
tests/data/images/favicon.icodeleted

Binary file not shown.

tests/data/just-meta.htmldeleted−7
@@ -1,7 +0,0 @@
1−<pre>
2−external: {$external$}
3−date: {$date$}
4−subblog: {$subblog$}
5−intfield: {$intfield$}
6−numfield: {$numfield$}
7−</pre>
tests/data/just-meta.html.outdeleted−7
@@ -1,7 +0,0 @@
1−<pre>
2−external: {External data}
3−date: {2012-10-22 14:35:24}
4−subblog: {food}
5−intfield: {42}
6−numfield: {3.14}
7−</pre>
tests/data/partial-helper.htmldeleted−3
@@ -1,3 +0,0 @@
1−<p>This is an included partial.</p>
2−
3−$body$
tests/data/partial.htmldeleted−3
@@ -1,3 +0,0 @@
1−<p>This is a file that includes a partial.</p>
2−
3−$partial("partial-helper.html")$
tests/data/partial.html.outdeleted−7
@@ -1,7 +0,0 @@
1−<p>This is a file that includes a partial.</p>
2−
3−<p>This is an included partial.</p>
4−
5−This is an example.
6−
7−
tests/data/posts/2018-09-26.mddeleted−1
@@ -1 +0,0 @@
1−Something happened today.
tests/data/posts/2019/05/10/tomorrow.mddeleted−1
@@ -1 +0,0 @@
1−This day hasn't happened yet (as of writing this).
tests/data/strip.htmldeleted−34
@@ -1,34 +0,0 @@
1−<div>
2− I'm so rich I have $$3.
3−
4− $rev("foo")$
5− $-rev(rev("foo"))$
6−
7− $if(body)-$
8− I have body
9− $else-$
10− or no
11− $-endif-$
12−
13− $if(unbound)$
14− should not be printed
15− $endif$
16−
17− $-if(body)-$
18− should be printed
19− $-endif$
20−
21− <ul>
22− $for(authors)-$
23− <li>$name$</li>
24− $endfor-$
25− </ul>
26−
27− $for(authors)-$
28− $name-$
29− $sep$,
30− $-endfor$
31−
32− $body$
33−</div>
34−
tests/data/strip.html.outdeleted−18
@@ -1,18 +0,0 @@
1−<div>
2− I'm so rich I have $3.
3−
4− ooffoo
5−
6− I have body
7− should be printed
8−
9− <ul>
10− <li>Jan</li>
11− <li>Piet</li>
12− </ul>
13−
14− Jan,Piet
15−
16− <p>This is an example.</p>
17−</div>
18−
tests/data/template.html+1−26
@@ -1,30 +1,5 @@
1<div>
1<div>
2 I'm so rich I have $$3.
2 I'm so rich I have $$3.
3−
3+ $echo test!$
4− $rev("foo")$
5− $rev(rev("foo"))$
6−
7− $if(body)$
8− I have body
9− $else$
10− or no
11− $endif$
12−
13− $if(unbound)$
14− should not be printed
15− $endif$
16−
17− $if(body)$
18− should be printed
19− $endif$
20−
21− <ul>
22− $for(authors)$
23− <li>$name$</li>
24− $endfor$
25− </ul>
26−
27− $for(authors)$$name$$sep$, $endfor$
28−
29 $body$
4 $body$
30</div>
5</div>
tests/data/template.html.out+1−24
@@ -1,28 +1,5 @@
1<div>
1<div>
2 I'm so rich I have $3.
2 I'm so rich I have $3.
3−
3+ test!
4− oof
5− foo
6−
7−
8− I have body
9−
10−
11−
12−
13−
14− should be printed
15−
16−
17− <ul>
18−
19− <li>Jan</li>
20−
21− <li>Piet</li>
22−
23− </ul>
24−
25− Jan, Piet
26−
27 <p>This is an example.</p>
4 <p>This is an example.</p>
28</div>
5</div>
web/about.markdown−2
@@ -38,5 +38,3 @@ who still maintains the package. Contributors:
38- [seschwar](https://github.com/seschwar)
38- [seschwar](https://github.com/seschwar)
39- [favonia](https://github.com/favonia)
39- [favonia](https://github.com/favonia)
40- [Robin Windels](https://github.com/rwindelz)
40- [Robin Windels](https://github.com/rwindelz)
41−- [Miikka Koskinen](http://miikka.me/)
42−- [Simonas Kazlauskas](http://kazlauskas.me/)
web/css/syntax.css+18−61
@@ -1,61 +1,18 @@
1−a.sourceLine { display: inline-block; line-height: 1.25; }
1+/* Generated by pandoc. */
2−a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
2+table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
3−a.sourceLine:empty { height: 1.2em; }
3+ { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
4−.sourceCode { overflow: visible; }
4+td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
5−code.sourceCode { white-space: pre; position: relative; }
5+td.sourceCode { padding-left: 5px; }
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 */
6+pre.sourceCode span.kw { color: #007020; font-weight: bold; }
39−code span.ch { color: #4070a0; } /* Char */
40−code span.cn { color: #880000; } /* Constant */
41−code span.co { color: #60a0b0; font-style: italic; } /* Comment */
42−code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
43−code span.do { color: #ba2121; font-style: italic; } /* Documentation */
44−code span.dt { color: #902000; } /* DataType */
7+pre.sourceCode span.dt { color: #902000; }
45−code span.dv { color: #40a070; } /* DecVal */
8+pre.sourceCode span.dv { color: #40a070; }
46−code span.er { color: #ff0000; font-weight: bold; } /* Error */
9+pre.sourceCode span.bn { color: #40a070; }
47−code span.ex { } /* Extension */
48−code span.fl { color: #40a070; } /* Float */
10+pre.sourceCode span.fl { color: #40a070; }
11+pre.sourceCode span.ch { color: #4070a0; }
12+pre.sourceCode span.st { color: #4070a0; }
13+pre.sourceCode span.co { color: #60a0b0; font-style: italic; }
14+pre.sourceCode span.ot { color: #007020; }
15+pre.sourceCode span.al { color: red; font-weight: bold; }
49−code span.fu { color: #06287e; } /* Function */
16+pre.sourceCode span.fu { color: #06287e; }
50−code span.im { } /* Import */
17+pre.sourceCode span.re { }
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 */
18+pre.sourceCode span.er { color: red; font-weight: bold; }
web/examples.markdown+12−176
@@ -9,184 +9,8 @@ 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−
17−## Hakyll 4
18−
19- <http://jaspervdj.be/>,
12- <http://jaspervdj.be/>,
20 [source](https://github.com/jaspervdj/jaspervdj)
13 [source](https://github.com/jaspervdj/jaspervdj)
21−- <http://www.imagination-land.org/>,
22− [source](https://github.com/Keruspe/blog/)
23−- <https://bananasandlenses.net/>,
24− [source](https://github.com/denisshevchenko/bananasandlenses)
25−- <http://blog.clement.delafargue.name/>,
26− [source](https://github.com/divarvel/blog)
27−- <http://bneijt.nl>,
28− [source](https://github.com/bneijt/bneijt.nl)
29−- <http://brianshourd.com/>,
30− [source](https://github.com/brianshourd/brianshourd.com)
31−- <http://variadic.me>,
32− [source](https://github.com/eakron/variadic.me)
33−- <http://yannesposito.com/>,
34− [source](https://github.com/yogsototh/yblog)
35−- <http://dannysu.com/>,
36− [source](https://github.com/dannysu/hakyll-blog)
37−- <http://meta.plasm.us/>,
38− [source](https://github.com/travisbrown/metaplasm)
39−- <http://workforpizza.com/>,
40− [source](https://github.com/irneh/workforpizza)
41−- <http://poleiro.info/>,
42− [source](https://github.com/arthuraa/poleiro)
43−- <http://www.austinrochford.com/>,
44− [source](https://github.com/AustinRochford/blog)
45−- <http://www.regisfoucault.com/>,
46− [source](https://github.com/regisfoucault/blog)
47−- <http://www.chaoxuprime.com/>,
48− [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
49−- <http://www.skybluetrades.net/>,
50− [source](https://github.com/ian-ross/blog)
51−- <http://chromaticleaves.com/>,
52− [source](https://github.com/ericrasmussen/chromaticleaves)
53−- <http://www.clever-cloud.com/en/>,
54− [source](https://github.com/CleverCloud/clever-cloud.com)
55−- <http://blaenkdenum.com/>,
56− [source](https://github.com/blaenk/blaenk.github.io)
57−- <https://xinitrc.de/>,
58− [source](https://github.com/xinitrc/xinitrc.de)
59−- <http://nickcharlton.net/>,
60− [source](https://github.com/nickcharlton/nickcharlton.net)
61−- <http://scr.stunts.hu/>,
62− [literate source](http://scr.stunts.hu/hakyll.html)
63−- <http://www.eanalytica.com/>,
64− [literate source](http://www.eanalytica.com/site/)
65−- <http://cse.iitk.ac.in/users/ppk>,
66− [source](https://github.com/piyush-kurur-pages/website)
67−- <http://web.engr.oregonstate.edu/~walkiner/>,
68− [source](https://github.com/walkie/WebPage)
69−- <http://techblog.rosedu.org/>,
70− [source](https://github.com/rosedu/techblog)
71−- <http://freizl.github.io>,
72− [source](https://github.com/freizl/freizl.github.com/tree/master/build)
73−- <http://www.gwern.net/>,
74− [source](https://github.com/gwern/gwern.net)
75−- <http://www.corentindupont.info/>,
76− [source](https://github.com/cdupont/CorentinDupont-WebPage)
77−- <http://jelv.is>,
78− [source](https://github.com/TikhonJelvis/website)
79−- <https://secure.plaimi.net/>,
80− [source](https://github.com/plaimi/www)
81−- <http://duplode.github.io/>,
82− [source](https://github.com/duplode/duplode.github.io/tree/sources)
83−- <http://tzemanovic.github.io/>,
84− [source](https://github.com/tzemanovic/tzemanovic)
85−- <http://listx.github.io>,
86− [source](https://github.com/listx/listx_blog)
87−- <http://kyle.marek-spartz.org>,
88− [source](https://github.com/zeckalpha/kyle.marek-spartz.org)
89−- <http://ladatura-corsets.com/>,
90− [source](https://github.com/dsferruzza/datura-corsets)
91−- <http://reichertbrothers.com/>,
92− [source](https://github.com/rbros/reichertbrothers.com)
93−- <http://alemedeiros.sdf.org>,
94− [source](https://github.com/alemedeiros/homepage)
95−- <http://www.haskell.mn>,
96− [source](https://github.com/HaskellMN/www.haskell.mn)
97−- <https://axiomatic.neophilus.net/>,
98− [source](https://github.com/Libbum/AxiomaticSemantics)
99−- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
100− [source](https://github.com/fractalcat/tesser.org)
101−- <https://weien.io/>,
102−  [source](https://github.com/wei2912/blog-src)
103−- <http://www.metasepi.org/>,
104− [source](https://github.com/metasepi/metasepi-website)
105−- <http://gisli.hamstur.is/>,
106− [source](https://github.com/gislik/gisli.hamstur.is)
107−- <http://blog.ssanj.net/>,
108− [source](https://github.com/ssanj/babyloncandle)
109−- <http://ruhaskell.org/>,
110− [source](https://github.com/ruHaskell/ruhaskell)
111−- <http://www.haskell.od.ua/>,
112− [source](https://github.com/odhug/odhug.github.com)
113−- <http://blog.rekahsoft.ca/>,
114− [source](http://git.rekahsoft.ca/blog-rekahsoft-ca)
115−- <http://www.andreascarpino.it>,
116− [source](https://gitlab.com/ilpianista/website)
117−- <http://cascadeofinsights.com/>,
118− [source](https://github.com/agbell/agbell.github.io/tree/hakyll)
119−- <http://www.xoltar.org/>,
120− [source](https://github.com/xoltar/xoltar.org)
121−- <http://sigkill.dk/>
122− [literate source](http://sigkill.dk/programs/sigkill.html)
123−- <http://curry-club-augsburg.de/>,
124− [source](https://github.com/curry-club-aux/curry-club-augsburg.de)
125−- <http://ismailmustafa.com/>,
126− [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
127−- <http://lettier.github.io/>,
128− [source](https://github.com/lettier/lettier.github.io)
129−- <http://raindev.io>,
130− [source](https://github.com/raindev/raindev.github.io)
131−- <http://hal2016.haskell.org/>,
132− [source](https://github.com/nomeata/hal2016-website/)
133−- <http://acehack.org/>,
134− [source](https://github.com/sakshamsharma/acehack/)
135−- <https://nek0.eu/>,
136− [source](https://github.com/nek0/nek0.eu)
137−- <http://static-dust.klpn.se/>,
138− [source](https://github.com/klpn/static-dust)
139−- <http://www.kuznero.com/>,
140− [source](https://github.com/kuznero/kuznero.github.io)
141−- <http://nicolerauch.de/>,
142− [source](https://github.com/NicoleRauch/BusinessWebsite)
143−- <http://johnduhamel.io/>,
144− [source](https://github.com/jjduhamel/blog)
145−- <http://alexanderlobov.ru/>,
146− [source](https://github.com/alexanderlobov/homepage)
147−- <http://www.rosanbo.com/>,
148− [source](https://github.com/polo2ro/blog)
149−- <http://limansky.me/>,
150− [source](https://github.com/limansky/limansky_me)
151−- <http://rahul.nu/>,
152− [source](https://github.com/rp/rp.github.io/tree/dev)
153−- <http://haskanything.com/>,
154− [source](https://github.com/beerendlauwers/HaskAnything)
155−- <https://anler.me/>,
156− [source](https://github.com/anler/anler.me)
157−- <http://argumatronic.com/>,
158− [source](https://gitlab.com/GinBaby/argumatronic/tree/master)
159−- <http://sillybytes.net/>,
160− [source](https://github.com/sillybytes/sillybytes)
161−- <https://falconpilot.github.io/>,
162− [source](https://github.com/FalconPilot/falconpilot.github.io)
163−- <http://spyhop.cafe/>,
164− [source](https://github.com/thunky-monk/spyhop-cafe)
165−- <https://bendo.github.io/>,
166− [source](https://github.com/bendo/bendo.github.io)
167−- <http://adarsh.cc/>,
168− [source](https://github.com/adarshp/adarsh.cc)
169−- <http://0a.io>,
170− [source](https://github.com/arrchyy/0a.io)
171−- <https://nasy.moe/>,
172− [source](https://github.com/nasyxx/NasyMoe)
173−- <http://uniprocess.org/>,
174− [source](https://gitlab.com/uniprocess/uniprocess.org)
175−- <https://regularflolloping.com/>,
176− [source](https://github.com/techieAgnostic/rf)
177−- <http://enter-haken.github.io/>,
178− [source](https://github.com/enter-haken/blog)
179−- <https://modular.blind.guru/>,
180− [source](https://github.com/mlang/eurorack-metadata)
181−- <http://www.jonashietala.se/>,
182− [source](https://github.com/treeman/jonashietala)
183−- <https://abhinavsarkar.net/>,
184− [source](https://github.com/abhin4v/abhin4v.github.io)
185−- <https://dailyreadingnotes.com/>,
186− [source](https://github.com/hackeryarn/daily-reading-notes)
187−
188−## Hakyll 3.X
189−
190- <http://ethanschoonover.com/>,
14- <http://ethanschoonover.com/>,
191 [source](https://github.com/altercation/ethanschoonover.com)
15 [source](https://github.com/altercation/ethanschoonover.com)
192- <http://extralogical.net/>,
16- <http://extralogical.net/>,
@@ -199,10 +23,18 @@ directly with the default Hakyll site.
199 [source](https://github.com/wunki/wunki.org)
23 [source](https://github.com/wunki/wunki.org)
200- <http://rs.io>,
24- <http://rs.io>,
201 [source](https://github.com/robertseaton/rs.io/)
25 [source](https://github.com/robertseaton/rs.io/)
26+- <http://www.gwern.net/>,
27+ source: `darcs get http://www.gwern.net`
28+- <http://www.skybluetrades.net/>,
29+ [source](https://github.com/ian-ross/blog)
202- <http://blog.coldflake.com/>,
30- <http://blog.coldflake.com/>,
203 [source](https://github.com/marcmo/blog.coldflake)
31 [source](https://github.com/marcmo/blog.coldflake)
204- <http://deepak.jois.name>,
32- <http://deepak.jois.name>,
205 [source](https://github.com/deepakjois/website)
33 [source](https://github.com/deepakjois/website)
34+- <http://sigkill.dk/>,
35+ [literate source](http://sigkill.dk/programs/sigkill.html)
36+- <http://variadic.me>,
37+ [source](https://github.com/eakron/variadic.me)
206- <http://orftz.com>,
38- <http://orftz.com>,
207 [source](https://github.com/orftz/orftz.com)
39 [source](https://github.com/orftz/orftz.com)
208- <http://citationneeded.me/>,
40- <http://citationneeded.me/>,
@@ -211,5 +43,9 @@ directly with the default Hakyll site.
211 [source](http://gitorious.org/shakthimaan-blog)
43 [source](http://gitorious.org/shakthimaan-blog)
212- <http://www.alfredodinapoli.com/>,
44- <http://www.alfredodinapoli.com/>,
213 [source](https://github.com/CharlesStain/alfredodinapoli.com)
45 [source](https://github.com/CharlesStain/alfredodinapoli.com)
46+- <http://www.rohanjain.in/>,
47+ [source](https://github.com/crodjer/rohanjain.in)
48+- <http://datahackermd.com>,
49+ [source](http://github.com/akshayjshah/datahackermd)
214- <http://michaelxavier.net>,
50- <http://michaelxavier.net>,
215 [source](https://github.com/michaelxavier/michaelxavier.net)
51 [source](https://github.com/michaelxavier/michaelxavier.net)
web/images/hakyll-system-2.png

Binary file not shown.

web/index.markdown+2−4
@@ -31,10 +31,8 @@ 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`, or
34+You can get the latest version from hackage using `cabal install hakyll`. Then,
35−using [stack] by using `stack install hakyll`. Then, you can:
35+you can:
36−
37−[stack]: http://www.haskellstack.org/
38
36
39- read the [tutorials](/tutorials.html);
37- read the [tutorials](/tutorials.html);
40- mail the [google discussion group](http://groups.google.com/group/hakyll);
38- mail the [google discussion group](http://groups.google.com/group/hakyll);
web/libraries.markdowndeleted−21
@@ -1,21 +0,0 @@
1−---
2−title: Libraries
3−---
4−
5−# Libraries
6−
7−Since some functionality is too specific to be included in the main Hakyll
8−library, people have generously written utility libraries and tools for Hakyll.
9−Here is a list of the ones I know of (I would be happy to hear about others).
10−
11−- [hakyll-contrib-links](http://hackage.haskell.org/package/hakyll-contrib-links),
12− a Hakyll library that helps maintain a separate links database.
13−- [hakyll-agda](https://hackage.haskell.org/package/hakyll-agda),
14− a simple module useful to generate blog posts from literate Agda files.
15−- [hakyll-contrib-hyphenation](http://hackage.haskell.org/package/hakyll-contrib-hyphenation),
16− automatic hyphenation for Hakyll.
17−- [hakyll-convert](https://hackage.haskell.org/package/hakyll-convert),
18− a conversion tool from other blog engines to Hakyll.
19−- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
20− Hakyll wrapper for the Elm compiler.
21−- [hakyll-sass](http://hackage.haskell.org/package/hakyll-sass), a compiler for SASS templates.
web/site.hs+33−31
@@ -1,23 +1,19 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3−import Control.Arrow (second)
3+import Control.Applicative ((<$>))
4+import Control.Arrow (second)
4−import Control.Monad (filterM, forM_)
5+import Control.Monad (forM_)
6+import Data.Char (isDigit)
5−import Data.List (isPrefixOf, sortBy)
7+import Data.List (isPrefixOf, partition)
6−import Data.Monoid ((<>))
8+import Data.Monoid (mappend)
7−import Data.Ord (comparing)
8import Hakyll
9import Hakyll
9−import System.Directory (copyFile)
10−import System.FilePath (dropTrailingPathSeparator, splitPath)
10+import System.FilePath (dropTrailingPathSeparator, splitPath)
11import Text.Pandoc
11import Text.Pandoc
12
12
13
13
14--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
15main :: IO ()
15main :: IO ()
16main = hakyllWith config $ do
16main = hakyllWith config $ do
17− -- Copy CHANGELOG.md here.
18− preprocess $ copyFile "../CHANGELOG.md" "releases.markdown"
19−
20− -- CSS
21 match "css/*" $ do
17 match "css/*" $ do
22 route idRoute
18 route idRoute
23 compile compressCssCompiler
19 compile compressCssCompiler
@@ -47,7 +43,7 @@ main = hakyllWith config $ do
47 -- Tutorials
43 -- Tutorials
48 match "tutorials/*" $ do
44 match "tutorials/*" $ do
49 route $ setExtension "html"
45 route $ setExtension "html"
50− compile $ pandocCompilerWith defaultHakyllReaderOptions withToc
46+ compile $ pandocCompilerWith defaultHakyllParserState withToc
51 >>= loadAndApplyTemplate "templates/tutorial.html" defaultContext
47 >>= loadAndApplyTemplate "templates/tutorial.html" defaultContext
52 >>= loadAndApplyTemplate "templates/default.html" defaultContext
48 >>= loadAndApplyTemplate "templates/default.html" defaultContext
53 >>= relativizeUrls
49 >>= relativizeUrls
@@ -56,11 +52,23 @@ main = hakyllWith config $ do
56 create ["tutorials.html"] $ do
52 create ["tutorials.html"] $ do
57 route idRoute
53 route idRoute
58 compile $ do
54 compile $ do
59− ctx <- tutorialsCtx <$>
60− sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
55+ tutorials <- loadAll "tutorials/*"
56+ itemTpl <- loadBody "templates/tutorial-item.html"
57+ let (series, articles) = partitionTutorials $
58+ chronological tutorials
59+
60+ series' <- applyTemplateList itemTpl defaultContext series
61+ articles' <- applyTemplateList itemTpl defaultContext articles
62+
63+ let tutorialsCtx =
64+ constField "title" "Tutorials" `mappend`
65+ constField "series" series' `mappend`
66+ constField "articles" articles' `mappend`
67+ defaultContext
68+
61 makeItem ""
69 makeItem ""
62− >>= loadAndApplyTemplate "templates/tutorials.html" ctx
70+ >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
63− >>= loadAndApplyTemplate "templates/default.html" ctx
71+ >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
64 >>= relativizeUrls
72 >>= relativizeUrls
65
73
66 -- Templates
74 -- Templates
@@ -68,7 +76,8 @@ main = hakyllWith config $ do
68 where
76 where
69 withToc = defaultHakyllWriterOptions
77 withToc = defaultHakyllWriterOptions
70 { writerTableOfContents = True
78 { writerTableOfContents = True
71− , writerTemplate = Just "$toc$\n$body$"
79+ , writerTemplate = "$toc$\n$body$"
80+ , writerStandalone = True
72 }
81 }
73
82
74
83
@@ -77,7 +86,7 @@ config :: Configuration
77config = defaultConfiguration
86config = defaultConfiguration
78 { deployCommand = "rsync --checksum -ave 'ssh -p 2222' \
87 { deployCommand = "rsync --checksum -ave 'ssh -p 2222' \
79 \_site/* \
88 \_site/* \
80− \jaspervdj@jaspervdj.be:jaspervdj.be/hakyll/"
89+ \jaspervdj@jaspervdj.be:jaspervdj.be/hakyll/hakyll4"
81 }
90 }
82
91
83
92
@@ -102,16 +111,9 @@ hackage url
102
111
103
112
104--------------------------------------------------------------------------------
113--------------------------------------------------------------------------------
105−-- | Partition tutorials into tutorial series, other articles, external articles
114+-- | Partition tutorials into tutorial series & other articles
106−tutorialsCtx :: [Item String] -> Context String
115+partitionTutorials :: [Item a] -> ([Item a], [Item a])
107−tutorialsCtx tuts =
116+partitionTutorials = partition $ \i ->
108− constField "title" "Tutorials" <>
117+ case splitPath (toFilePath $ itemIdentifier i) of
109− listField "main" defaultContext (ofType "main") <>
118+ [_, (x : _)] -> isDigit x
110− listField "articles" defaultContext (ofType "article") <>
119+ _ -> False
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+6−3
@@ -29,13 +29,16 @@
29 <h1>Navigation</h1>
29 <h1>Navigation</h1>
30 <a href="/index.html">home</a>
30 <a href="/index.html">home</a>
31 <a href="/tutorials.html">tutorials</a>
31 <a href="/tutorials.html">tutorials</a>
32− <a href="/libraries.html">libraries</a>
33 <a href="/reference.html">reference</a>
32 <a href="/reference.html">reference</a>
34 <a href="/about.html">about</a>
33 <a href="/about.html">about</a>
35 <a href="/tutorials/faq.html">faq</a>
34 <a href="/tutorials/faq.html">faq</a>
36 <a href="/examples.html">examples</a>
35 <a href="/examples.html">examples</a>
37− <a href="/releases.html">releases</a>
36+
38− <a href="https://jaspervdj.be/contact.html#donate" target="_blank">donate</a>
37+ <!-- Flattr button -->
38+ <h1>Donate</h1>
39+ <a href="http://flattr.com/thing/291889/Hakyll" target="_blank">
40+ <img src="http://api.flattr.com/button/flattr-badge-large.png"
41+ alt="Flattr this" title="Flattr this" border="0" /></a>
39 </div>
42 </div>
40
43
41 <div id="content">
44 <div id="content">
web/templates/tutorial-item.htmlnew file+1
@@ -0,0 +1 @@
1+<li><a href="$url$">$title$</a> by <em>$author$</em></li>
web/templates/tutorials.html+7−50
@@ -1,56 +1,13 @@
1<h1>Tutorials about Hakyll</h1>
1<h1>Tutorials about Hakyll</h1>
2−<h2 id="series">Tutorial series</h2>
2+<h2>Tutorial series</h2>
3−<ul>
3+<ul>$series$</ul>
4− $for(main)$
5− <li>
6− <a href="$url$">$title$</a>
7− </li>
8− $endfor$
9−</ul>
10−<h2 id="articles">Other articles</h2>
4+<h2>Other articles</h2>
11<p>In no particular order:</p>
5<p>In no particular order:</p>
12−<ul>
13− $for(articles)$
6+<ul>$articles$</ul>
14− <li>
15− <a href="$url$">$title$</a> by <em>$author$</em>
16− </li>
17− $endfor$
18−</ul>
19−<h2 id="external-articles">External articles</h2>
20−<p>
21− These are articles on external blogs. If you wrote a similar article, feel
22− free to <a href="http://jaspervdj.be/contact.html">shoot me an email</a> so
23− I can add it to the list.
24−</p>
25−<p>In no particular order:</p>
26−<ul>
27− $for(externals)$
28− <li>
29− <a href="$url$">$title$</a> by <em>$author$</em>
30− </li>
31− $endfor$
32−</ul>
33−<h2 id="robertwpearce">Robert Pearce's series</h2>
34−<p>
35− <a href="https://robertwpearce.com/">Robert Pearce</a> has been writing an
36− excellent tutorial-style series on Hakyll as well. You can find those here:
37−</p>
38−<ul>
39− $for(robertwpearce)$
40− <li>
41− <a href="$url$">$title$</a>
42− </li>
43− $endfor$
44−</ul>
45<p>
7<p>
46 All these tutorials assume you are using the latest stable version of
8 All these tutorials assume you are using the latest stable version of
47 Hakyll. If this is not the case, you might want to update using:
9 Hakyll. If this is not the case, you might want to update using:
48</p>
10</p>
49−<pre><code>$$ ghc-pkg unregister hakyll
11+<pre><code>$ ghc-pkg unregister hakyll
50−$$ cabal update
12+$ cabal update
51−$$ cabal install hakyll</code></pre>
52−
53−<p>
54− Or using stack:
55−</p>
56−<pre><code>$$ stack install hakyll</code></pre>
13+$ cabal install hakyll</code></pre>
web/tutorials/01-installation.markdown+17−20
@@ -1,54 +1,51 @@
1---
1---
2title: Installation
2title: Installation
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4−type: main
5---
4---
6
5
7Installation
6Installation
8------------
7------------
9
8
10−Installation is provided via Hackage, and some packages are available for
9+Installation is provided using [cabal], and some packages are available for
11−different distributions. For installation from source (i.e. via Hackage),
10+different distributions.
12−[stack] is recommended:
13
11
14− $ stack install hakyll
12+ $ cabal install hakyll
15
13
16−[stack]: http://www.haskellstack.org/
14+[cabal]: http://www.haskell.org/cabal/
15+
16+If you have a recent installation of `cabal` and your time is somewhat valuable,
17+use:
18+
19+ $ cabal install -j hakyll
17
20
18Linux distro packages:
21Linux distro packages:
19
22
23+- [Archlinux (AUR)](https://aur.archlinux.org/packages/haskell-hakyll/)
20- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
24- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
21−- [Fedora](https://apps.fedoraproject.org/packages/ghc-hakyll)
22−- [Nix](https://nixos.org/nixos/packages.html#hakyll): `$ nix-env -i hakyll`
23
25
24Building the example site
26Building the example site
25-------------------------
27-------------------------
26
28
27−Apart from the main Hakyll library, the cabal package also provides you with an
29+Apart from the main Hakyll library, the cabal package also provided you with an
28executable `hakyll-init` to create an example site. This is an easy way to get
30executable `hakyll-init` to create an example site. This is an easy way to get
29started:
31started:
30
32
31− $ stack exec hakyll-init my-site
33+ $ hakyll-init my-site
32−
33−This creates a folder `my-site` in the current directory, with some example
34−content and a generic configuration.
35
34
36−If `hakyll-init` is not found, you should make sure your stack bin path
35+If `hakyll-init` is not found, you should make sure `$HOME/.cabal/bin` is in
37−(usually `$HOME/.local/bin`) is in your `$PATH`. You can check your stack local
36+your `$PATH`.
38−bin path by running `stack path --local-bin`.
39
37
40The file `site.hs` holds the configuration of your site, as an executable
38The file `site.hs` holds the configuration of your site, as an executable
41haskell program. We can compile and run it like this:
39haskell program. We can compile and run it like this:
42
40
43 $ cd my-site
41 $ cd my-site
44− $ stack init # creates stack.yaml file based on my-site.cabal
42+ $ ghc --make site.hs
45− $ stack build
46− $ stack exec site build
43+ $ ./site build
47
44
48If you installed `hakyll` with a preview server (this is the default), you can
45If you installed `hakyll` with a preview server (this is the default), you can
49now use
46now use
50
47
51− $ stack exec site watch
48+ $ ./site preview
52
49
53and have a look at your site at
50and have a look at your site at
54[http://localhost:8000/](http://localhost:8000/).
51[http://localhost:8000/](http://localhost:8000/).
web/tutorials/02-basics.markdown+8−13
@@ -1,31 +1,26 @@
1---
1---
2title: The basics
2title: The basics
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4−type: main
5---
4---
6
5
7Building and cleaning
6Building and cleaning
8---------------------
7---------------------
9
8
10If you followed along with the previous tutorial, you should now have the
9If you followed along with the previous tutorial, you should now have the
11−example site up and running. By running `stack exec site build`, you created
10+example site up and running. By running `./site build`, you created two
12−two directories:
11+directories:
13
12
14- `_site`, with your site as HTML files, ready to be deployed;
13- `_site`, with your site as HTML files, ready to be deployed;
15- `_cache`, which Hakyll uses internally.
14- `_cache`, which Hakyll uses internally.
16
15
17−`stack exec site clean` removes these directories, and `stack exec site
16+`./site clean` removes these directories, and `./site rebuild` performs a
18−rebuild` performs a `clean` and then a `build`.
17+`clean` and then a `build`.
19
18
20−In general, you want to use `stack exec site build` when you just made changes
19+In general, it's only necessary to use `rebuild` when you made changes to your
21−to the contents of your website. If you made changes to `site.hs`, you need to
20+`site.hs`, and not when you just made changes to the contents of your website.
22−recompile `site.hs` followed by a rebuild:
23
21
24− stack build
25− stack exec site rebuild
26−
27−At this point, feel free to change some files, `stack exec site build` and see
22+At this point, feel free to change some files, `./site build` and see what
28−what happens!
23+happens!
29
24
30Pages and metadata
25Pages and metadata
31------------------
26------------------
web/tutorials/03-rules-routes-compilers.markdown+2−3
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7Basic rules
6Basic rules
@@ -37,7 +36,7 @@ match "css/*" $ do
37
36
38```
37```
39
38
40−This is a declarative DSL: the order in which you write the rules makes little
39+This is a declarative DSL: the order in which you write the rules make little
41difference, Hakyll will use dependency tracking to determine the correct order.
40difference, Hakyll will use dependency tracking to determine the correct order.
42
41
43We group the different rules using `match`. The first argument for `match` is a
42We group the different rules using `match`. The first argument for `match` is a
@@ -62,7 +61,7 @@ this will become clear soon. The real question here is why we use `create`
62instead of `match`.
61instead of `match`.
63
62
64The answer is simple: there is no `archive.html` file in our project directory!
63The answer is simple: there is no `archive.html` file in our project directory!
65−So if we were to use `match`, no file would be matched, and hence, nothing
64+So if we were to use `match`, no a file would be matched, and hence, nothing
66would appear in the output directory. `create`, however, ensures the items
65would appear in the output directory. `create`, however, ensures the items
67listed are always produced.
66listed are always produced.
68
67
web/tutorials/04-compilers.markdown+28−149
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7Loading items
6Loading items
@@ -42,11 +41,9 @@ Templates
42
41
43Let's have a look at a simple template:
42Let's have a look at a simple template:
44
43
45−```html
46−<h1>$title$</h1>
44+ <h1>$title$</h1>
47−<div class="info">Posted on $date$</div>
45+ <div class="info">Posted on $date$</div>
48−$body$
46+ $body$
49−```
50
47
51As you can probably guess, template files just contain text and only the `$`
48As you can probably guess, template files just contain text and only the `$`
52character has special meaning: text between dollar signs ("fields") is replaced
49character has special meaning: text between dollar signs ("fields") is replaced
@@ -54,11 +51,9 @@ when the template is applied. If you want an actual dollar sign in the output,
54use `$$`.
51use `$$`.
55
52
56You usually compile the templates from disk using the aptly named
53You usually compile the templates from disk using the aptly named
57−`templateBodyCompiler`:
54+`templateCompiler`:
58
55
59−```haskell
60−match "templates/*" $ compile templateBodyCompiler
56+ match "templates/*" $ compile templateCompiler
61−```
62
57
63Notice the lack of `route` here: this is because we don't need to write the
58Notice the lack of `route` here: this is because we don't need to write the
64templates to your `_site` folder, we just want to use them elsewhere.
59templates to your `_site` folder, we just want to use them elsewhere.
@@ -91,9 +86,9 @@ And `$title$` like this:
91
86
92```haskell
87```haskell
93titleContext :: Context a
88titleContext :: Context a
94−titleContext = field "title" $ \item -> do
89+titleContext :: field "title" $ \item -> do
95 metadata <- getMetadata (itemIdentifier item)
90 metadata <- getMetadata (itemIdentifier item)
96− return $ fromMaybe "No title" $ lookupString "title" metadata
91+ return $ fromMaybe "No title" $ M.lookup "title" metadata
97```
92```
98
93
99And compose them using the `Monoid` instance:
94And compose them using the `Monoid` instance:
@@ -116,10 +111,8 @@ you to use:
116- `$path$` for the original filepath of the page;
111- `$path$` for the original filepath of the page;
117- `$foo$` where foo is specified in the metadata.
112- `$foo$` where foo is specified in the metadata.
118
113
119−`$date$` is not provided by default. In the scaffold, we use the convenience
120−context function `dateField`, which will parse an `Item`'s filename to check if
121−it begins with a date. You can see how we add it in the definition of `postCtx`
114+`$date$` is not provided by default, you can see how we add it in the definition
122−in `site.hs`:
115+of `postCtx` in `site.hs`:
123
116
124```haskell
117```haskell
125postCtx :: Context String
118postCtx :: Context String
@@ -131,7 +124,7 @@ postCtx =
131### Loading and applying templates
124### Loading and applying templates
132
125
133Now we know about templates, context and how to load arbitrary items. This gives
126Now we know about templates, context and how to load arbitrary items. This gives
134−us enough background information in order to understand how you can apply a
127+us enough background information in order to understand you can apply a
135template:
128template:
136
129
137```haskell
130```haskell
@@ -148,148 +141,34 @@ compile $
148 pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx
141 pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx
149```
142```
150
143
151−Control flow in templates
152−-------------------------
153−
154−Sometimes string interpolation does not suffice, and you want a little more
155−control over how your templates are laid out. Hakyll provides a few control
156−structures for this. The syntax for these structures was based on the syntax
157−used in pandoc templates, since Hakyll already has tight integration with
158−pandoc.
159−
160−### Conditionals
161−
162−In `templates/post.html` of the example site we generated using `hakyll-init`,
163−we see an example of a conditional:
164−
165−```html
166−<div class="info">
167− Posted on $date$
168− $if(author)$
169− by $author$
170− $endif$
171−</div>
172−```
173−
174−This example should be pretty straightforward. One important thing to notice is
175−that `$if(foo)$` **does not** check the truth value of `$foo$`: it merely checks
176−if such a key is present.
177−
178−Note that an if-else form is supported as well:
179−
180−```html
181−<div class="info">
182− Posted on $date$
183− $if(author)$
184− by $author$
185− $else$
186− by some unknown author
187− $endif$
188−</div>
189−```
190−
191−### Partials
192−
193−Partials allow you to [DRY] up your templates by putting repetitive actions into
194−separate template files. You can then include them using
195−`$partial("filename.html")$`.
196−
197−[DRY]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
198−
199−An example can be found in `templates/archive.html`:
200−
201−```html
202−Here you can find all my previous posts:
203−$partial("templates/post-list.html")$
204−```
205−
206−This partial is just another template and uses the same syntax. Note that in
207−order to use something like this, we also need to load the partial template in
208−our `site.hs`:
209−
210−```haskell
211−match "templates/post-list.html" $ compile templateCompiler
212−```
213−
214−Fortunately, we usually don't need to add this since we already have:
215−
216−```haskell
217−match "templates/*" $ compile templateCompiler
218−```
219−
220−### Producing a list of items: for
144+## Producing a list of items
221
145
222At this point, everything in the example site we generated should be clear to
146At this point, everything in the example site we generated should be clear to
223you, except for how we produce the list of posts in `archive.html` and
147you, except for how we produce the list of posts in `archive.html` and
224−`index.html`. Let's look at the `templates/post-list.html` template:
148+`index.html`.
225−
226−```html
227−<ul>
228− $for(posts)$
229− <li>
230− <a href="$url$">$title$</a> - $date$
231− </li>
232− $endfor$
233−</ul>
234−```
235
149
236−This uses the `$for(foo)$` construct. This construct allows you loop over a
150+However, this really isn't hard and just uses the things we saw before: loading
237−list, in this case, `$posts$`. Inside the body of this for loop, all fields
151+other items and applying templates.
238−refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
239−
240−You can also add a simple separator with the special `$sep$` field.
241−Everything between `$sep$` and `$endfor$` will be regarded as a separator
242−that will only be shown if there is more than one item in the list.
243−
244−```html
245−<ul>
246− $for(posts)$
247− $x$
248− $sep$,
249− $endfor$
250−</ul>
251−```
252
152
253−
153+We can reproduce a list of items in the archive using the following code:
254−Of course, $posts$ does not magically appear. We have to specify this in
255−`site.hs`. Let's look at how `archive.html` is generated:
256
154
257```haskell
155```haskell
156+compile $ do
258−posts <- recentFirst =<< loadAll "posts/*"
157+ posts <- recentFirst <$> loadAll "posts/*"
259−let archiveCtx =
158+ itemTpl <- loadBody "templates/post-item.html"
260− listField "posts" postCtx (return posts) `mappend`
159+ list <- applyTemplateList itemTpl postCtx posts
261− constField "title" "Archives" `mappend`
160+ makeItem list
262− defaultContext
263```
161```
264
162
265−We discussed `loadAll` earlier in this tutorial.
266−
267`recentFirst` sorts items by date. This relies on the convention that posts are
163`recentFirst` sorts items by date. This relies on the convention that posts are
268−always named `YYYY-MM-DD-title.extension` in Hakyll -- or that the date must be
164+always named `YYYY-MM-DD-title.extension` in Hakyll -- if you use some other
269−present in the metadata.
165+format, you'll have to write some other sorting method.
270−
271−```haskell
272−recentFirst :: [Item a] -> Compiler [Item a]
273−```
274−
275−After loading and sorting the items, we use `listField` to create the `$posts$`
276−key.
277
166
278```haskell
167```haskell
279−listField :: String -> Context a -> Compiler [Item a] -> Context b
168+recentFirst :: [Item a] -> [Item a]
280```
169```
281
170
282−The first parameter is simply the name of the key (`"posts"`). Secondly we have
171+After loading and sorting the items, we load a template for the posts.
283−a `Context` with which all items should be rendered -- for our example site, we
172+`applyTemplateList` applies this template to every post and concatenates the
284−already wrote such a `Context` for posts: `postCtx`. Lastly, we have a
173+result, which is a simple `String`. After that, we need `makeItem` to wrap the
285−`Compiler` which loads the items. We already loaded the items so we can just use
174+returned `String` to `Item String`.
286−`return posts`.
287−
288−The following snippet would produce the same result:
289−
290−```haskell
291−let archiveCtx =
292− listField "posts" postCtx (recentFirst =<< loadAll "posts/*") `mappend`
293− constField "title" "Archives" `mappend`
294− defaultContext
295−```
web/tutorials/05-snapshots-feeds.markdown+2−4
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7Basic feed configuration
6Basic feed configuration
@@ -52,7 +51,7 @@ create ["atom.xml"] $ do
52 let feedCtx = postCtx `mappend`
51 let feedCtx = postCtx `mappend`
53 constField "description" "This is the post description"
52 constField "description" "This is the post description"
54
53
55− posts <- fmap (take 10) . recentFirst =<< loadAll "posts/*"
54+ posts <- take 10 . recentFirst <$> loadAll "posts/*"
56 renderAtom myFeedConfiguration feedCtx posts
55 renderAtom myFeedConfiguration feedCtx posts
57```
56```
58
57
@@ -123,7 +122,6 @@ create ["atom.xml"] $ do
123 route idRoute
122 route idRoute
124 compile $ do
123 compile $ do
125 let feedCtx = postCtx `mappend` bodyField "description"
124 let feedCtx = postCtx `mappend` bodyField "description"
126− posts <- fmap (take 10) . recentFirst =<<
127− loadAllSnapshots "posts/*" "content"
125+ posts <- take 10 . recentFirst <$> loadAllSnapshots "posts/*" "content"
128 renderAtom myFeedConfiguration feedCtx posts
126 renderAtom myFeedConfiguration feedCtx posts
129```
127```
web/tutorials/06-versions.markdown−1
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7Basics
6Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+5−2
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7The hakyll package [contains a bewildering array](/reference/) of modules, and
6The hakyll package [contains a bewildering array](/reference/) of modules, and
@@ -63,7 +62,7 @@ These modules contain some "pre-packaged" solutions for common situations. They
63can be used as-is, or their source can be used as inspiration for your own
62can be used as-is, or their source can be used as inspiration for your own
64tools.
63tools.
65
64
66−* [Hakyll.Web.Template.List](/reference/Hakyll-Web-Template-List.html)
65+* [Hakyll.Web.Page.List](/reference/Hakyll-Web-Page-List.html)
67
66
68 Combine several pages into a list, such as a list of posts, images in a
67 Combine several pages into a list, such as a list of posts, images in a
69 gallery, etc.
68 gallery, etc.
@@ -82,6 +81,10 @@ tools.
82
81
83 Use any unix utility as a compiler.
82 Use any unix utility as a compiler.
84
83
84+* [Hakyll.Core.Util.File](/reference/Hakyll-Core-Util-File.html)
85+
86+ Utility functions for working with the file system.
87+
85* [Hakyll.Core.Util.String](/reference/Hakyll-Core-Util-String.html)
88* [Hakyll.Core.Util.String](/reference/Hakyll-Core-Util-String.html)
86
89
87 A few utility functions for working with `String`s (trim spaces, find and
90 A few utility functions for working with `String`s (trim spaces, find and
web/tutorials/external-add-tags-to-your-hakyll-blog.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Add tags to your Hakyll blog
3−author: Javran Cheng
4−url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
5−type: external
6−---
web/tutorials/external-blogger-port.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Port an existing Blogger blog to Hakyll
3−author: Daniel Campoverde
4−url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
5−type: external
6−---
web/tutorials/external-cassius-template.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Using Cassius templating with Hakyll
3−author: Daniel Campoverde
4−url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
5−type: external
6−---
web/tutorials/external-clean-urls-with-hakyll.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Clean URLs with Hakyll
3−author: Rohan Jain
4−url: http://www.rohanjain.in/hakyll-clean-urls/
5−type: external
6−---
web/tutorials/external-deploying-with-widely.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Deploying with Widely
3−author: Kyle Marek-Spartz
4−url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
5−type: external
6−---
web/tutorials/external-external-code-inclusion.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: 'External code inclusion with Hakyll'
3−author: 'Tobias Pleyer'
4−url: 'http://blog.tpleyer.de/posts/2019-04-21-external-code-inclusion-with-hakyll.html'
5−type: external
6−---
web/tutorials/external-functionfield.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Hakyll's functionField
3−author: Beerend Lauwers
4−url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
5−type: external
6−---
web/tutorials/external-github-pages-circle-ci.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Create a GitHub page with Hakyll and CircleCI
3−author: Juan Pedro Villa
4−url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
5−type: external
6−---
web/tutorials/external-inlining-and-compressing-css.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Inlining and compressing CSS
3−author: Kyle Marek-Spartz
4−url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
5−type: external
6−---
web/tutorials/faq.markdown+1−50
@@ -1,45 +1,8 @@
1---
1---
2title: FAQ
2title: FAQ
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4−type: article
5---
4---
6
5
7−## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
8−
9−If you get any of the errors:
10−
11− commitBuffer: invalid argument (invalid character)
12−
13−or:
14−
15− hGetContents: invalid argument (Invalid or incomplete multibyte or wide
16− character)
17−
18−It means that your Hakyll executable couldn't write to (in the former case) or
19−read (in the latter) from an UTF-8 encoded file.
20−
21−On most linux distros, you can solve this by setting your `LANG` to use UTF-8,
22−using something like:
23−
24− LANG=nl_BE.UTF-8 ./site build
25−
26−You should also add this to your `.profile`, or whatever config file you use.
27−
28−On Windows, running `chcp 65001` before running your Hakyll executable has been
29−reported to work.
30−
31−Alternatively, you can specify this in your `site.hs`:
32−
33−```haskell
34−import qualified GHC.IO.Encoding as E
35−
36−main :: IO ()
37−main = do
38− E.setLocaleEncoding E.utf8
39− hakyll $ do
40− ...
41−```
42−
43## "File name does not match module name" on Mac OS
6## "File name does not match module name" on Mac OS
44
7
45 Hakyll.hs:1:1:
8 Hakyll.hs:1:1:
@@ -68,25 +31,13 @@ recent version of Pandoc (1.9 and onwards). Note that you also need to include
68some CSS in order for this to work! This site, for example, uses the [default
31some CSS in order for this to work! This site, for example, uses the [default
69Pandoc syntax CSS file][syntax-css].
32Pandoc syntax CSS file][syntax-css].
70
33
71−To highlight a code block, you need to use Pandoc's fenced code block syntax to
72−set the block's language. For example, here's how you highlight Haskell code:
73−
74− ``` haskell
75− fac n = foldr (*) 1 [1..n]
76− ```
77−
78−For details, see Pandoc's user guide on [fenced code
79−blocks][pandoc-code-blocks] and [inline code][pandoc-inline-code].
80−
81[syntax-css]: https://github.com/jaspervdj/hakyll/blob/master/web/css/syntax.css
34[syntax-css]: https://github.com/jaspervdj/hakyll/blob/master/web/css/syntax.css
82−[pandoc-code-blocks]: http://johnmacfarlane.net/pandoc/README.html#fenced-code-blocks
83−[pandoc-inline-code]: http://johnmacfarlane.net/pandoc/README.html#verbatim
84
35
85## When should I rebuild and when should I build?
36## When should I rebuild and when should I build?
86
37
87If you execute a `./site build`, Hakyll will build your site incrementally.
38If you execute a `./site build`, Hakyll will build your site incrementally.
88However, we can not detect if you edited `site.hs`. In this case, you first want
39However, we can not detect if you edited `site.hs`. In this case, you first want
89−to compile `site.hs` again, and then do a `./site rebuild`.
40+to compile it again `site.hs` again, and then do a `./site rebuild`.
90
41
91After rebuilding your site, all files will look as "modified" to the filesystem.
42After rebuilding your site, all files will look as "modified" to the filesystem.
92This means that when you upload your site, it will usually transfer all files --
43This means that when you upload your site, it will usually transfer all files --
web/tutorials/github-pages-tutorial.mddeleted−181
@@ -1,181 +0,0 @@
1−---
2−title: Using Hakyll with GitHub Pages
3−author: Erik Stevenson
4−type: article
5−---
6−
7−## Introduction
8−
9−[GitHub Pages](http://pages.github.com) has become a popular static website hosting solution due to its simplicity. Simply push a couple files to a repository and it's off to the races.
10−
11−Working with Hakyll on a GitHub Pages-hosted website is complicated slightly due to Hakyll outputting files to a ```_site``` subdirectory, our desire to have the source code as well as the compiled site stored in a single repository, and our desire to automate it.
12−
13−This guide will walkthrough the creation and setup of a GitHub site that has two independent branches.
14−
15−1. ```master``` - This is where your site lives. It's what you see when you go to ```https://<your repo>.github.io```. This branch *needs* to be called master.
16−2. ```develop``` - This is where your website's source is. That's all your Haskell code, your posts and templates, etc, and it's where you do work from. This name was chosen arbitrarily and can be freely substituted for any name of your choosing.
17−
18−When you're finished, you will be able to, with one command, refresh your website's contents and send any changes to your GitHub Page.
19−
20−In the interest of keeping this guide as simple as possible, I'll be making a few assumptions.
21−
22−1. Haskell is being used with [Stack](http://docs.haskellstack.org/en/stable/README/).
23−2. Creating a user/organization site (vice a project site).
24−3. You haven't changed Hakyll's default output directory of '_site/'.
25−
26−These instructions should be easy to adapt for any situation though.
27−
28−## GitHub Setup
29−
30−1. If required, create a new repository for your blog.
31−2. If required, create a ```master``` branch.
32−2. If applicable/desired, create/add to your repository any files that your site needs that will not be produced by your Hakyll project. For example, ```CNAME``` as outlined [here](https://help.github.com/articles/setting-up-your-pages-site-repository/).
33−3. Create a ```.gitignore``` file with at a minimum, the following entries:
34−
35−```
36−_cache/
37−_site/
38−.stack-work/
39−```
40−
41−## Local setup
42−
43−1. If required, create a new Hakyll project. If you're a stack user, there is a Hakyll template available that makes this step easy.
44−
45−```stack new myblog hakyll-template```
46−
47−2. Create a ```.gitignore``` file in your blog's directory with at a minimum, the same directories listed as in the GitHub repository.
48−3. Use the following git commands to setup your local repository.
49−
50−```
51−git init
52−# create new branch called develop and switch to it.
53−git checkout -b develop
54−# track all the source files for our blog.
55−git add .
56−# make our first commit
57−git commit -m "initial commit."
58−# and add the GitHub repository as a remote.
59−git remote add origin <URL to your GitHub pages repository>
60−```
61−
62−## Deployment
63−
64−So everything's all setup and we're ready to deploy.
65−
66−> **Note:** Performing the following commands from your ```develop``` branch is recommended since you will end up back in that branch at the end.
67−
68−Temporarily save any uncommitted changes that may exist in the current branch.
69−
70−```
71−git stash
72−```
73−
74−Ensure we are in the correct branch.
75−
76−```
77−git checkout develop
78−```
79−
80−Get a clean build of our site.
81−
82−```
83−stack exec myblog clean
84−stack exec myblog build
85−```
86−
87−Update the local list of remote branches to ensure we're able to checkout the branch we want in the next step.
88−
89−```
90−git fetch --all
91−```
92−
93−Switch to the `master` branch.
94−
95−> **Note:** Checking out this branch does not overwrite the files that Hakyll just produced because we have '_site' listed in both .gitignore files.
96−
97−```
98−git checkout -b master --track origin/master
99−```
100−
101−Next, copy the freshly made contents of '_site' over the old ones.
102−
103−> **Note:** Deleting a file from your site's source will not remove it from your `master` repository if it has already been published. An alternative to `cp` is discussed at the end of this guide.
104−
105−```
106−cp -a _site/. .
107−```
108−
109−Commit our changes.
110−
111−```
112−git add -A
113−git commit -m "Publish."
114−```
115−
116−And send them to GitHub.
117−
118−```
119−git push origin master:master
120−```
121−
122−Final clean up and return to the original state.
123−
124−```
125−git checkout develop
126−git branch -D master
127−git stash pop
128−```
129−
130−## Putting it all together
131−
132−Below is a complete listing of all the commands used to automate deployment to Github Pages. A `deployCommand` can be set as part of Hakyll's configuration options. More information and an example is provided [here](https://jaspervdj.be/hakyll/reference/Hakyll-Core-Configuration.html).
133−
134−```
135−# Temporarily store uncommited changes
136−git stash
137−
138−# Verify correct branch
139−git checkout develop
140−
141−# Build new files
142−stack exec myblog clean
143−stack exec myblog build
144−
145−# Get previous files
146−git fetch --all
147−git checkout -b master --track origin/master
148−
149−# Overwrite existing files with new files
150−cp -a _site/. .
151−
152−# Commit
153−git add -A
154−git commit -m "Publish."
155−
156−# Push
157−git push origin master:master
158−
159−# Restoration
160−git checkout develop
161−git branch -D master
162−git stash pop
163−```
164−
165−*And that's it.*
166−
167−## Removing old files with `rsync`
168−
169−Earlier it was mentioned a flaw is that deleted files will persist in the published site until deleted manually. This is easily overcome by using `rsync` instead of `cp`.
170−
171−```
172−rsync -a --filter='P _site/' \
173− --filter='P _cache/' \
174− --filter='P .git/' \
175− --filter='P .gitignore' \
176− --filter='P .stack-work' \
177− --delete-excluded \
178− _site/ .
179−```
180−
181−The only drawback this approach has is the requirement that *every* file in your site "go through" Hakyll. Fortunately, in many cases this is not an issue.
web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1−2
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7Introduction
6Introduction
@@ -38,7 +37,7 @@ The `Page` type in Hakyll 3.X has been removed and replaced by an `Item` type.
38`pageCompiler` no longer exists -- where you previously used this, you probably
37`pageCompiler` no longer exists -- where you previously used this, you probably
39want to use `pandocCompiler` instead.
38want to use `pandocCompiler` instead.
40
39
41−`Page`s were manipulated using `setField`/`getField` functions in Hakyll 3.X.
40+`Page`s where manipulated using `setField`/`getField` functions in Hakyll 3.X.
42In Hakyll 4, all metadata is completely immutable, so these functions have been
41In Hakyll 4, all metadata is completely immutable, so these functions have been
43removed. In order to format and add fields, use a `Context` -- see the next
42removed. In order to format and add fields, use a `Context` -- see the next
44section.
43section.
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmldeleted−5
@@ -1,5 +0,0 @@
1−---
2−title: 'Pt. 1: Setup & Initial Customization'
3−url: 'https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html'
4−type: robertwpearce
5−---
web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.htmldeleted−5
@@ -1,5 +0,0 @@
1−---
2−title: 'Pt. 2: Generating a Sitemap XML File'
3−url: 'https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html'
4−type: robertwpearce
5−---
web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.htmldeleted−5
@@ -1,5 +0,0 @@
1−---
2−title: 'Pt. 3: Generating RSS and Atom XML Feeds'
3−url: 'https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html'
4−type: robertwpearce
5−---
web/tutorials/robertwpearce-04-copying-static-files-for-your-build.htmldeleted−5
@@ -1,5 +0,0 @@
1−---
2−title: 'Pt. 4: Copying Static Files For Your Build'
3−url: 'https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html'
4−type: robertwpearce
5−---
web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.htmldeleted−5
@@ -1,5 +0,0 @@
1−---
2−title: 'Pt. 5 – Generating Custom Post Filenames From a Title Slug'
3−url: 'https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html'
4−type: robertwpearce
5−---
web/tutorials/using-clay-with-hakyll.markdown+2−16
@@ -1,7 +1,6 @@
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
5---
4---
6
5
7[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
6[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
@@ -14,25 +13,15 @@ the CSS. This would be an example of such a file:
14``` haskell
13``` haskell
15{-# LANGUAGE OverloadedStrings #-}
14{-# LANGUAGE OverloadedStrings #-}
16import Clay
15import Clay
16+import qualified Data.Text.Lazy.IO as T
17
17
18test :: Css
18test :: Css
19test = ...
19test = ...
20
20
21main :: IO ()
21main :: IO ()
22−main = putCss test
22+main = T.putStr $ render test
23```
23```
24
24
25−If you prefer the compact version, you're going to have an extra import:
26−
27−
28−``` haskell
29−import qualified Data.Text.Lazy.IO as T
30−
31−main :: IO ()
32−main = T.putStr $ renderWith compact test
33−```
34−
35−
36Let's assume such a file is called `css/foo.hs`. In our compiled site, we want
25Let's assume such a file is called `css/foo.hs`. In our compiled site, we want
37to map this to `css/foo.css`. Hence, the route is a simple `setExtension`. For
26to map this to `css/foo.css`. Hence, the route is a simple `setExtension`. For
38compilation, we simply pass the Clay file through `runghc` with no options.
27compilation, we simply pass the Clay file through `runghc` with no options.
@@ -41,9 +30,6 @@ compilation, we simply pass the Clay file through `runghc` with no options.
41match "css/*.hs" $ do
30match "css/*.hs" $ do
42 route $ setExtension "css"
31 route $ setExtension "css"
43 compile $ getResourceString >>= withItemBody (unixFilter "runghc" [])
32 compile $ getResourceString >>= withItemBody (unixFilter "runghc" [])
44−
45−-- cabal sandbox users can use (unixFilter "cabal" ["exec", "runghc"])
46−-- given that you've added cabal to your PATH
47```
33```
48
34
49The major advantage of using this method (as opposed to importing the Clay files
35The major advantage of using this method (as opposed to importing the Clay files
web/tutorials/using-teasers-in-hakyll.markdowndeleted−109
@@ -1,109 +0,0 @@
1−---
2−title: Using teasers in Hakyll
3−author: Ivan Veselov
4−type: article
5−---
6−
7−## Introduction
8−
9−Sometimes it is convenient to have an excerpt of a post displayed on
10−the index page along with a "Read more..." link to encourage your
11−readers to read the rest of the post.
12−
13−You can do this in Hakyll by using "teasers" functionality.
14−
15−## Marking teasers in posts
16−
17−First, you have to put a tag in your post which separates the teaser
18−from the rest of the article. We use `<!--more-->` for this to mimic
19−the [WordPress convention](http://codex.wordpress.org/Customizing_the_Read_More):
20−
21−``` markdown
22−---
23−title: My teasering post
24−---
25−In this post I'll try to explain how to use teasers.
26−
27−<!--more-->
28−
29−And here I am proceeding with the explanation.
30−
31−```
32−
33−This is an HTML comment, so it doesn't have any visual impact on the
34−post, it is there solely for semantic purposes.
35−
36−## Referring to teasers in templates
37−
38−Now, we want to refer to the teaser in the template. We can do this
39−pretty intuitively by using `$teaser$` key:
40−
41−``` html
42−<li class="post_item">
43− $date$ - $title$
44− <p>Teaser: $teaser$</p>
45− <a href="$url$">Read more</a>
46−</li>
47−```
48−
49−## Gluing together
50−
51−The only thing left is to glue those things together. An important
52−question here is on what stage of the compilation we want to extract a
53−teaser. Usually, we want to use pandoc output, i.e. raw HTML without
54−any templates applied (since we do not want some surrounding
55−JavaScript or common text from the templates to be included in the
56−teaser). We can specify this by using snapshots: we save the snapshot
57−during compilation and load it to resolve `$teaser$` key later:
58−
59−``` haskell
60−match "posts/*" $ do
61− route $ setExtension ".html"
62− compile $
63− pandocCompiler
64− -- save immediately after pandoc, but before the templates are applied
65− >>= saveSnapshot "content"
66− >>= loadAndApplyTemplate "templates/post.html" defaultContext
67− ...
68−```
69−
70−You can read more about snapshots in
71−[Snapshots tutorial](/tutorials/05-snapshots-feeds.html).
72−
73−Then we use this snapshot while generating teasers using the
74−`teaserField` function:
75−
76−``` haskell
77− loadAndApplyTemplate
78− "template/postitem.html"
79− (teaserField "teaser" "content" <> defaultContext)
80− item
81−```
82−
83−Here, we've just added a new context which knows how to handle
84−`$teaser$` key to the default context (note that we passed the same
85−snapshot name `"content"` which we used while saving).
86−
87−## Optional teasers
88−
89−In case you don't add a `<!--more-->` comment, `$teaser$` will not be defined.
90−This means you can use something like:
91−
92−```html
93−$if(teaser)$
94− $teaser$
95−$else$
96− $body$
97−$endif$
98−```
99−
100−## Known issues
101−
102−Since we use an HTML comment `<!--more-->` to separate the teaser,
103−sometimes `pandoc` can "eat" the comment while converting (for example
104−this happens with literate Haskell source). In order to overcome this
105−problem you may try to use something like this as a separator:
106−
107−``` html
108−<div></div><!--more-->
109−```