on master help sign in
.circleci/config.ymlnew file+19
since splitsnapshots
unifiedsplit
@@ -0,0 +1,19 @@
1+version: 2
2+jobs:
3+ build:
4+ docker:
5+ - image: 'fpco/stack-build:latest'
6+ steps:
7+ - checkout
8+ - restore_cache:
9+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}'
10+ - run:
11+ # We set jobs to 1 here because that prevents Out-Of-Memory exceptions
12+ # while compiling dependencies.
13+ name: 'Install dependencies'
14+ command: '.circleci/tickle.sh stack build --test --copy-bins --jobs=1'
15+ - save_cache:
16+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
17+ paths:
18+ - '~/.stack-work'
19+ - '~/.stack'
.circleci/tickle.shnew file+24
@@ -0,0 +1,24 @@
1+#!/bin/bash
2+set -o nounset -o errexit -o pipefail
3+
4+function tickle() {
5+ while [ true ]; do
6+ echo "[$(date +%H:%M:%S)] Tickling..."
7+ sleep 60
8+ done
9+}
10+
11+echo "Forking tickle process..."
12+tickle &
13+TICKLE_PID=$!
14+
15+echo "Forking build process..."
16+eval $@ &
17+BUILD_PID=$!
18+
19+echo "Waiting for build thread ($BUILD_PID)..."
20+wait $BUILD_PID
21+
22+echo "Killing tickle thread ($TICKLE_PID)..."
23+kill $TICKLE_PID
24+echo "All done!"
.gitignore+3
@@ -23,3 +23,6 @@ web/_cache
23web/_site
23web/_site
24web/site
24web/site
25web/reference
25web/reference
26+
27+# This is generated from the CHANGELOG
28+web/releases.markdown
.travis.ymldeleted−20
@@ -1,20 +0,0 @@
1−language: haskell
2−ghc: '7.10'
3−branches:
4− only:
5− - master
6−cache:
7− directories:
8− - $HOME/.stack
9−before_install:
10− - echo "override before_install"
11−install:
12− - wget https://github.com/commercialhaskell/stack/releases/download/v1.0.0/stack-1.0.0-linux-x86_64.tar.gz -O stack.gz
13− - tar xvf stack.gz
14− - chmod +x stack-1.0.0-linux-x86_64/stack
15− - stack-1.0.0-linux-x86_64/stack setup
16− - stack-1.0.0-linux-x86_64/stack build
17−before_script:
18− - echo "override before_script"
19−script:
20− - stack-1.0.0-linux-x86_64/stack test
web/releases.markdown → CHANGELOG.mdrenamed+160
@@ -4,6 +4,166 @@ title: Releases
4
4
5# Releases
5# Releases
6
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+
7## Hakyll 4.9.2.0
167## Hakyll 4.9.2.0
8
168
9- Fix integer fields in YAML metadata (Fix by Nikolaos S. Papaspyrou)
169- Fix integer fields in YAML metadata (Fix by Nikolaos S. Papaspyrou)
CODE_OF_CONDUCT.mdnew file+45
@@ -0,0 +1,45 @@
1+# Contributor Covenant Code of Conduct
2+
3+## Our Pledge
4+
5+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6+
7+## Our Standards
8+
9+Examples of behavior that contributes to creating a positive environment include:
10+* Using welcoming and inclusive language
11+* Being respectful of differing viewpoints and experiences
12+* Gracefully accepting constructive criticism
13+* Focusing on what is best for the community
14+* Showing empathy towards other community members
15+
16+Examples of unacceptable behavior by participants include:
17+
18+* The use of sexualized language or imagery and unwelcome sexual attention or advances
19+* Trolling, insulting/derogatory comments, and personal or political attacks
20+* Public or private harassment
21+* Publishing others' private information, such as a physical or electronic address, without explicit permission
22+* Other conduct which could reasonably be considered inappropriate in a professional setting
23+
24+## Our Responsibilities
25+
26+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
27+
28+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
29+
30+## Scope
31+
32+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
33+
34+## Enforcement
35+
36+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by [contacting the project team at m@jaspervdj.be](mailto:m@jaspervdj.be). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
37+
38+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
39+
40+## Attribution
41+
42+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
43+
44+[homepage]: http://contributor-covenant.org
45+[version]: http://contributor-covenant.org/version/1/4/
LICENSE+1−1
@@ -1,4 +1,4 @@
1−Copyright (c) 2009, Jasper Van der Jeugt
1+Copyright (c) 2009 - 2017, Jasper Van der Jeugt
2 
2 
3All rights reserved.
3All rights reserved.
4 
4 
Makefile+6−6
@@ -1,9 +1,9 @@
1+PACKAGE="$(shell stack list-dependencies --separator='-' | grep hakyll)"
2+LOCAL_DOC_ROOT="$(shell stack path --local-doc-root)"
3+
1# Generate the docs and copy them to the website dir
4# Generate the docs and copy them to the website dir
2haddock:
5haddock:
3− cabal haddock --hyperlink-source
6+ stack build --haddock --no-haddock-deps
4− rm -rf web/reference/
7+ rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
5− cp -r dist/doc/html/hakyll/ web/reference/
6
8
7−# Run the tests
9+.PHONY: haddock
8−test:
9− runghc -isrc -itests tests/TestSuite.hs
README.markdown+1−1
@@ -1,6 +1,6 @@
1# hakyll
1# hakyll
2
2
3−[![Build Status](https://img.shields.io/travis/jaspervdj/hakyll.svg)](http://travis-ci.org/jaspervdj/hakyll)
3+[![Build Status](https://img.shields.io/circleci/project/github/jaspervdj/hakyll.svg)](https://circleci.com/gh/jaspervdj/hakyll)
4
4
5Hakyll is a static site generator library in Haskell. More information
5Hakyll is a static site generator library in Haskell. More information
6(including a tutorial) can be found on
6(including a tutorial) can be found on
data/example/css/default.css+122−37
@@ -1,56 +1,141 @@
1−body {
1+html {
2− color: black;
3− font-size: 16px;
2+ font-size: 62.5%;
4− margin: 0px auto 0px auto;
5− width: 600px;
6}
3}
7
4
8−div#header {
5+body {
9− border-bottom: 2px solid black;
6+ font-size: 1.6rem;
10− margin-bottom: 30px;
7+ color: #000;
11− padding: 12px 0px 12px 0px;
12}
8}
13
9
14−div#logo a {
10+header {
15− color: black;
11+ border-bottom: 0.2rem solid #000;
16− float: left;
17− font-size: 18px;
18− font-weight: bold;
19− text-decoration: none;
20}
12}
21
13
22−div#header #navigation {
14+nav {
23− text-align: right;
15+ text-align: right;
24}
16}
25
17
26−div#header #navigation a {
18+nav a {
27− color: black;
28− font-size: 18px;
19+ font-size: 1.8rem;
29− font-weight: bold;
20+ font-weight: bold;
30− margin-left: 12px;
21+ color: black;
31− text-decoration: none;
22+ text-decoration: none;
32− text-transform: uppercase;
23+ text-transform: uppercase;
33}
24}
34
25
35−div#footer {
26+footer {
27+ margin-top: 3rem;
28+ padding: 1.2rem 0;
36− border-top: solid 2px black;
29+ border-top: 0.2rem solid #000;
37− color: #555;
38− font-size: 12px;
30+ font-size: 1.2rem;
39− margin-top: 30px;
31+ color: #555;
40− padding: 12px 0px 12px 0px;
41− text-align: right;
42}
32}
43
33
44h1 {
34h1 {
45− font-size: 24px;
35+ font-size: 2.4rem;
46}
36}
47
37
48h2 {
38h2 {
39+ font-size: 2rem;
40+}
41+
42+article .header {
43+ font-size: 1.4rem;
44+ font-style: italic;
45+ color: #555;
46+}
47+
48+.logo a {
49+ font-weight: bold;
50+ color: #000;
51+ text-decoration: none;
52+}
53+
54+@media (max-width: 319px) {
55+ body {
56+ width: 90%;
57+ margin: 0;
58+ padding: 0 5%;
59+ }
60+ header {
61+ margin: 4.2rem 0;
62+ }
63+ nav {
64+ margin: 0 auto 3rem;
65+ text-align: center;
66+ }
67+ footer {
68+ text-align: center;
69+ }
70+ .logo {
71+ text-align: center;
72+ margin: 1rem auto 3rem;
73+ }
74+ .logo a {
49− font-size: 20px;
75+ font-size: 2.4rem;
76+ }
77+ nav a {
78+ display: block;
79+ line-height: 1.6;
80+ }
81+}
82+
83+@media (min-width: 320px) {
84+ body {
85+ width: 90%;
86+ margin: 0;
87+ padding: 0 5%;
88+ }
89+ header {
90+ margin: 4.2rem 0;
91+ }
92+ nav {
93+ margin: 0 auto 3rem;
94+ text-align: center;
95+ }
96+ footer {
97+ text-align: center;
98+ }
99+ .logo {
100+ text-align: center;
101+ margin: 1rem auto 3rem;
102+ }
103+ .logo a {
104+ font-size: 2.4rem;
105+ }
106+ nav a {
107+ display: inline;
108+ margin: 0 0.6rem;
109+ }
50}
110}
51
111
52−div.info {
112+@media (min-width: 640px) {
53− color: #555;
113+ body {
114+ width: 60rem;
115+ margin: 0 auto;
116+ padding: 0;
117+ }
118+ header {
119+ margin: 0 0 3rem;
120+ padding: 1.2rem 0;
121+ }
122+ nav {
123+ margin: 0;
124+ text-align: right;
125+ }
126+ nav a {
127+ margin: 0 0 0 1.2rem;
128+ display: inline;
129+ }
130+ footer {
131+ text-align: right;
132+ }
133+ .logo {
134+ margin: 0;
135+ text-align: left;
136+ }
137+ .logo a {
138+ float: left;
54− font-size: 14px;
139+ font-size: 1.8rem;
55− font-style: italic;
140+ }
56}
141}
data/example/templates/default.html+9−9
@@ -8,26 +8,26 @@
8 <link rel="stylesheet" href="/css/default.css" />
8 <link rel="stylesheet" href="/css/default.css" />
9 </head>
9 </head>
10 <body>
10 <body>
11− <div id="header">
11+ <header>
12− <div id="logo">
12+ <div class="logo">
13 <a href="/">My Hakyll Blog</a>
13 <a href="/">My Hakyll Blog</a>
14 </div>
14 </div>
15− <div id="navigation">
15+ <nav>
16 <a href="/">Home</a>
16 <a href="/">Home</a>
17 <a href="/about.html">About</a>
17 <a href="/about.html">About</a>
18 <a href="/contact.html">Contact</a>
18 <a href="/contact.html">Contact</a>
19 <a href="/archive.html">Archive</a>
19 <a href="/archive.html">Archive</a>
20− </div>
20+ </nav>
21− </div>
21+ </header>
22
22
23− <div id="content">
23+ <main role="main">
24 <h1>$title$</h1>
24 <h1>$title$</h1>
25 $body$
25 $body$
26− </div>
26+ </main>
27
27
28− <div id="footer">
28+ <footer>
29 Site proudly generated by
29 Site proudly generated by
30 <a href="http://jaspervdj.be/hakyll">Hakyll</a>
30 <a href="http://jaspervdj.be/hakyll">Hakyll</a>
31− </div>
31+ </footer>
32 </body>
32 </body>
33</html>
33</html>
data/example/templates/post.html+11−8
@@ -1,8 +1,11 @@
1−<div class="info">
1+<article>
2+ <section class="header">
2− Posted on $date$
3+ Posted on $date$
3− $if(author)$
4+ $if(author)$
4− by $author$
5+ by $author$
5− $endif$
6+ $endif$
6−</div>
7+ </section>
7−
8+ <section>
8−$body$
9+ $body$
10+ </section>
11+</article>
hakyll.cabal+131−126
@@ -1,5 +1,5 @@
1Name: hakyll
1Name: hakyll
2−Version: 4.9.2.0
2+Version: 4.13.0.0
3
3
4Synopsis: A static website compiler library
4Synopsis: A static website compiler library
5Description:
5Description:
@@ -37,7 +37,7 @@ License: BSD3
37License-File: LICENSE
37License-File: LICENSE
38Category: Web
38Category: Web
39
39
40−Cabal-Version: >= 1.8
40+Cabal-Version: >= 1.10
41Build-Type: Simple
41Build-Type: Simple
42Data-Dir: data
42Data-Dir: data
43
43
@@ -56,12 +56,9 @@ Data-files:
56 example/index.html
56 example/index.html
57 example/about.rst
57 example/about.rst
58 example/contact.markdown
58 example/contact.markdown
59− templates/atom-item.xml
60− templates/atom.xml
61− templates/rss-item.xml
62− templates/rss.xml
63
59
64Extra-source-files:
60Extra-source-files:
61+ CHANGELOG.md
65 tests/data/example.md
62 tests/data/example.md
66 tests/data/example.md.metadata
63 tests/data/example.md.metadata
67 tests/data/images/favicon.ico
64 tests/data/images/favicon.ico
@@ -71,11 +68,16 @@ Extra-source-files:
71 tests/data/partial.html
68 tests/data/partial.html
72 tests/data/partial.html.out
69 tests/data/partial.html.out
73 tests/data/posts/2010-08-26-birthday.md
70 tests/data/posts/2010-08-26-birthday.md
71+ tests/data/posts/2018-09-26.md
74 tests/data/russian.md
72 tests/data/russian.md
75 tests/data/strip.html
73 tests/data/strip.html
76 tests/data/strip.html.out
74 tests/data/strip.html.out
77 tests/data/template.html
75 tests/data/template.html
78 tests/data/template.html.out
76 tests/data/template.html.out
77+ data/templates/atom-item.xml
78+ data/templates/atom.xml
79+ data/templates/rss-item.xml
80+ data/templates/rss.xml
79
81
80Source-Repository head
82Source-Repository head
81 Type: git
83 Type: git
@@ -93,24 +95,43 @@ Flag checkExternal
93 Description: Include external link checking
95 Description: Include external link checking
94 Default: True
96 Default: True
95
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+
96Library
108Library
97− Ghc-Options: -Wall
109+ Ghc-Options: -Wall
98− Hs-Source-Dirs: src
110+ Hs-Source-Dirs: lib
111+ Default-language: Haskell2010
99
112
100 Exposed-Modules:
113 Exposed-Modules:
101 Hakyll
114 Hakyll
102 Hakyll.Commands
115 Hakyll.Commands
103 Hakyll.Core.Compiler
116 Hakyll.Core.Compiler
117+ Hakyll.Core.Compiler.Internal
104 Hakyll.Core.Configuration
118 Hakyll.Core.Configuration
105 Hakyll.Core.Dependencies
119 Hakyll.Core.Dependencies
106 Hakyll.Core.File
120 Hakyll.Core.File
107 Hakyll.Core.Identifier
121 Hakyll.Core.Identifier
108 Hakyll.Core.Identifier.Pattern
122 Hakyll.Core.Identifier.Pattern
109 Hakyll.Core.Item
123 Hakyll.Core.Item
124+ Hakyll.Core.Logger
110 Hakyll.Core.Metadata
125 Hakyll.Core.Metadata
126+ Hakyll.Core.Provider
127+ Hakyll.Core.Provider.Metadata
111 Hakyll.Core.Routes
128 Hakyll.Core.Routes
112 Hakyll.Core.Rules
129 Hakyll.Core.Rules
130+ Hakyll.Core.Rules.Internal
131+ Hakyll.Core.Runtime
132+ Hakyll.Core.Store
113 Hakyll.Core.UnixFilter
133 Hakyll.Core.UnixFilter
134+ Hakyll.Core.Util.File
114 Hakyll.Core.Util.String
135 Hakyll.Core.Util.String
115 Hakyll.Core.Writable
136 Hakyll.Core.Writable
116 Hakyll.Main
137 Hakyll.Main
@@ -118,11 +139,9 @@ Library
118 Hakyll.Web.Feed
139 Hakyll.Web.Feed
119 Hakyll.Web.Html
140 Hakyll.Web.Html
120 Hakyll.Web.Html.RelativizeUrls
141 Hakyll.Web.Html.RelativizeUrls
121− Hakyll.Web.Pandoc
122− Hakyll.Web.Pandoc.Biblio
123− Hakyll.Web.Pandoc.FileType
124− Hakyll.Web.Tags
125 Hakyll.Web.Paginate
142 Hakyll.Web.Paginate
143+ Hakyll.Web.Redirect
144+ Hakyll.Web.Tags
126 Hakyll.Web.Template
145 Hakyll.Web.Template
127 Hakyll.Web.Template.Context
146 Hakyll.Web.Template.Context
128 Hakyll.Web.Template.Internal
147 Hakyll.Web.Template.Internal
@@ -134,64 +153,53 @@ Library
134 Data.List.Extended
153 Data.List.Extended
135 Data.Yaml.Extended
154 Data.Yaml.Extended
136 Hakyll.Check
155 Hakyll.Check
137− Hakyll.Core.Compiler.Internal
138 Hakyll.Core.Compiler.Require
156 Hakyll.Core.Compiler.Require
157+ Hakyll.Core.Identifier.Pattern.Internal
139 Hakyll.Core.Item.SomeItem
158 Hakyll.Core.Item.SomeItem
140− Hakyll.Core.Logger
141− Hakyll.Core.Provider
142 Hakyll.Core.Provider.Internal
159 Hakyll.Core.Provider.Internal
143− Hakyll.Core.Provider.Metadata
144 Hakyll.Core.Provider.MetadataCache
160 Hakyll.Core.Provider.MetadataCache
145− Hakyll.Core.Rules.Internal
146− Hakyll.Core.Runtime
147− Hakyll.Core.Store
148− Hakyll.Core.Util.File
149 Hakyll.Core.Util.Parser
161 Hakyll.Core.Util.Parser
150− Hakyll.Web.Pandoc.Binary
151 Paths_hakyll
162 Paths_hakyll
152
163
153 Build-Depends:
164 Build-Depends:
154− base >= 4.8 && < 5,
165+ base >= 4.8 && < 5,
155− binary >= 0.5 && < 0.9,
166+ binary >= 0.5 && < 0.10,
156− blaze-html >= 0.5 && < 0.9,
167+ blaze-html >= 0.5 && < 0.10,
157− blaze-markup >= 0.5.1 && < 0.8,
168+ blaze-markup >= 0.5.1 && < 0.9,
158− bytestring >= 0.9 && < 0.11,
169+ bytestring >= 0.9 && < 0.11,
159− containers >= 0.3 && < 0.6,
170+ containers >= 0.3 && < 0.7,
160− cryptohash >= 0.7 && < 0.12,
171+ cryptohash >= 0.7 && < 0.12,
161− data-default >= 0.4 && < 0.8,
172+ data-default >= 0.4 && < 0.8,
162− deepseq >= 1.3 && < 1.5,
173+ deepseq >= 1.3 && < 1.5,
163− directory >= 1.0 && < 1.3,
174+ directory >= 1.0 && < 1.4,
175+ file-embed >= 0.0.10.1 && < 0.0.12,
164− filepath >= 1.0 && < 1.5,
176+ filepath >= 1.0 && < 1.5,
165− lrucache >= 1.1.1 && < 1.3,
177+ lrucache >= 1.1.1 && < 1.3,
166− mtl >= 1 && < 2.3,
178+ mtl >= 1 && < 2.3,
167− network >= 2.6 && < 2.7,
168− network-uri >= 2.6 && < 2.7,
179+ network-uri >= 2.6 && < 2.7,
169− pandoc >= 1.14 && < 1.20,
180+ optparse-applicative >= 0.12 && < 0.15,
170− pandoc-citeproc >= 0.4 && < 0.11,
171− parsec >= 3.0 && < 3.2,
181+ parsec >= 3.0 && < 3.2,
172− process >= 1.0 && < 1.5,
182+ process >= 1.6 && < 1.7,
173− random >= 1.0 && < 1.2,
183+ random >= 1.0 && < 1.2,
174− regex-base >= 0.93 && < 0.94,
175− regex-tdfa >= 1.1 && < 1.3,
184+ regex-tdfa >= 1.1 && < 1.3,
176− resourcet >= 1.1 && < 1.2,
185+ resourcet >= 1.1 && < 1.3,
177− scientific >= 0.3.4 && < 0.4,
186+ scientific >= 0.3.4 && < 0.4,
178− tagsoup >= 0.13.1 && < 0.15,
187+ tagsoup >= 0.13.1 && < 0.15,
188+ template-haskell >= 2.14 && < 2.15,
179− text >= 0.11 && < 1.3,
189+ text >= 0.11 && < 1.3,
180− time >= 1.4 && < 1.7,
190+ time >= 1.8 && < 1.10,
181− time-locale-compat >= 0.1 && < 0.2,
191+ time-locale-compat >= 0.1 && < 0.2,
182− unordered-containers >= 0.2 && < 0.3,
192+ unordered-containers >= 0.2 && < 0.3,
183− vector >= 0.11 && < 0.12,
193+ vector >= 0.11 && < 0.13,
184− yaml >= 0.8.11 && < 0.9,
194+ yaml >= 0.8.11 && < 0.12
185− optparse-applicative >= 0.12 && < 0.14
186
195
187 If flag(previewServer)
196 If flag(previewServer)
188 Build-depends:
197 Build-depends:
189 wai >= 3.2 && < 3.3,
198 wai >= 3.2 && < 3.3,
190 warp >= 3.2 && < 3.3,
199 warp >= 3.2 && < 3.3,
191 wai-app-static >= 3.1 && < 3.2,
200 wai-app-static >= 3.1 && < 3.2,
192− http-types >= 0.9 && < 0.10,
201+ http-types >= 0.9 && < 0.13,
193− fsnotify >= 0.2 && < 0.3,
202+ fsnotify >= 0.2 && < 0.4
194− system-filepath >= 0.4.6 && <= 0.5
195 Cpp-options:
203 Cpp-options:
196 -DPREVIEW_SERVER
204 -DPREVIEW_SERVER
197 Other-modules:
205 Other-modules:
@@ -200,8 +208,7 @@ Library
200
208
201 If flag(watchServer)
209 If flag(watchServer)
202 Build-depends:
210 Build-depends:
203− fsnotify >= 0.2 && < 0.3,
211+ fsnotify >= 0.2 && < 0.4
204− system-filepath >= 0.4.6 && <= 0.5
205 Cpp-options:
212 Cpp-options:
206 -DWATCH_SERVER
213 -DWATCH_SERVER
207 Other-modules:
214 Other-modules:
@@ -209,16 +216,31 @@ Library
209
216
210 If flag(checkExternal)
217 If flag(checkExternal)
211 Build-depends:
218 Build-depends:
212− http-conduit >= 2.2 && < 2.3,
219+ http-conduit >= 2.2 && < 2.4,
213− http-types >= 0.7 && < 0.10
220+ http-types >= 0.7 && < 0.13
214 Cpp-options:
221 Cpp-options:
215 -DCHECK_EXTERNAL
222 -DCHECK_EXTERNAL
216
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+
217Test-suite hakyll-tests
237Test-suite hakyll-tests
218− Type: exitcode-stdio-1.0
238+ Type: exitcode-stdio-1.0
219− Hs-source-dirs: src tests
239+ Hs-source-dirs: tests
220− Main-is: TestSuite.hs
240+ Main-is: TestSuite.hs
221− Ghc-options: -Wall
241+ Ghc-options: -Wall -threaded
242+ Default-language: Haskell2010
243+
222 Other-modules:
244 Other-modules:
223 Hakyll.Core.Dependencies.Tests
245 Hakyll.Core.Dependencies.Tests
224 Hakyll.Core.Identifier.Tests
246 Hakyll.Core.Identifier.Tests
@@ -233,90 +255,73 @@ Test-suite hakyll-tests
233 Hakyll.Web.CompressCss.Tests
255 Hakyll.Web.CompressCss.Tests
234 Hakyll.Web.Html.RelativizeUrls.Tests
256 Hakyll.Web.Html.RelativizeUrls.Tests
235 Hakyll.Web.Html.Tests
257 Hakyll.Web.Html.Tests
236− Hakyll.Web.Pandoc.FileType.Tests
258+ Hakyll.Web.Tags.Tests
237 Hakyll.Web.Template.Context.Tests
259 Hakyll.Web.Template.Context.Tests
238 Hakyll.Web.Template.Tests
260 Hakyll.Web.Template.Tests
239 TestSuite.Util
261 TestSuite.Util
240
262
241 Build-Depends:
263 Build-Depends:
264+ hakyll,
242− QuickCheck >= 2.8 && < 2.10,
265+ QuickCheck >= 2.8 && < 2.14,
243− tasty >= 0.11 && < 0.12,
266+ tasty >= 0.11 && < 1.3,
244− tasty-hunit >= 0.9 && < 0.10,
267+ tasty-hunit >= 0.9 && < 0.11,
245− tasty-quickcheck >= 0.8 && < 0.9,
268+ tasty-quickcheck >= 0.8 && < 0.11,
246 -- Copy pasted from hakyll dependencies:
269 -- Copy pasted from hakyll dependencies:
247− base >= 4.8 && < 5,
270+ base >= 4.8 && < 5,
248− binary >= 0.5 && < 0.9,
249− blaze-html >= 0.5 && < 0.9,
250− blaze-markup >= 0.5.1 && < 0.8,
251− bytestring >= 0.9 && < 0.11,
271+ bytestring >= 0.9 && < 0.11,
252− containers >= 0.3 && < 0.6,
272+ containers >= 0.3 && < 0.7,
253− cryptohash >= 0.7 && < 0.12,
254− data-default >= 0.4 && < 0.8,
255− deepseq >= 1.3 && < 1.5,
256− directory >= 1.0 && < 1.3,
257− filepath >= 1.0 && < 1.5,
273+ filepath >= 1.0 && < 1.5,
258− lrucache >= 1.1.1 && < 1.3,
259− mtl >= 1 && < 2.3,
260− network >= 2.6 && < 2.7,
261− network-uri >= 2.6 && < 2.7,
262− pandoc >= 1.14 && < 1.20,
263− pandoc-citeproc >= 0.4 && < 0.11,
264− parsec >= 3.0 && < 3.2,
265− process >= 1.0 && < 1.5,
266− random >= 1.0 && < 1.2,
267− regex-base >= 0.93 && < 0.94,
268− regex-tdfa >= 1.1 && < 1.3,
269− resourcet >= 1.1 && < 1.2,
270− scientific >= 0.3.4 && < 0.4,
271− tagsoup >= 0.13.1 && < 0.15,
272− text >= 0.11 && < 1.3,
274+ text >= 0.11 && < 1.3,
273− time >= 1.4 && < 1.7,
274− time-locale-compat >= 0.1 && < 0.2,
275− unordered-containers >= 0.2 && < 0.3,
275+ unordered-containers >= 0.2 && < 0.3,
276− vector >= 0.11 && < 0.12,
277− yaml >= 0.8.11 && < 0.9,
276+ yaml >= 0.8.11 && < 0.12
278− optparse-applicative >= 0.12 && < 0.14
279
277
280 If flag(previewServer)
278 If flag(previewServer)
281− Build-depends:
282− wai >= 3.2 && < 3.3,
283− warp >= 3.2 && < 3.3,
284− wai-app-static >= 3.1 && < 3.2,
285− http-types >= 0.9 && < 0.10,
286− fsnotify >= 0.2 && < 0.3,
287− system-filepath >= 0.4.6 && <= 0.5
288 Cpp-options:
279 Cpp-options:
289 -DPREVIEW_SERVER
280 -DPREVIEW_SERVER
290− Other-modules:
291− Hakyll.Preview.Poll
292− Hakyll.Preview.Server
293
281
294 If flag(watchServer)
282 If flag(watchServer)
295− Build-depends:
296− fsnotify >= 0.2 && < 0.3,
297− system-filepath >= 0.4.6 && <= 0.5
298 Cpp-options:
283 Cpp-options:
299 -DWATCH_SERVER
284 -DWATCH_SERVER
300− Other-modules:
301− Hakyll.Preview.Poll
302
285
303 If flag(checkExternal)
286 If flag(checkExternal)
304− Build-depends:
305− http-conduit >= 2.2 && < 2.3,
306− http-types >= 0.7 && < 0.10
307 Cpp-options:
287 Cpp-options:
308 -DCHECK_EXTERNAL
288 -DCHECK_EXTERNAL
309
289
290+ If flag(usePandoc)
291+ Other-modules:
292+ Hakyll.Web.Pandoc.FileType.Tests
293+ Cpp-options:
294+ -DUSE_PANDOC
295+
310Executable hakyll-init
296Executable hakyll-init
297+ Main-is: Init.hs
311− Ghc-options: -Wall
298+ Ghc-options: -Wall -threaded
312− Hs-source-dirs: src
299+ Hs-source-dirs: src
313− Main-is: Hakyll/Init.hs
300+ Default-language: Haskell2010
301+
302+ Other-modules:
303+ Paths_hakyll
314
304
315 Build-depends:
305 Build-depends:
306+ hakyll,
316 base >= 4 && < 5,
307 base >= 4 && < 5,
317− directory >= 1.0 && < 1.3,
308+ directory >= 1.0 && < 1.4,
318 filepath >= 1.0 && < 1.5
309 filepath >= 1.0 && < 1.5
319
310
320− Other-modules:
311+Executable hakyll-website
321− Hakyll.Core.Util.File
312+ Main-is: site.hs
322− Paths_hakyll
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
src/Data/List/Extended.hs → lib/Data/List/Extended.hsrenamed

The content is the same.

src/Data/Yaml/Extended.hs → lib/Data/Yaml/Extended.hsrenamed

The content is the same.

src/Hakyll.hs → lib/Hakyll.hsrenamed+7−1
@@ -20,11 +20,14 @@ module Hakyll
20 , module Hakyll.Web.Feed
20 , module Hakyll.Web.Feed
21 , module Hakyll.Web.Html
21 , module Hakyll.Web.Html
22 , module Hakyll.Web.Html.RelativizeUrls
22 , module Hakyll.Web.Html.RelativizeUrls
23+ , module Hakyll.Web.Paginate
24+#ifdef USE_PANDOC
23 , module Hakyll.Web.Pandoc
25 , module Hakyll.Web.Pandoc
24 , module Hakyll.Web.Pandoc.Biblio
26 , module Hakyll.Web.Pandoc.Biblio
25 , module Hakyll.Web.Pandoc.FileType
27 , module Hakyll.Web.Pandoc.FileType
28+#endif
29+ , module Hakyll.Web.Redirect
26 , module Hakyll.Web.Tags
30 , module Hakyll.Web.Tags
27− , module Hakyll.Web.Paginate
28 , module Hakyll.Web.Template
31 , module Hakyll.Web.Template
29 , module Hakyll.Web.Template.Context
32 , module Hakyll.Web.Template.Context
30 , module Hakyll.Web.Template.List
33 , module Hakyll.Web.Template.List
@@ -51,9 +54,12 @@ import Hakyll.Web.Feed
51import Hakyll.Web.Html
54import Hakyll.Web.Html
52import Hakyll.Web.Html.RelativizeUrls
55import Hakyll.Web.Html.RelativizeUrls
53import Hakyll.Web.Paginate
56import Hakyll.Web.Paginate
57+#ifdef USE_PANDOC
54import Hakyll.Web.Pandoc
58import Hakyll.Web.Pandoc
55import Hakyll.Web.Pandoc.Biblio
59import Hakyll.Web.Pandoc.Biblio
56import Hakyll.Web.Pandoc.FileType
60import Hakyll.Web.Pandoc.FileType
61+#endif
62+import Hakyll.Web.Redirect
57import Hakyll.Web.Tags
63import Hakyll.Web.Tags
58import Hakyll.Web.Template
64import Hakyll.Web.Template
59import Hakyll.Web.Template.Context
65import Hakyll.Web.Template.Context
src/Hakyll/Check.hs → lib/Hakyll/Check.hsrenamed+26−16
@@ -8,29 +8,31 @@ module Hakyll.Check
8
8
9
9
10--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11−import Control.Monad (forM_, foldM)
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_)
12−import Control.Monad.Reader (ask, ReaderT, runReaderT)
16+import Control.Monad.Reader (ReaderT, ask, runReaderT)
17+import Control.Monad.State (StateT, get, modify, runStateT)
13import Control.Monad.Trans (liftIO)
18import Control.Monad.Trans (liftIO)
14import Control.Monad.Trans.Resource (runResourceT)
19import Control.Monad.Trans.Resource (runResourceT)
15−import Data.ByteString.Char8 (unpack)
16import Data.List (isPrefixOf)
20import Data.List (isPrefixOf)
17import qualified Data.Map.Lazy as Map
21import qualified Data.Map.Lazy as Map
22+#if MIN_VERSION_base(4,9,0)
23+import Data.Semigroup (Semigroup (..))
24+#endif
18import Network.URI (unEscapeString)
25import Network.URI (unEscapeString)
19import System.Directory (doesDirectoryExist,
26import System.Directory (doesDirectoryExist,
20 doesFileExist)
27 doesFileExist)
21import System.Exit (ExitCode (..))
28import System.Exit (ExitCode (..))
22import System.FilePath (takeDirectory, takeExtension,
29import System.FilePath (takeDirectory, takeExtension,
23 (</>))
30 (</>))
24−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar,
25− readMVar)
26import qualified Text.HTML.TagSoup as TS
31import qualified Text.HTML.TagSoup as TS
27
32
28
33
29--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
30#ifdef CHECK_EXTERNAL
35#ifdef CHECK_EXTERNAL
31−import Control.Exception (SomeAsyncException (..),
32− SomeException (..), try, throw)
33−import Control.Monad.State (get, modify, StateT, runStateT)
34import Data.List (intercalate)
36import Data.List (intercalate)
35import Data.Typeable (cast)
37import Data.Typeable (cast)
36import Data.Version (versionBranch)
38import Data.Version (versionBranch)
@@ -86,10 +88,20 @@ data CheckerWrite = CheckerWrite
86
88
87
89
88--------------------------------------------------------------------------------
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
89instance Monoid CheckerWrite where
100instance Monoid CheckerWrite where
90 mempty = CheckerWrite 0 0
101 mempty = CheckerWrite 0 0
91 mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
102 mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
92 CheckerWrite (f1 + f2) (o1 + o2)
103 CheckerWrite (f1 + f2) (o1 + o2)
104+#endif
93
105
94
106
95--------------------------------------------------------------------------------
107--------------------------------------------------------------------------------
@@ -186,7 +198,7 @@ skip :: URL -> Maybe String -> Checker ()
186skip url maybeReason = do
198skip url maybeReason = do
187 logger <- checkerLogger <$> ask
199 logger <- checkerLogger <$> ask
188 case maybeReason of
200 case maybeReason of
189− Nothing -> return ()
201+ Nothing -> return ()
190 Just reason -> Logger.debug logger reason
202 Just reason -> Logger.debug logger reason
191 putCheckResult url mempty {checkerOk = 1}
203 putCheckResult url mempty {checkerOk = 1}
192
204
@@ -241,19 +253,14 @@ checkExternalUrl url = do
241 Left (SomeException e) ->
253 Left (SomeException e) ->
242 case (cast e :: Maybe SomeAsyncException) of
254 case (cast e :: Maybe SomeAsyncException) of
243 Just ae -> throw ae
255 Just ae -> throw ae
244− _ -> faulty url (Just $ showException e)
256+ _ -> faulty url (Just $ showException e)
245 Right _ -> ok url
257 Right _ -> ok url
246 where
258 where
247 -- Convert exception to a concise form
259 -- Convert exception to a concise form
248 showException e = case cast e of
260 showException e = case cast e of
249 Just (Http.HttpExceptionRequest _ e') -> show e'
261 Just (Http.HttpExceptionRequest _ e') -> show e'
250− _ -> head $ words $ show e
262+ _ -> head $ words $ show e
251−#else
252−checkExternalUrl url = skip url Nothing
253−#endif
254
263
255−
256−--------------------------------------------------------------------------------
257requestExternalUrl :: URL -> Checker (Either SomeException Bool)
264requestExternalUrl :: URL -> Checker (Either SomeException Bool)
258requestExternalUrl url = liftIO $ try $ do
265requestExternalUrl url = liftIO $ try $ do
259 mgr <- Http.newManager Http.tlsManagerSettings
266 mgr <- Http.newManager Http.tlsManagerSettings
@@ -273,6 +280,9 @@ requestExternalUrl url = liftIO $ try $ do
273 -- Nice user agent info
280 -- Nice user agent info
274 ua = fromString $ "hakyll-check/" ++
281 ua = fromString $ "hakyll-check/" ++
275 (intercalate "." $ map show $ versionBranch Paths_hakyll.version)
282 (intercalate "." $ map show $ versionBranch Paths_hakyll.version)
283+#else
284+checkExternalUrl url = skip url Nothing
285+#endif
276
286
277
287
278--------------------------------------------------------------------------------
288--------------------------------------------------------------------------------
src/Hakyll/Commands.hs → lib/Hakyll/Commands.hsrenamed+6−2
@@ -2,7 +2,8 @@
2-- | Implementation of Hakyll commands: build, preview...
2-- | Implementation of Hakyll commands: build, preview...
3{-# LANGUAGE CPP #-}
3{-# LANGUAGE CPP #-}
4module Hakyll.Commands
4module Hakyll.Commands
5+ ( Check(..)
5− ( build
6+ , build
6 , check
7 , check
7 , clean
8 , clean
8 , preview
9 , preview
@@ -15,9 +16,11 @@ module Hakyll.Commands
15
16
16--------------------------------------------------------------------------------
17--------------------------------------------------------------------------------
17import Control.Concurrent
18import Control.Concurrent
18−import System.Exit (ExitCode, exitWith)
19+import System.Exit (ExitCode)
20+
19
21
20--------------------------------------------------------------------------------
22--------------------------------------------------------------------------------
23+import Hakyll.Check (Check(..))
21import qualified Hakyll.Check as Check
24import qualified Hakyll.Check as Check
22import Hakyll.Core.Configuration
25import Hakyll.Core.Configuration
23import Hakyll.Core.Logger (Logger)
26import Hakyll.Core.Logger (Logger)
@@ -27,6 +30,7 @@ import Hakyll.Core.Rules.Internal
27import Hakyll.Core.Runtime
30import Hakyll.Core.Runtime
28import Hakyll.Core.Util.File
31import Hakyll.Core.Util.File
29
32
33+
30--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
31#ifdef WATCH_SERVER
35#ifdef WATCH_SERVER
32import Hakyll.Preview.Poll (watchUpdates)
36import Hakyll.Preview.Poll (watchUpdates)
src/Hakyll/Core/Compiler.hs → lib/Hakyll/Core/Compiler.hsrenamed+44−2
@@ -24,13 +24,16 @@ module Hakyll.Core.Compiler
24 , cached
24 , cached
25 , unsafeCompiler
25 , unsafeCompiler
26 , debugCompiler
26 , debugCompiler
27+ , noResult
28+ , withErrorMessage
27 ) where
29 ) where
28
30
29
31
30--------------------------------------------------------------------------------
32--------------------------------------------------------------------------------
31−import Control.Monad (when)
33+import Control.Monad (unless, when, (>=>))
32import Data.Binary (Binary)
34import Data.Binary (Binary)
33import Data.ByteString.Lazy (ByteString)
35import Data.ByteString.Lazy (ByteString)
36+import qualified Data.List.NonEmpty as NonEmpty
34import Data.Typeable (Typeable)
37import Data.Typeable (Typeable)
35import System.Environment (getProgName)
38import System.Environment (getProgName)
36import System.FilePath (takeExtension)
39import System.FilePath (takeExtension)
@@ -62,6 +65,7 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
62
65
63
66
64--------------------------------------------------------------------------------
67--------------------------------------------------------------------------------
68+-- | Create an item from the underlying identifier and a given value.
65makeItem :: a -> Compiler (Item a)
69makeItem :: a -> Compiler (Item a)
66makeItem x = do
70makeItem x = do
67 identifier <- getUnderlying
71 identifier <- getUnderlying
@@ -141,6 +145,10 @@ saveSnapshot snapshot item = do
141
145
142
146
143--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
148+-- | Turn on caching for a compilation value to avoid recomputing it
149+-- on subsequent Hakyll runs.
150+-- The storage key consists of the underlying identifier of the compiled
151+-- ressource and the given name.
144cached :: (Binary a, Typeable a)
152cached :: (Binary a, Typeable a)
145 => String
153 => String
146 -> Compiler a
154 -> Compiler a
@@ -149,6 +157,10 @@ cached name compiler = do
149 id' <- compilerUnderlying <$> compilerAsk
157 id' <- compilerUnderlying <$> compilerAsk
150 store <- compilerStore <$> compilerAsk
158 store <- compilerStore <$> compilerAsk
151 provider <- compilerProvider <$> compilerAsk
159 provider <- compilerProvider <$> compilerAsk
160+
161+ -- Give a better error message when the resource is not there at all.
162+ unless (resourceExists provider id') $ fail $ itDoesntEvenExist id'
163+
152 let modified = resourceModified provider id'
164 let modified = resourceModified provider id'
153 if modified
165 if modified
154 then do
166 then do
@@ -166,14 +178,44 @@ cached name compiler = do
166 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
178 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
167 "Try running: " ++ progName ++ " clean"
179 "Try running: " ++ progName ++ " clean"
168
180
181+ itDoesntEvenExist id' =
182+ "Hakyll.Core.Compiler.cached: You are trying to (perhaps " ++
183+ "indirectly) use `cached` on a non-existing resource: there " ++
184+ "is no file backing " ++ show id'
185+
169
186
170--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
188+-- | Run an IO computation without dependencies in a Compiler
171unsafeCompiler :: IO a -> Compiler a
189unsafeCompiler :: IO a -> Compiler a
172unsafeCompiler = compilerUnsafeIO
190unsafeCompiler = compilerUnsafeIO
173
191
174
192
175--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
194+-- | Fail so that it is treated as non-defined in an @\$if()\$@ branching
195+-- "Hakyll.Web.Template" macro, and alternative
196+-- 'Hakyll.Web.Template.Context.Context's are tried
197+--
198+-- @since 4.13.0
199+noResult :: String -> Compiler a
200+noResult = compilerNoResult . return
201+
202+
203+--------------------------------------------------------------------------------
204+-- | Prepend an error line to the error, if there is one. This allows you to
205+-- add helpful context to error messages.
206+--
207+-- @since 4.13.0
208+withErrorMessage :: String -> Compiler a -> Compiler a
209+withErrorMessage x = do
210+ compilerTry >=> either (compilerResult . CompilerError . prepend) return
211+ where
212+ prepend (CompilationFailure es) = CompilationFailure (x `NonEmpty.cons` es)
213+ prepend (CompilationNoResult es) = CompilationNoResult (x : es)
214+
215+
216+--------------------------------------------------------------------------------
176−-- | Compiler for debugging purposes
217+-- | Compiler for debugging purposes.
218+-- Passes a message to the debug logger that is printed in verbose mode.
177debugCompiler :: String -> Compiler ()
219debugCompiler :: String -> Compiler ()
178debugCompiler msg = do
220debugCompiler msg = do
179 logger <- compilerLogger <$> compilerAsk
221 logger <- compilerLogger <$> compilerAsk
src/Hakyll/Core/Compiler/Internal.hs → lib/Hakyll/Core/Compiler/Internal.hsrenamed+140−41
@@ -1,5 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Internally used compiler module
2-- | Internally used compiler module
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -9,19 +11,26 @@ module Hakyll.Core.Compiler.Internal
9 Snapshot
11 Snapshot
10 , CompilerRead (..)
12 , CompilerRead (..)
11 , CompilerWrite (..)
13 , CompilerWrite (..)
14+ , CompilerErrors (..)
12 , CompilerResult (..)
15 , CompilerResult (..)
13 , Compiler (..)
16 , Compiler (..)
14 , runCompiler
17 , runCompiler
15
18
16 -- * Core operations
19 -- * Core operations
20+ , compilerResult
17 , compilerTell
21 , compilerTell
18 , compilerAsk
22 , compilerAsk
23+ , compilerUnsafeIO
24+
25+ -- * Error operations
19 , compilerThrow
26 , compilerThrow
27+ , compilerNoResult
20 , compilerCatch
28 , compilerCatch
21− , compilerResult
29+ , compilerTry
22− , compilerUnsafeIO
30+ , compilerErrorMessages
23
31
24 -- * Utilities
32 -- * Utilities
33+ , compilerDebugEntries
25 , compilerTellDependencies
34 , compilerTellDependencies
26 , compilerTellCacheHits
35 , compilerTellCacheHits
27 ) where
36 ) where
@@ -31,7 +40,12 @@ module Hakyll.Core.Compiler.Internal
31import Control.Applicative (Alternative (..))
40import Control.Applicative (Alternative (..))
32import Control.Exception (SomeException, handle)
41import Control.Exception (SomeException, handle)
33import Control.Monad (forM_)
42import Control.Monad (forM_)
34−import Control.Monad.Except (MonadError (..))
43+import Control.Monad.Except (MonadError (..))
44+import Data.List.NonEmpty (NonEmpty (..))
45+import qualified Data.List.NonEmpty as NonEmpty
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
35import Data.Set (Set)
49import Data.Set (Set)
36import qualified Data.Set as S
50import qualified Data.Set as S
37
51
@@ -41,7 +55,6 @@ import Hakyll.Core.Configuration
41import Hakyll.Core.Dependencies
55import Hakyll.Core.Dependencies
42import Hakyll.Core.Identifier
56import Hakyll.Core.Identifier
43import Hakyll.Core.Identifier.Pattern
57import Hakyll.Core.Identifier.Pattern
44−import Hakyll.Core.Logger (Logger)
45import qualified Hakyll.Core.Logger as Logger
58import qualified Hakyll.Core.Logger as Logger
46import Hakyll.Core.Metadata
59import Hakyll.Core.Metadata
47import Hakyll.Core.Provider
60import Hakyll.Core.Provider
@@ -71,7 +84,7 @@ data CompilerRead = CompilerRead
71 , -- | Compiler store
84 , -- | Compiler store
72 compilerStore :: Store
85 compilerStore :: Store
73 , -- | Logger
86 , -- | Logger
74− compilerLogger :: Logger
87+ compilerLogger :: Logger.Logger
75 }
88 }
76
89
77
90
@@ -83,18 +96,46 @@ data CompilerWrite = CompilerWrite
83
96
84
97
85--------------------------------------------------------------------------------
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
86instance Monoid CompilerWrite where
108instance Monoid CompilerWrite where
87 mempty = CompilerWrite [] 0
109 mempty = CompilerWrite [] 0
88 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
110 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
89 CompilerWrite (d1 ++ d2) (h1 + h2)
111 CompilerWrite (d1 ++ d2) (h1 + h2)
112+#endif
90
113
91
114
92--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
93−data CompilerResult a where
116+-- | Distinguishes reasons in a 'CompilerError'
117+data CompilerErrors a
118+ -- | One or more exceptions occured during compilation
119+ = CompilationFailure (NonEmpty a)
120+ -- | Absence of any result, most notably in template contexts. May still
121+ -- have error messages.
122+ | CompilationNoResult [a]
123+ deriving Functor
124+
125+
126+-- | Unwrap a `CompilerErrors`
127+compilerErrorMessages :: CompilerErrors a -> [a]
128+compilerErrorMessages (CompilationFailure x) = NonEmpty.toList x
129+compilerErrorMessages (CompilationNoResult x) = x
130+
131+
132+--------------------------------------------------------------------------------
133+-- | An intermediate result of a compilation step
134+data CompilerResult a
94− CompilerDone :: a -> CompilerWrite -> CompilerResult a
135+ = CompilerDone a CompilerWrite
95− CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
136+ | CompilerSnapshot Snapshot (Compiler a)
96− CompilerError :: [String] -> CompilerResult a
97− CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
98
139
99
140
100--------------------------------------------------------------------------------
141--------------------------------------------------------------------------------
@@ -112,14 +153,14 @@ instance Functor Compiler where
112 return $ case res of
153 return $ case res of
113 CompilerDone x w -> CompilerDone (f x) w
154 CompilerDone x w -> CompilerDone (f x) w
114 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
155 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
115− CompilerError e -> CompilerError e
116 CompilerRequire i c' -> CompilerRequire i (fmap f c')
156 CompilerRequire i c' -> CompilerRequire i (fmap f c')
157+ CompilerError e -> CompilerError e
117 {-# INLINE fmap #-}
158 {-# INLINE fmap #-}
118
159
119
160
120--------------------------------------------------------------------------------
161--------------------------------------------------------------------------------
121instance Monad Compiler where
162instance Monad Compiler where
122− return x = Compiler $ \_ -> return $ CompilerDone x mempty
163+ return x = compilerResult $ CompilerDone x mempty
123 {-# INLINE return #-}
164 {-# INLINE return #-}
124
165
125 Compiler c >>= f = Compiler $ \r -> do
166 Compiler c >>= f = Compiler $ \r -> do
@@ -132,14 +173,14 @@ instance Monad Compiler where
132 CompilerSnapshot s c' -> CompilerSnapshot s $ do
173 CompilerSnapshot s c' -> CompilerSnapshot s $ do
133 compilerTell w -- Save dependencies!
174 compilerTell w -- Save dependencies!
134 c'
175 c'
135− CompilerError e -> CompilerError e
136 CompilerRequire i c' -> CompilerRequire i $ do
176 CompilerRequire i c' -> CompilerRequire i $ do
137 compilerTell w -- Save dependencies!
177 compilerTell w -- Save dependencies!
138 c'
178 c'
179+ CompilerError e -> CompilerError e
139
180
140 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
181 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
141− CompilerError e -> return $ CompilerError e
142 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
182 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
143 {-# INLINE (>>=) #-}
184 {-# INLINE (>>=) #-}
144
185
145 fail = compilerThrow . return
186 fail = compilerThrow . return
@@ -156,87 +197,145 @@ instance Applicative Compiler where
156
197
157
198
158--------------------------------------------------------------------------------
199--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
159instance MonadMetadata Compiler where
201instance MonadMetadata Compiler where
160 getMetadata = compilerGetMetadata
202 getMetadata = compilerGetMetadata
161 getMatches = compilerGetMatches
203 getMatches = compilerGetMatches
162
204
163
205
164--------------------------------------------------------------------------------
206--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
165instance MonadError [String] Compiler where
209instance MonadError [String] Compiler where
166− throwError = compilerThrow
210+ throwError = compilerThrow
167− catchError = compilerCatch
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
168
212
169
213
170--------------------------------------------------------------------------------
214--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
171runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
216runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
172runCompiler compiler read' = handle handler $ unCompiler compiler read'
217runCompiler compiler read' = handle handler $ unCompiler compiler read'
173 where
218 where
174 handler :: SomeException -> IO (CompilerResult a)
219 handler :: SomeException -> IO (CompilerResult a)
175− handler e = return $ CompilerError [show e]
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
176
221
177
222
178--------------------------------------------------------------------------------
223--------------------------------------------------------------------------------
224+-- | Trying alternative compilers if the first fails, regardless whether through
225+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226+-- Aggregates error messages if all fail.
179instance Alternative Compiler where
227instance Alternative Compiler where
180− empty = compilerThrow []
228+ empty = compilerNoResult []
181− x <|> y = compilerCatch x $ \es -> do
229+ x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
182− logger <- compilerLogger <$> compilerAsk
230+ case (rx, ry) of
183− forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
231+ (CompilationFailure xs, CompilationFailure ys) ->
232+ compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys
233+ (CompilationFailure xs, CompilationNoResult ys) ->
234+ debug ys >> compilerThrow (NonEmpty.toList xs)
235+ (CompilationNoResult xs, CompilationFailure ys) ->
236+ debug xs >> compilerThrow (NonEmpty.toList ys)
237+ (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys
238+ ))
239+ where
184− "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
240+ debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
185− y
186 {-# INLINE (<|>) #-}
241 {-# INLINE (<|>) #-}
187
242
188
243
189--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
245+-- | Put the result back in a compiler
246+compilerResult :: CompilerResult a -> Compiler a
247+compilerResult x = Compiler $ \_ -> return x
248+{-# INLINE compilerResult #-}
249+
250+
251+--------------------------------------------------------------------------------
252+-- | Get the current environment
190compilerAsk :: Compiler CompilerRead
253compilerAsk :: Compiler CompilerRead
191compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
254compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
192{-# INLINE compilerAsk #-}
255{-# INLINE compilerAsk #-}
193
256
194
257
195--------------------------------------------------------------------------------
258--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
196compilerTell :: CompilerWrite -> Compiler ()
260compilerTell :: CompilerWrite -> Compiler ()
197−compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
261+compilerTell = compilerResult . CompilerDone ()
198{-# INLINE compilerTell #-}
262{-# INLINE compilerTell #-}
199
263
200
264
201--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
266+-- | Run an IO computation without dependencies in a Compiler
267+compilerUnsafeIO :: IO a -> Compiler a
268+compilerUnsafeIO io = Compiler $ \_ -> do
269+ x <- io
270+ return $ CompilerDone x mempty
271+{-# INLINE compilerUnsafeIO #-}
272+
273+
274+--------------------------------------------------------------------------------
275+-- | Throw errors in the 'Compiler'.
276+--
277+-- If no messages are given, this is considered a 'CompilationNoResult' error.
278+-- Otherwise, it is treated as a proper compilation failure.
202compilerThrow :: [String] -> Compiler a
279compilerThrow :: [String] -> Compiler a
203−compilerThrow es = Compiler $ \_ -> return $ CompilerError es
280+compilerThrow = compilerResult . CompilerError .
204−{-# INLINE compilerThrow #-}
281+ maybe (CompilationNoResult []) CompilationFailure .
282+ NonEmpty.nonEmpty
283+
284+-- | Put a 'CompilerError' with multiple messages as 'CompilationNoResult'
285+compilerNoResult :: [String] -> Compiler a
286+compilerNoResult = compilerResult . CompilerError . CompilationNoResult
205
287
206
288
207--------------------------------------------------------------------------------
289--------------------------------------------------------------------------------
208−compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
290+-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'
291+--
292+-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right
293+compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
294+compilerTry (Compiler x) = Compiler $ \r -> do
295+ res <- x r
296+ case res of
297+ CompilerDone res' w -> return (CompilerDone (Right res') w)
298+ CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))
299+ CompilerRequire i c -> return (CompilerRequire i (compilerTry c))
300+ CompilerError e -> return (CompilerDone (Left e) mempty)
301+{-# INLINE compilerTry #-}
302+
303+
304+--------------------------------------------------------------------------------
305+-- | Allows you to recover from 'CompilerError's.
306+-- Uses the same parameter order as 'catchError' so that it can be used infix.
307+--
308+-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
209compilerCatch (Compiler x) f = Compiler $ \r -> do
310compilerCatch (Compiler x) f = Compiler $ \r -> do
210 res <- x r
311 res <- x r
211 case res of
312 case res of
212 CompilerDone res' w -> return (CompilerDone res' w)
313 CompilerDone res' w -> return (CompilerDone res' w)
213 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
314 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
214− CompilerError e -> unCompiler (f e) r
215 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
315 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316+ CompilerError e -> unCompiler (f e) r
216{-# INLINE compilerCatch #-}
317{-# INLINE compilerCatch #-}
217
318
218
319
219--------------------------------------------------------------------------------
320--------------------------------------------------------------------------------
220−-- | Put the result back in a compiler
321+compilerDebugLog :: [String] -> Compiler ()
221−compilerResult :: CompilerResult a -> Compiler a
322+compilerDebugLog ms = do
222−compilerResult x = Compiler $ \_ -> return x
323+ logger <- compilerLogger <$> compilerAsk
223−{-# INLINE compilerResult #-}
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
224−
225
325
226--------------------------------------------------------------------------------
326--------------------------------------------------------------------------------
227−compilerUnsafeIO :: IO a -> Compiler a
327+-- | Pass a list of messages with a heading to the debug logger
228−compilerUnsafeIO io = Compiler $ \_ -> do
328+compilerDebugEntries :: String -> [String] -> Compiler ()
229− x <- io
329+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
230− return $ CompilerDone x mempty
330+ where
231−{-# INLINE compilerUnsafeIO #-}
331+ indent = unlines . map (" "++) . lines
232
332
233
333
234--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
235compilerTellDependencies :: [Dependency] -> Compiler ()
335compilerTellDependencies :: [Dependency] -> Compiler ()
236compilerTellDependencies ds = do
336compilerTellDependencies ds = do
237− logger <- compilerLogger <$> compilerAsk
337+ compilerDebugLog $ map (\d ->
238− forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
239− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
240 compilerTell mempty {compilerDependencies = ds}
339 compilerTell mempty {compilerDependencies = ds}
241{-# INLINE compilerTellDependencies #-}
340{-# INLINE compilerTellDependencies #-}
242
341
src/Hakyll/Core/Compiler/Require.hs → lib/Hakyll/Core/Compiler/Require.hsrenamed+2
@@ -91,6 +91,7 @@ loadBody id' = loadSnapshotBody id' final
91
91
92
92
93--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
94+-- | A shortcut for only requiring the body for a specific snapshot of an item
94loadSnapshotBody :: (Binary a, Typeable a)
95loadSnapshotBody :: (Binary a, Typeable a)
95 => Identifier -> Snapshot -> Compiler a
96 => Identifier -> Snapshot -> Compiler a
96loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
97loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -103,6 +104,7 @@ loadAll pattern = loadAllSnapshots pattern final
103
104
104
105
105--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
107+-- | Load a specific snapshot for each of dynamic list of items
106loadAllSnapshots :: (Binary a, Typeable a)
108loadAllSnapshots :: (Binary a, Typeable a)
107 => Pattern -> Snapshot -> Compiler [Item a]
109 => Pattern -> Snapshot -> Compiler [Item a]
108loadAllSnapshots pattern snapshot = do
110loadAllSnapshots pattern snapshot = do
src/Hakyll/Core/Configuration.hs → lib/Hakyll/Core/Configuration.hsrenamed

The content is the same.

src/Hakyll/Core/Dependencies.hs → lib/Hakyll/Core/Dependencies.hsrenamed

The content is the same.

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

The content is the same.

src/Hakyll/Core/Item/SomeItem.hs → lib/Hakyll/Core/Item/SomeItem.hsrenamed

The content is the same.

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

The content is the same.

src/Hakyll/Core/Provider.hs → lib/Hakyll/Core/Provider.hsrenamed

The content is the same.

src/Hakyll/Core/Provider/Internal.hs → lib/Hakyll/Core/Provider/Internal.hsrenamed

The content is the same.

src/Hakyll/Core/Provider/Metadata.hs → lib/Hakyll/Core/Provider/Metadata.hsrenamed+3−2
@@ -27,6 +27,7 @@ import Hakyll.Core.Identifier
27import Hakyll.Core.Metadata
27import Hakyll.Core.Metadata
28import Hakyll.Core.Provider.Internal
28import Hakyll.Core.Provider.Internal
29import System.IO as IO
29import System.IO as IO
30+import System.IO.Error (modifyIOError, ioeSetLocation)
30
31
31
32
32--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
@@ -51,7 +52,7 @@ loadMetadata p identifier = do
51--------------------------------------------------------------------------------
52--------------------------------------------------------------------------------
52loadMetadataHeader :: FilePath -> IO (Metadata, String)
53loadMetadataHeader :: FilePath -> IO (Metadata, String)
53loadMetadataHeader fp = do
54loadMetadataHeader fp = do
54− fileContent <- readFile fp
55+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp
55 case parsePage fileContent of
56 case parsePage fileContent of
56 Right x -> return x
57 Right x -> return x
57 Left err -> throwIO $ MetadataException fp err
58 Left err -> throwIO $ MetadataException fp err
@@ -60,7 +61,7 @@ loadMetadataHeader fp = do
60--------------------------------------------------------------------------------
61--------------------------------------------------------------------------------
61loadMetadataFile :: FilePath -> IO Metadata
62loadMetadataFile :: FilePath -> IO Metadata
62loadMetadataFile fp = do
63loadMetadataFile fp = do
63− fileContent <- B.readFile fp
64+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp
64 let errOrMeta = Yaml.decodeEither' fileContent
65 let errOrMeta = Yaml.decodeEither' fileContent
65 either (fail . show) return errOrMeta
66 either (fail . show) return errOrMeta
66
67
src/Hakyll/Core/Provider/MetadataCache.hs → lib/Hakyll/Core/Provider/MetadataCache.hsrenamed

The content is the same.

src/Hakyll/Core/Routes.hs → lib/Hakyll/Core/Routes.hsrenamed+17
@@ -25,6 +25,7 @@
25-- not appear in your site directory.
25-- not appear in your site directory.
26--
26--
27-- * If an item matches multiple routes, the first rule will be chosen.
27-- * If an item matches multiple routes, the first rule will be chosen.
28+{-# LANGUAGE CPP #-}
28{-# LANGUAGE Rank2Types #-}
29{-# LANGUAGE Rank2Types #-}
29module Hakyll.Core.Routes
30module Hakyll.Core.Routes
30 ( UsedMetadata
31 ( UsedMetadata
@@ -42,6 +43,9 @@ module Hakyll.Core.Routes
42
43
43
44
44--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
45import System.FilePath (replaceExtension)
49import System.FilePath (replaceExtension)
46
50
47
51
@@ -74,6 +78,18 @@ newtype Routes = Routes
74
78
75
79
76--------------------------------------------------------------------------------
80--------------------------------------------------------------------------------
81+#if MIN_VERSION_base(4,9,0)
82+instance Semigroup Routes where
83+ (<>) (Routes f) (Routes g) = Routes $ \p id' -> do
84+ (mfp, um) <- f p id'
85+ case mfp of
86+ Nothing -> g p id'
87+ Just _ -> return (mfp, um)
88+
89+instance Monoid Routes where
90+ mempty = Routes $ \_ _ -> return (Nothing, False)
91+ mappend = (<>)
92+#else
77instance Monoid Routes where
93instance Monoid Routes where
78 mempty = Routes $ \_ _ -> return (Nothing, False)
94 mempty = Routes $ \_ _ -> return (Nothing, False)
79 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
95 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
@@ -81,6 +97,7 @@ instance Monoid Routes where
81 case mfp of
97 case mfp of
82 Nothing -> g p id'
98 Nothing -> g p id'
83 Just _ -> return (mfp, um)
99 Just _ -> return (mfp, um)
100+#endif
84
101
85
102
86--------------------------------------------------------------------------------
103--------------------------------------------------------------------------------
src/Hakyll/Core/Rules.hs → lib/Hakyll/Core/Rules.hsrenamed

The content is the same.

src/Hakyll/Core/Rules/Internal.hs → lib/Hakyll/Core/Rules/Internal.hsrenamed+14
@@ -1,4 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE Rank2Types #-}
4{-# LANGUAGE Rank2Types #-}
4module Hakyll.Core.Rules.Internal
5module Hakyll.Core.Rules.Internal
@@ -16,6 +17,9 @@ import Control.Monad.Reader (ask)
16import Control.Monad.RWS (RWST, runRWST)
17import Control.Monad.RWS (RWST, runRWST)
17import Control.Monad.Trans (liftIO)
18import Control.Monad.Trans (liftIO)
18import qualified Data.Map as M
19import qualified Data.Map as M
20+#if MIN_VERSION_base(4,9,0)
21+import Data.Semigroup (Semigroup (..))
22+#endif
19import Data.Set (Set)
23import Data.Set (Set)
20
24
21
25
@@ -52,10 +56,20 @@ data RuleSet = RuleSet
52
56
53
57
54--------------------------------------------------------------------------------
58--------------------------------------------------------------------------------
59+#if MIN_VERSION_base(4,9,0)
60+instance Semigroup RuleSet where
61+ (<>) (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
62+ RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
63+
64+instance Monoid RuleSet where
65+ mempty = RuleSet mempty mempty mempty mempty
66+ mappend = (<>)
67+#else
55instance Monoid RuleSet where
68instance Monoid RuleSet where
56 mempty = RuleSet mempty mempty mempty mempty
69 mempty = RuleSet mempty mempty mempty mempty
57 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
70 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
58 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
71 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72+#endif
59
73
60
74
61--------------------------------------------------------------------------------
75--------------------------------------------------------------------------------
src/Hakyll/Core/Runtime.hs → lib/Hakyll/Core/Runtime.hsrenamed+3−3
@@ -199,9 +199,9 @@ chase trail id'
199 result <- liftIO $ runCompiler compiler read'
199 result <- liftIO $ runCompiler compiler read'
200 case result of
200 case result of
201 -- Rethrow error
201 -- Rethrow error
202− CompilerError [] -> throwError
202+ CompilerError e -> throwError $ case compilerErrorMessages e of
203− "Compiler failed but no info given, try running with -v?"
203+ [] -> "Compiler failed but no info given, try running with -v?"
204− CompilerError es -> throwError $ intercalate "; " es
204+ es -> intercalate "; " es
205
205
206 -- Signal that a snapshot was saved ->
206 -- Signal that a snapshot was saved ->
207 CompilerSnapshot snapshot c -> do
207 CompilerSnapshot snapshot c -> do
src/Hakyll/Core/Store.hs → lib/Hakyll/Core/Store.hsrenamed+26−23
@@ -16,7 +16,6 @@ module Hakyll.Core.Store
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−import Control.Exception (IOException, handle)
20import qualified Crypto.Hash.MD5 as MD5
19import qualified Crypto.Hash.MD5 as MD5
21import Data.Binary (Binary, decode, encodeFile)
20import Data.Binary (Binary, decode, encodeFile)
22import qualified Data.ByteString as B
21import qualified Data.ByteString as B
@@ -27,11 +26,13 @@ import Data.Maybe (isJust)
27import qualified Data.Text as T
26import qualified Data.Text as T
28import qualified Data.Text.Encoding as T
27import qualified Data.Text.Encoding as T
29import Data.Typeable (TypeRep, Typeable, cast, typeOf)
28import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29+import Numeric (showHex)
30import System.Directory (createDirectoryIfMissing)
30import System.Directory (createDirectoryIfMissing)
31import System.Directory (doesFileExist, removeFile)
31import System.Directory (doesFileExist, removeFile)
32import System.FilePath ((</>))
32import System.FilePath ((</>))
33import System.IO (IOMode (..), hClose, openFile)
33import System.IO (IOMode (..), hClose, openFile)
34−import Text.Printf (printf)
34+import System.IO.Error (catchIOError, ioeSetFileName,
35+ ioeSetLocation, modifyIOError)
35
36
36
37
37--------------------------------------------------------------------------------
38--------------------------------------------------------------------------------
@@ -84,6 +85,14 @@ new inMemory directory = do
84 where
85 where
85 csize = Just 500
86 csize = Just 500
86
87
88+--------------------------------------------------------------------------------
89+withStore :: Store -> String -> (String -> FilePath -> IO a) -> [String] -> IO a
90+withStore store loc run identifier = modifyIOError handle $ run key path
91+ where
92+ key = hash identifier
93+ path = storeDirectory store </> key
94+ handle e = e `ioeSetFileName` (path ++ " for " ++ intercalate "/" identifier)
95+ `ioeSetLocation` ("Store." ++ loc)
87
96
88--------------------------------------------------------------------------------
97--------------------------------------------------------------------------------
89-- | Auxiliary: add an item to the in-memory cache
98-- | Auxiliary: add an item to the in-memory cache
@@ -124,17 +133,16 @@ cacheDelete (Store _ (Just lru)) key = do
124--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
125-- | Store an item
134-- | Store an item
126set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
135set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
127−set store identifier value = do
136+set store identifier value = withStore store "set" (\key path -> do
128− encodeFile (storeDirectory store </> key) value
137+ encodeFile path value
129 cacheInsert store key value
138 cacheInsert store key value
130− where
131− key = hash identifier
139+ ) identifier
132
140
133
141
134--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
135-- | Load an item
143-- | Load an item
136get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
144get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
137−get store identifier = do
145+get store = withStore store "get" $ \key path -> do
138 -- First check the in-memory map
146 -- First check the in-memory map
139 ref <- cacheLookup store key
147 ref <- cacheLookup store key
140 case ref of
148 case ref of
@@ -146,17 +154,14 @@ get store identifier = do
146 then return NotFound
154 then return NotFound
147 -- Found in the filesystem
155 -- Found in the filesystem
148 else do
156 else do
149− v <- decodeClose
157+ v <- decodeClose path
150 cacheInsert store key v
158 cacheInsert store key v
151 return $ Found v
159 return $ Found v
152 -- Found in the in-memory map (or wrong type), just return
160 -- Found in the in-memory map (or wrong type), just return
153 s -> return s
161 s -> return s
154 where
162 where
155− key = hash identifier
156− path = storeDirectory store </> key
157−
158 -- 'decodeFile' from Data.Binary which closes the file ASAP
163 -- 'decodeFile' from Data.Binary which closes the file ASAP
159− decodeClose = do
164+ decodeClose path = do
160 h <- openFile path ReadMode
165 h <- openFile path ReadMode
161 lbs <- BL.hGetContents h
166 lbs <- BL.hGetContents h
162 BL.length lbs `seq` hClose h
167 BL.length lbs `seq` hClose h
@@ -166,32 +171,30 @@ get store identifier = do
166--------------------------------------------------------------------------------
171--------------------------------------------------------------------------------
167-- | Strict function
172-- | Strict function
168isMember :: Store -> [String] -> IO Bool
173isMember :: Store -> [String] -> IO Bool
169−isMember store identifier = do
174+isMember store = withStore store "isMember" $ \key path -> do
170 inCache <- cacheIsMember store key
175 inCache <- cacheIsMember store key
171 if inCache then return True else doesFileExist path
176 if inCache then return True else doesFileExist path
172− where
173− key = hash identifier
174− path = storeDirectory store </> key
175
177
176
178
177--------------------------------------------------------------------------------
179--------------------------------------------------------------------------------
178-- | Delete an item
180-- | Delete an item
179delete :: Store -> [String] -> IO ()
181delete :: Store -> [String] -> IO ()
180−delete store identifier = do
182+delete store = withStore store "delete" $ \key path -> do
181 cacheDelete store key
183 cacheDelete store key
182− deleteFile $ storeDirectory store </> key
184+ deleteFile path
183− where
184− key = hash identifier
185
185
186
186
187--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
188-- | Delete a file unless it doesn't exist...
188-- | Delete a file unless it doesn't exist...
189deleteFile :: FilePath -> IO ()
189deleteFile :: FilePath -> IO ()
190−deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
190+deleteFile = (`catchIOError` \_ -> return ()) . removeFile
191
191
192
192
193--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
194-- | Mostly meant for internal usage
194-- | Mostly meant for internal usage
195hash :: [String] -> String
195hash :: [String] -> String
196−hash = concatMap (printf "%02x") . B.unpack .
197− MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
196+hash = toHex . B.unpack . MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
197+ where
198+ toHex [] = ""
199+ toHex (x : xs) | x < 16 = '0' : showHex x (toHex xs)
200+ | otherwise = showHex x (toHex xs)
src/Hakyll/Core/UnixFilter.hs → lib/Hakyll/Core/UnixFilter.hsrenamed+2−1
@@ -92,7 +92,8 @@ unixFilterWith writer reader programName args input = do
92 ExitSuccess -> return output
92 ExitSuccess -> return output
93 ExitFailure e -> fail $
93 ExitFailure e -> fail $
94 "Hakyll.Core.UnixFilter.unixFilterWith: " ++
94 "Hakyll.Core.UnixFilter.unixFilterWith: " ++
95− unwords (programName : args) ++ " gave exit code " ++ show e
95+ unwords (programName : args) ++ " gave exit code " ++ show e ++
96+ ". (Error: " ++ err ++ ")"
96
97
97
98
98--------------------------------------------------------------------------------
99--------------------------------------------------------------------------------
src/Hakyll/Core/Util/File.hs → lib/Hakyll/Core/Util/File.hsrenamed

The content is the same.

src/Hakyll/Core/Util/Parser.hs → lib/Hakyll/Core/Util/Parser.hsrenamed−1
@@ -2,7 +2,6 @@
2-- | Parser utilities
2-- | Parser utilities
3module Hakyll.Core.Util.Parser
3module Hakyll.Core.Util.Parser
4 ( metadataKey
4 ( metadataKey
5− , reservedKeys
6 ) where
5 ) where
7
6
8
7
src/Hakyll/Core/Util/String.hs → lib/Hakyll/Core/Util/String.hsrenamed+1−1
@@ -27,7 +27,7 @@ trim = reverse . trim' . reverse . trim'
27--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
28-- | A simple (but inefficient) regex replace funcion
28-- | A simple (but inefficient) regex replace funcion
29replaceAll :: String -- ^ Pattern
29replaceAll :: String -- ^ Pattern
30− -> (String -> String) -- ^ Replacement (called on capture)
30+ -> (String -> String) -- ^ Replacement (called on match)
31 -> String -- ^ Source string
31 -> String -- ^ Source string
32 -> String -- ^ Result
32 -> String -- ^ Result
33replaceAll pattern f source = replaceAll' source
33replaceAll pattern f source = replaceAll' source
src/Hakyll/Core/Writable.hs → lib/Hakyll/Core/Writable.hsrenamed

The content is the same.

lib/Hakyll/Main.hsnew file+180
@@ -0,0 +1,180 @@
1+--------------------------------------------------------------------------------
2+-- | Module providing the main hakyll function and command-line argument parsing
3+{-# LANGUAGE CPP #-}
4+
5+module Hakyll.Main
6+ ( hakyll
7+ , hakyllWith
8+ , hakyllWithArgs
9+ , hakyllWithExitCode
10+ , hakyllWithExitCodeAndArgs
11+ , Options(..)
12+ , Command(..)
13+ ) where
14+
15+
16+--------------------------------------------------------------------------------
17+import System.Environment (getProgName)
18+import System.Exit (ExitCode (ExitSuccess), exitWith)
19+import System.IO.Unsafe (unsafePerformIO)
20+
21+
22+--------------------------------------------------------------------------------
23+import Data.Monoid ((<>))
24+import qualified Options.Applicative as OA
25+
26+
27+--------------------------------------------------------------------------------
28+import qualified Hakyll.Check as Check
29+import qualified Hakyll.Commands as Commands
30+import qualified Hakyll.Core.Configuration as Config
31+import qualified Hakyll.Core.Logger as Logger
32+import Hakyll.Core.Rules
33+
34+
35+--------------------------------------------------------------------------------
36+-- | This usually is the function with which the user runs the hakyll compiler
37+hakyll :: Rules a -> IO ()
38+hakyll = hakyllWith Config.defaultConfiguration
39+
40+--------------------------------------------------------------------------------
41+-- | A variant of 'hakyll' which allows the user to specify a custom
42+-- configuration
43+hakyllWith :: Config.Configuration -> Rules a -> IO ()
44+hakyllWith conf rules = hakyllWithExitCode conf rules >>= exitWith
45+
46+--------------------------------------------------------------------------------
47+-- | A variant of 'hakyll' which returns an 'ExitCode'
48+hakyllWithExitCode :: Config.Configuration -> Rules a -> IO ExitCode
49+hakyllWithExitCode conf rules = do
50+ args <- defaultParser conf
51+ hakyllWithExitCodeAndArgs conf args rules
52+
53+--------------------------------------------------------------------------------
54+-- | A variant of 'hakyll' which expects a 'Configuration' and command-line
55+-- 'Options'. This gives freedom to implement your own parsing.
56+hakyllWithArgs :: Config.Configuration -> Options -> Rules a -> IO ()
57+hakyllWithArgs conf args rules =
58+ hakyllWithExitCodeAndArgs conf args rules >>= exitWith
59+
60+--------------------------------------------------------------------------------
61+hakyllWithExitCodeAndArgs :: Config.Configuration ->
62+ Options -> Rules a -> IO ExitCode
63+hakyllWithExitCodeAndArgs conf args rules = do
64+ let args' = optCommand args
65+ verbosity' = if verbosity args then Logger.Debug else Logger.Message
66+ check =
67+ if internal_links args' then Check.InternalLinks else Check.All
68+
69+ logger <- Logger.new verbosity'
70+ invokeCommands args' conf check logger rules
71+
72+--------------------------------------------------------------------------------
73+defaultParser :: Config.Configuration -> IO Options
74+defaultParser conf =
75+ OA.customExecParser (OA.prefs OA.showHelpOnError)
76+ (OA.info (OA.helper <*> optionParser conf)
77+ (OA.fullDesc <> OA.progDesc
78+ (progName ++ " - Static site compiler created with Hakyll")))
79+
80+
81+--------------------------------------------------------------------------------
82+invokeCommands :: Command -> Config.Configuration ->
83+ Check.Check -> Logger.Logger -> Rules a -> IO ExitCode
84+invokeCommands args conf check logger rules =
85+ case args of
86+ Build -> Commands.build conf logger rules
87+ Check _ -> Commands.check conf logger check
88+ Clean -> Commands.clean conf logger >> ok
89+ Deploy -> Commands.deploy conf
90+ Preview p -> Commands.preview conf logger rules p >> ok
91+ Rebuild -> Commands.rebuild conf logger rules
92+ Server _ _ -> Commands.server conf logger (host args) (port args) >> ok
93+ Watch _ p s -> Commands.watch conf logger (host args) p (not s) rules >> ok
94+ where
95+ ok = return ExitSuccess
96+
97+
98+--------------------------------------------------------------------------------
99+
100+-- | The parsed command-line options.
101+data Options = Options {verbosity :: Bool, optCommand :: Command}
102+ deriving (Show)
103+
104+-- | The command to run.
105+data Command
106+ = Build
107+ -- ^ Generate the site.
108+ | Check {internal_links :: Bool}
109+ -- ^ Validate the site output.
110+ | Clean
111+ -- ^ Clean up and remove cache.
112+ | Deploy
113+ -- ^ Upload/deploy your site.
114+ | Preview {port :: Int}
115+ -- ^ [DEPRECATED] Please use the watch command.
116+ | Rebuild
117+ -- ^ Clean and build again.
118+ | Server {host :: String, port :: Int}
119+ -- ^ Start a preview server.
120+ | Watch {host :: String, port :: Int, no_server :: Bool }
121+ -- ^ Autocompile on changes and start a preview server.
122+ deriving (Show)
123+
124+{-# DEPRECATED Preview "Use Watch instead." #-}
125+
126+optionParser :: Config.Configuration -> OA.Parser Options
127+optionParser conf = Options <$> verboseParser <*> commandParser conf
128+ where
129+ verboseParser = OA.switch (OA.long "verbose" <> OA.short 'v' <> OA.help "Run in verbose mode")
130+
131+
132+commandParser :: Config.Configuration -> OA.Parser Command
133+commandParser conf = OA.subparser $ foldr ((<>) . produceCommand) mempty commands
134+ where
135+ portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))
136+ hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))
137+
138+ produceCommand (c,a,b) = OA.command c (OA.info (OA.helper <*> a) (b))
139+
140+ commands =
141+ [ ( "build"
142+ , pure Build
143+ , OA.fullDesc <> OA.progDesc "Generate the site"
144+ )
145+ , ( "check"
146+ , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")
147+ , OA.fullDesc <> OA.progDesc "Validate the site output"
148+ )
149+ , ( "clean"
150+ , pure Clean
151+ , OA.fullDesc <> OA.progDesc "Clean up and remove cache"
152+ )
153+ , ( "deploy"
154+ , pure Deploy
155+ , OA.fullDesc <> OA.progDesc "Upload/deploy your site"
156+ )
157+ , ( "preview"
158+ , pure Preview <*> portParser
159+ , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"
160+ )
161+ , ( "rebuild"
162+ , pure Rebuild
163+ , OA.fullDesc <> OA.progDesc "Clean and build again"
164+ )
165+ , ( "server"
166+ , pure Server <*> hostParser <*> portParser
167+ , OA.fullDesc <> OA.progDesc "Start a preview server"
168+ )
169+ , ( "watch"
170+ , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")
171+ , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server."
172+ )
173+ ]
174+
175+
176+--------------------------------------------------------------------------------
177+-- | This is necessary because not everyone calls their program the same...
178+progName :: String
179+progName = unsafePerformIO getProgName
180+{-# NOINLINE progName #-}
src/Hakyll/Preview/Poll.hs → lib/Hakyll/Preview/Poll.hsrenamed+8−18
@@ -14,8 +14,7 @@ import Control.Exception (AsyncException, fromException,
14import Control.Monad (forever, void, when)
14import Control.Monad (forever, void, when)
15import System.Directory (canonicalizePath)
15import System.Directory (canonicalizePath)
16import System.FilePath (pathSeparators)
16import System.FilePath (pathSeparators)
17−import System.FSNotify (Event (..), startManager,
17+import qualified System.FSNotify as FSNotify
18− watchTree)
19
18
20#ifdef mingw32_HOST_OS
19#ifdef mingw32_HOST_OS
21import Control.Concurrent (threadDelay)
20import Control.Concurrent (threadDelay)
@@ -44,12 +43,12 @@ watchUpdates conf update = do
44 shouldBuild <- newEmptyMVar
43 shouldBuild <- newEmptyMVar
45 pattern <- update
44 pattern <- update
46 fullProviderDir <- canonicalizePath $ providerDirectory conf
45 fullProviderDir <- canonicalizePath $ providerDirectory conf
47− manager <- startManager
46+ manager <- FSNotify.startManager
48
47
49 let allowed event = do
48 let allowed event = do
50 -- Absolute path of the changed file. This must be inside provider
49 -- Absolute path of the changed file. This must be inside provider
51 -- dir, since that's the only dir we're watching.
50 -- dir, since that's the only dir we're watching.
52− let path = eventPath event
51+ let path = FSNotify.eventPath event
53 relative = dropWhile (`elem` pathSeparators) $
52 relative = dropWhile (`elem` pathSeparators) $
54 drop (length fullProviderDir) path
53 drop (length fullProviderDir) path
55 identifier = fromFilePath relative
54 identifier = fromFilePath relative
@@ -69,7 +68,7 @@ watchUpdates conf update = do
69
68
70 -- Send an event whenever something occurs so that the thread described
69 -- Send an event whenever something occurs so that the thread described
71 -- above will do a build.
70 -- above will do a build.
72− void $ watchTree manager providerDir (not . isRemove) $ \event -> do
71+ void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
73 allowed' <- allowed event
72 allowed' <- allowed event
74 when allowed' $ void $ tryPutMVar shouldBuild event
73 when allowed' $ void $ tryPutMVar shouldBuild event
75 where
74 where
@@ -77,7 +76,7 @@ watchUpdates conf update = do
77 update' _ _ = void update
76 update' _ _ = void update
78#else
77#else
79 update' event provider = do
78 update' event provider = do
80− let path = provider </> eventPath event
79+ let path = provider </> FSNotify.eventPath event
81 -- on windows, a 'Modified' event is also sent on file deletion
80 -- on windows, a 'Modified' event is also sent on file deletion
82 fileExists <- doesFileExist path
81 fileExists <- doesFileExist path
83
82
@@ -105,15 +104,6 @@ watchUpdates conf update = do
105
104
106
105
107--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
108−eventPath :: Event -> FilePath
109−eventPath evt = evtPath evt
110− where
111− evtPath (Added p _) = p
112− evtPath (Modified p _) = p
113− evtPath (Removed p _) = p
114−
115−
116−--------------------------------------------------------------------------------
117−isRemove :: Event -> Bool
107+isRemove :: FSNotify.Event -> Bool
118−isRemove (Removed _ _) = True
108+isRemove (FSNotify.Removed {}) = True
119−isRemove _ = False
109+isRemove _ = False
src/Hakyll/Preview/Server.hs → lib/Hakyll/Preview/Server.hsrenamed

The content is the same.

lib/Hakyll/Web/CompressCss.hsnew file+101
@@ -0,0 +1,101 @@
1+--------------------------------------------------------------------------------
2+-- | Module used for CSS compression. The compression is currently in a simple
3+-- state, but would typically reduce the number of bytes by about 25%.
4+module Hakyll.Web.CompressCss
5+ ( compressCssCompiler
6+ , compressCss
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Data.Char (isSpace)
12+import Data.List (dropWhileEnd, isPrefixOf)
13+
14+
15+--------------------------------------------------------------------------------
16+import Hakyll.Core.Compiler
17+import Hakyll.Core.Item
18+import Hakyll.Core.Util.String
19+
20+
21+--------------------------------------------------------------------------------
22+-- | Compiler form of 'compressCss'
23+compressCssCompiler :: Compiler (Item String)
24+compressCssCompiler = fmap compressCss <$> getResourceString
25+
26+
27+--------------------------------------------------------------------------------
28+-- | Compress CSS to speed up your site.
29+compressCss :: String -> String
30+compressCss = withoutStrings (handleCalcExpressions compressSeparators . compressWhitespace)
31+ . dropWhileEnd isSpace
32+ . dropWhile isSpace
33+ . stripComments
34+
35+
36+--------------------------------------------------------------------------------
37+-- | Compresses certain forms of separators.
38+compressSeparators :: String -> String
39+compressSeparators =
40+ replaceAll "; *}" (const "}") .
41+ replaceAll ";+" (const ";") .
42+ replaceAll " *[{};,>+~!] *" (take 1 . dropWhile isSpace) .
43+ replaceAll ": *" (take 1) -- not destroying pseudo selectors (#323)
44+
45+-- | Uses `compressCalcExpression` on all parenthesised calc expressions
46+-- and applies `transform` to all parts outside of them
47+handleCalcExpressions :: (String -> String) -> String -> String
48+handleCalcExpressions transform = top transform
49+ where
50+ top f "" = f ""
51+ top f str | "calc(" `isPrefixOf` str = f "calc" ++ nested 0 compressCalcExpression (drop 4 str)
52+ top f (x:xs) = top (f . (x:)) xs
53+
54+ -- when called with depth=0, the first character must be a '('
55+ nested :: Int -> (String -> String) -> String -> String
56+ nested _ f "" = f "" -- shouldn't happen, mismatched nesting
57+ nested depth f str | "calc(" `isPrefixOf` str = nested depth f (drop 4 str)
58+ nested 1 f (')':xs) = f ")" ++ top transform xs
59+ nested depth f (x:xs) = nested (case x of
60+ '(' -> depth + 1
61+ ')' -> depth - 1 -- assert: depth > 1
62+ _ -> depth
63+ ) (f . (x:)) xs
64+
65+-- | does not remove whitespace around + and -, which is important in calc() expressions
66+compressCalcExpression :: String -> String
67+compressCalcExpression =
68+ replaceAll " *[*/] *| *\\)|\\( *" (take 1 . dropWhile isSpace)
69+
70+--------------------------------------------------------------------------------
71+-- | Compresses all whitespace.
72+compressWhitespace :: String -> String
73+compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
74+
75+--------------------------------------------------------------------------------
76+-- | Function that strips CSS comments away (outside of strings).
77+stripComments :: String -> String
78+stripComments "" = ""
79+stripComments ('/':'*':str) = stripComments $ eatComment str
80+stripComments (x:xs) | x `elem` "\"'" = retainString x xs stripComments
81+ | otherwise = x : stripComments xs
82+
83+eatComment :: String -> String
84+eatComment "" = ""
85+eatComment ('*':'/':str) = str
86+eatComment (_:str) = eatComment str
87+
88+
89+--------------------------------------------------------------------------------
90+-- | Helper functions to handle string tokens correctly.
91+
92+-- TODO: handle backslash escapes
93+withoutStrings :: (String -> String) -> String -> String
94+withoutStrings f str = case span (`notElem` "\"'") str of
95+ (text, "") -> f text
96+ (text, d:rest) -> f text ++ retainString d rest (withoutStrings f)
97+
98+retainString :: Char -> String -> (String -> String) -> String
99+retainString delim str cont = case span (/= delim) str of
100+ (val, "") -> delim : val
101+ (val, _:rest) -> delim : val ++ delim : cont rest
src/Hakyll/Web/Feed.hs → lib/Hakyll/Web/Feed.hsrenamed+59−21
@@ -1,3 +1,6 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+
1--------------------------------------------------------------------------------
4--------------------------------------------------------------------------------
2-- | A Module that allows easy rendering of RSS feeds.
5-- | A Module that allows easy rendering of RSS feeds.
3--
6--
@@ -20,21 +23,40 @@ module Hakyll.Web.Feed
20 ( FeedConfiguration (..)
23 ( FeedConfiguration (..)
21 , renderRss
24 , renderRss
22 , renderAtom
25 , renderAtom
26+ , renderRssWithTemplates
27+ , renderAtomWithTemplates
23 ) where
28 ) where
24
29
25
30
26--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
27import Hakyll.Core.Compiler
32import Hakyll.Core.Compiler
28−import Hakyll.Core.Compiler.Internal
29import Hakyll.Core.Item
33import Hakyll.Core.Item
30−import Hakyll.Core.Util.String (replaceAll)
34+import Hakyll.Core.Util.String (replaceAll)
31import Hakyll.Web.Template
35import Hakyll.Web.Template
32import Hakyll.Web.Template.Context
36import Hakyll.Web.Template.Context
33import Hakyll.Web.Template.List
37import Hakyll.Web.Template.List
34
38
35
39
36--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
37−import Paths_hakyll
41+import Data.FileEmbed (makeRelativeToProject)
42+
43+
44+--------------------------------------------------------------------------------
45+rssTemplate :: Template
46+rssTemplate =
47+ $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate)
48+
49+rssItemTemplate :: Template
50+rssItemTemplate =
51+ $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate)
52+
53+atomTemplate :: Template
54+atomTemplate =
55+ $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate)
56+
57+atomItemTemplate :: Template
58+atomItemTemplate =
59+ $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate)
38
60
39
61
40--------------------------------------------------------------------------------
62--------------------------------------------------------------------------------
@@ -55,16 +77,13 @@ data FeedConfiguration = FeedConfiguration
55
77
56--------------------------------------------------------------------------------
78--------------------------------------------------------------------------------
57-- | Abstract function to render any feed.
79-- | Abstract function to render any feed.
58−renderFeed :: FilePath -- ^ Feed template
80+renderFeed :: Template -- ^ Default feed template
59− -> FilePath -- ^ Item template
81+ -> Template -- ^ Default item template
60 -> FeedConfiguration -- ^ Feed configuration
82 -> FeedConfiguration -- ^ Feed configuration
61 -> Context String -- ^ Context for the items
83 -> Context String -- ^ Context for the items
62 -> [Item String] -- ^ Input items
84 -> [Item String] -- ^ Input items
63 -> Compiler (Item String) -- ^ Resulting item
85 -> Compiler (Item String) -- ^ Resulting item
64−renderFeed feedPath itemPath config itemContext items = do
86+renderFeed feedTpl itemTpl config itemContext items = do
65− feedTpl <- loadTemplate feedPath
66− itemTpl <- loadTemplate itemPath
67−
68 protectedItems <- mapM (applyFilter protectCDATA) items
87 protectedItems <- mapM (applyFilter protectCDATA) items
69 body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
88 body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
70 applyTemplate feedTpl feedContext body
89 applyTemplate feedTpl feedContext body
@@ -73,10 +92,6 @@ renderFeed feedPath itemPath config itemContext items = do
73 applyFilter tr str = return $ fmap tr str
92 applyFilter tr str = return $ fmap tr str
74 protectCDATA :: String -> String
93 protectCDATA :: String -> String
75 protectCDATA = replaceAll "]]>" (const "]]&gt;")
94 protectCDATA = replaceAll "]]>" (const "]]&gt;")
76− -- Auxiliary: load a template from a datafile
77− loadTemplate path = do
78− file <- compilerUnsafeIO $ getDataFileName path
79− unsafeReadTemplateFile file
80
95
81 itemContext' = mconcat
96 itemContext' = mconcat
82 [ itemContext
97 [ itemContext
@@ -102,8 +117,35 @@ renderFeed feedPath itemPath config itemContext items = do
102 updatedField = field "updated" $ \_ -> case items of
117 updatedField = field "updated" $ \_ -> case items of
103 [] -> return "Unknown"
118 [] -> return "Unknown"
104 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
119 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
105− ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
106 StringField s -> return s
120 StringField s -> return s
121+ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
122+
123+--------------------------------------------------------------------------------
124+-- | Render an RSS feed using given templates with a number of items.
125+renderRssWithTemplates ::
126+ Template -- ^ Feed template
127+ -> Template -- ^ Item template
128+ -> FeedConfiguration -- ^ Feed configuration
129+ -> Context String -- ^ Item context
130+ -> [Item String] -- ^ Feed items
131+ -> Compiler (Item String) -- ^ Resulting feed
132+renderRssWithTemplates feedTemplate itemTemplate config context = renderFeed
133+ feedTemplate itemTemplate config
134+ (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
135+
136+
137+--------------------------------------------------------------------------------
138+-- | Render an Atom feed using given templates with a number of items.
139+renderAtomWithTemplates ::
140+ Template -- ^ Feed template
141+ -> Template -- ^ Item template
142+ -> FeedConfiguration -- ^ Feed configuration
143+ -> Context String -- ^ Item context
144+ -> [Item String] -- ^ Feed items
145+ -> Compiler (Item String) -- ^ Resulting feed
146+renderAtomWithTemplates feedTemplate itemTemplate config context = renderFeed
147+ feedTemplate itemTemplate config
148+ (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
107
149
108
150
109--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
@@ -112,9 +154,7 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
112 -> Context String -- ^ Item context
154 -> Context String -- ^ Item context
113 -> [Item String] -- ^ Feed items
155 -> [Item String] -- ^ Feed items
114 -> Compiler (Item String) -- ^ Resulting feed
156 -> Compiler (Item String) -- ^ Resulting feed
115−renderRss config context = renderFeed
157+renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
116− "templates/rss.xml" "templates/rss-item.xml" config
117− (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
118
158
119
159
120--------------------------------------------------------------------------------
160--------------------------------------------------------------------------------
@@ -123,13 +163,11 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
123 -> Context String -- ^ Item context
163 -> Context String -- ^ Item context
124 -> [Item String] -- ^ Feed items
164 -> [Item String] -- ^ Feed items
125 -> Compiler (Item String) -- ^ Resulting feed
165 -> Compiler (Item String) -- ^ Resulting feed
126−renderAtom config context = renderFeed
166+renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
127− "templates/atom.xml" "templates/atom-item.xml" config
128− (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
129
167
130
168
131--------------------------------------------------------------------------------
169--------------------------------------------------------------------------------
132-- | Copies @$updated$@ from @$published$@ if it is not already set.
170-- | Copies @$updated$@ from @$published$@ if it is not already set.
133makeItemContext :: String -> Context a -> Context a
171makeItemContext :: String -> Context a -> Context a
134makeItemContext fmt context = mconcat
172makeItemContext fmt context = mconcat
135− [dateField "published" fmt, context, dateField "updated" fmt]
173+ [context, dateField "published" fmt, dateField "updated" fmt]
src/Hakyll/Web/Html.hs → lib/Hakyll/Web/Html.hsrenamed+6−2
@@ -3,6 +3,7 @@
3module Hakyll.Web.Html
3module Hakyll.Web.Html
4 ( -- * Generic
4 ( -- * Generic
5 withTags
5 withTags
6+ , withTagList
6
7
7 -- * Headers
8 -- * Headers
8 , demoteHeaders
9 , demoteHeaders
@@ -36,8 +37,11 @@ import Network.URI (isUnreserved, escapeURIString)
36--------------------------------------------------------------------------------
37--------------------------------------------------------------------------------
37-- | Map over all tags in the document
38-- | Map over all tags in the document
38withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
39withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
39−withTags f = renderTags' . map f . parseTags'
40+withTags = withTagList . map
40
41
42+-- | Map over all tags (as list) in the document
43+withTagList :: ([TS.Tag String] -> [TS.Tag String]) -> String -> String
44+withTagList f = renderTags' . f . parseTags'
41
45
42--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
43-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
47-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
@@ -55,7 +59,7 @@ demoteHeaders = withTags $ \tag -> case tag of
55
59
56--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
57isUrlAttribute :: String -> Bool
61isUrlAttribute :: String -> Bool
58−isUrlAttribute = (`elem` ["src", "href", "data", "poster"])
62+isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
59
63
60
64
61--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
src/Hakyll/Web/Html/RelativizeUrls.hs → lib/Hakyll/Web/Html/RelativizeUrls.hsrenamed

The content is the same.

src/Hakyll/Web/Paginate.hs → lib/Hakyll/Web/Paginate.hsrenamed+28−1
@@ -11,7 +11,8 @@ module Hakyll.Web.Paginate
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
14−import Control.Monad (forM_)
14+import Control.Applicative (empty)
15+import Control.Monad (forM_, forM)
15import qualified Data.Map as M
16import qualified Data.Map as M
16import qualified Data.Set as S
17import qualified Data.Set as S
17
18
@@ -93,6 +94,18 @@ paginatePage pag pageNumber
93-- | A default paginate context which provides the following keys:
94-- | A default paginate context which provides the following keys:
94--
95--
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@
96paginateContext :: Paginate -> PageNumber -> Context a
109paginateContext :: Paginate -> PageNumber -> Context a
97paginateContext pag currentPage = mconcat
110paginateContext pag currentPage = mconcat
98 [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
111 [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
@@ -106,6 +119,20 @@ paginateContext pag currentPage = mconcat
106 , field "currentPageNum" $ \i -> thisPage i >>= num
119 , field "currentPageNum" $ \i -> thisPage i >>= num
107 , field "currentPageUrl" $ \i -> thisPage i >>= url
120 , field "currentPageUrl" $ \i -> thisPage i >>= url
108 , constField "numPages" $ show $ paginateNumPages pag
121 , constField "numPages" $ show $ paginateNumPages pag
122+ , Context $ \k _ i -> case k of
123+ "allPages" -> do
124+ let ctx =
125+ field "isCurrent" (\n -> if fst (itemBody n) == currentPage then return "true" else empty) `mappend`
126+ field "num" (num . itemBody) `mappend`
127+ field "url" (url . itemBody)
128+
129+ list <- forM [1 .. lastPage] $
130+ \n -> if n == currentPage then thisPage i else otherPage n
131+ items <- mapM makeItem list
132+ return $ ListField ctx items
133+ _ -> do
134+ empty
135+
109 ]
136 ]
110 where
137 where
111 lastPage = paginateNumPages pag
138 lastPage = paginateNumPages pag
src/Hakyll/Web/Pandoc.hs → lib/Hakyll/Web/Pandoc.hsrenamed+13−12
@@ -22,9 +22,9 @@ module Hakyll.Web.Pandoc
22
22
23
23
24--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
25−import qualified Data.Set as S
25+import qualified Data.Text as T
26import Text.Pandoc
26import Text.Pandoc
27−import Text.Pandoc.Error (PandocError (..))
27+import Text.Pandoc.Highlighting (pygments)
28
28
29
29
30--------------------------------------------------------------------------------
30--------------------------------------------------------------------------------
@@ -48,12 +48,10 @@ readPandocWith
48 -> Item String -- ^ String to read
48 -> Item String -- ^ String to read
49 -> Compiler (Item Pandoc) -- ^ Resulting document
49 -> Compiler (Item Pandoc) -- ^ Resulting document
50readPandocWith ropt item =
50readPandocWith ropt item =
51− case traverse (reader ropt (itemFileType item)) item of
51+ case runPure $ traverse (reader ropt (itemFileType item)) (fmap T.pack item) of
52− Left (ParseFailure err) -> fail $
52+ Left err -> fail $
53− "Hakyll.Web.Pandoc.readPandocWith: parse failed: " ++ err
54− Left (ParsecError _ err) -> fail $
55 "Hakyll.Web.Pandoc.readPandocWith: parse failed: " ++ show err
53 "Hakyll.Web.Pandoc.readPandocWith: parse failed: " ++ show err
56− Right item' -> return item'
54+ Right item' -> return item'
57 where
55 where
58 reader ro t = case t of
56 reader ro t = case t of
59 DocBook -> readDocBook ro
57 DocBook -> readDocBook ro
@@ -69,7 +67,7 @@ readPandocWith ropt item =
69 "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
67 "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
70 "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
68 "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
71
69
72− addExt ro e = ro {readerExtensions = S.insert e $ readerExtensions ro}
70+ addExt ro e = ro {readerExtensions = enableExtension e $ readerExtensions ro}
73
71
74
72
75--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
@@ -84,7 +82,10 @@ writePandoc = writePandocWith defaultHakyllWriterOptions
84writePandocWith :: WriterOptions -- ^ Writer options for pandoc
82writePandocWith :: WriterOptions -- ^ Writer options for pandoc
85 -> Item Pandoc -- ^ Document to write
83 -> Item Pandoc -- ^ Document to write
86 -> Item String -- ^ Resulting HTML
84 -> Item String -- ^ Resulting HTML
87−writePandocWith wopt = fmap $ writeHtmlString wopt
85+writePandocWith wopt (Item itemi doc) =
86+ case runPure $ writeHtml5String wopt doc of
87+ Left err -> error $ "Hakyll.Web.Pandoc.writePandocWith: " ++ show err
88+ Right item' -> Item itemi $ T.unpack item'
88
89
89
90
90--------------------------------------------------------------------------------
91--------------------------------------------------------------------------------
@@ -147,7 +148,7 @@ defaultHakyllReaderOptions :: ReaderOptions
147defaultHakyllReaderOptions = def
148defaultHakyllReaderOptions = def
148 { -- The following option causes pandoc to read smart typography, a nice
149 { -- The following option causes pandoc to read smart typography, a nice
149 -- and free bonus.
150 -- and free bonus.
150− readerSmart = True
151+ readerExtensions = enableExtension Ext_smart pandocExtensions
151 }
152 }
152
153
153
154
@@ -157,8 +158,8 @@ defaultHakyllWriterOptions :: WriterOptions
157defaultHakyllWriterOptions = def
158defaultHakyllWriterOptions = def
158 { -- This option causes literate haskell to be written using '>' marks in
159 { -- This option causes literate haskell to be written using '>' marks in
159 -- html, which I think is a good default.
160 -- html, which I think is a good default.
160− writerExtensions = S.insert Ext_literate_haskell (writerExtensions def)
161+ writerExtensions = enableExtension Ext_smart pandocExtensions
161 , -- We want to have hightlighting by default, to be compatible with earlier
162 , -- We want to have hightlighting by default, to be compatible with earlier
162 -- Hakyll releases
163 -- Hakyll releases
163− writerHighlight = True
164+ writerHighlightStyle = Just pygments
164 }
165 }
src/Hakyll/Web/Pandoc/Biblio.hs → lib/Hakyll/Web/Pandoc/Biblio.hsrenamed+17−6
@@ -25,17 +25,19 @@ module Hakyll.Web.Pandoc.Biblio
25--------------------------------------------------------------------------------
25--------------------------------------------------------------------------------
26import Control.Monad (liftM, replicateM)
26import Control.Monad (liftM, replicateM)
27import Data.Binary (Binary (..))
27import Data.Binary (Binary (..))
28−import Data.Default (def)
29import Data.Typeable (Typeable)
28import Data.Typeable (Typeable)
30import Hakyll.Core.Compiler
29import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Identifier
31import Hakyll.Core.Identifier
32import Hakyll.Core.Item
32import Hakyll.Core.Item
33+import Hakyll.Core.Provider
33import Hakyll.Core.Writable
34import Hakyll.Core.Writable
34import Hakyll.Web.Pandoc
35import Hakyll.Web.Pandoc
35import Hakyll.Web.Pandoc.Binary ()
36import Hakyll.Web.Pandoc.Binary ()
36import qualified Text.CSL as CSL
37import qualified Text.CSL as CSL
37import Text.CSL.Pandoc (processCites)
38import Text.CSL.Pandoc (processCites)
38−import Text.Pandoc (Pandoc, ReaderOptions (..))
39+import Text.Pandoc (Pandoc, ReaderOptions (..),
40+ enableExtension, Extension (..))
39
41
40
42
41--------------------------------------------------------------------------------
43--------------------------------------------------------------------------------
@@ -83,8 +85,11 @@ instance Writable Biblio where
83--------------------------------------------------------------------------------
85--------------------------------------------------------------------------------
84biblioCompiler :: Compiler (Item Biblio)
86biblioCompiler :: Compiler (Item Biblio)
85biblioCompiler = do
87biblioCompiler = do
86− filePath <- toFilePath <$> getUnderlying
88+ filePath <- getResourceFilePath
87− makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile filePath)
89+ makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile idpred filePath)
90+ where
91+ -- This is a filter on citations. We include all citations.
92+ idpred = const True
88
93
89
94
90--------------------------------------------------------------------------------
95--------------------------------------------------------------------------------
@@ -95,7 +100,9 @@ readPandocBiblio :: ReaderOptions
95 -> Compiler (Item Pandoc)
100 -> Compiler (Item Pandoc)
96readPandocBiblio ropt csl biblio item = do
101readPandocBiblio ropt csl biblio item = do
97 -- Parse CSL file, if given
102 -- Parse CSL file, if given
103+ provider <- compilerProvider <$> compilerAsk
104+ style <- unsafeCompiler $
98− style <- unsafeCompiler $ CSL.readCSLFile Nothing . toFilePath . itemIdentifier $ csl
105+ CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
99
106
100 -- We need to know the citation keys, add then *before* actually parsing the
107 -- We need to know the citation keys, add then *before* actually parsing the
101 -- actual page. If we don't do this, pandoc won't even consider them
108 -- actual page. If we don't do this, pandoc won't even consider them
@@ -112,4 +119,8 @@ pandocBiblioCompiler cslFileName bibFileName = do
112 csl <- load $ fromFilePath cslFileName
119 csl <- load $ fromFilePath cslFileName
113 bib <- load $ fromFilePath bibFileName
120 bib <- load $ fromFilePath bibFileName
114 liftM writePandoc
121 liftM writePandoc
115− (getResourceBody >>= readPandocBiblio def csl bib)
122+ (getResourceBody >>= readPandocBiblio ropt csl bib)
123+ where ropt = defaultHakyllReaderOptions
124+ { -- The following option enables citation rendering
125+ readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
126+ }
src/Hakyll/Web/Pandoc/Binary.hs → lib/Hakyll/Web/Pandoc/Binary.hsrenamed+19−17
@@ -1,31 +1,33 @@
1{-# OPTIONS_GHC -fno-warn-orphans #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveGeneric #-}
3module Hakyll.Web.Pandoc.Binary where
3module Hakyll.Web.Pandoc.Binary where
4
4
5−import Data.Binary (Binary (..))
5+import Data.Binary (Binary (..))
6
6
7−import qualified Text.CSL as CSL
7+import qualified Text.CSL as CSL
8−import qualified Text.CSL.Reference as REF
8+import qualified Text.CSL.Reference as REF
9−import qualified Text.CSL.Style as STY
9+import qualified Text.CSL.Style as STY
10−import Text.Pandoc
10+import Text.Pandoc
11
11
12--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
13-- orphans
13-- orphans
14
14
15−instance Binary REF.CNum
15+instance Binary Alignment
16−instance Binary REF.Literal
17−instance Binary REF.RefDate
18−instance Binary REF.RefType
19−instance Binary STY.Agent
20−instance Binary STY.Formatted
21−instance Binary Inline
22instance Binary Block
16instance Binary Block
17+instance Binary CSL.Reference
23instance Binary Citation
18instance Binary Citation
24−instance Binary MathType
25−instance Binary Alignment
26instance Binary CitationMode
19instance Binary CitationMode
27−instance Binary QuoteType
28instance Binary Format
20instance Binary Format
21+instance Binary Inline
29instance Binary ListNumberDelim
22instance Binary ListNumberDelim
30instance Binary ListNumberStyle
23instance Binary ListNumberStyle
31−instance Binary CSL.Reference
24+instance Binary MathType
25+instance Binary QuoteType
26+instance Binary REF.CLabel
27+instance Binary REF.CNum
28+instance Binary REF.Literal
29+instance Binary REF.RefDate
30+instance Binary REF.RefType
31+instance Binary REF.Season
32+instance Binary STY.Agent
33+instance Binary STY.Formatted
src/Hakyll/Web/Pandoc/FileType.hs → lib/Hakyll/Web/Pandoc/FileType.hsrenamed

The content is the same.

lib/Hakyll/Web/Redirect.hsnew file+100
@@ -0,0 +1,100 @@
1+-- | Module used for generating HTML redirect pages. This allows renaming pages
2+-- to avoid breaking existing links without requiring server-side support for
3+-- formal 301 Redirect error codes
4+module Hakyll.Web.Redirect
5+ ( Redirect (..)
6+ , createRedirects
7+ ) where
8+
9+import Control.Applicative ((<$>))
10+import Control.Monad (forM_, when)
11+import Data.Binary (Binary (..))
12+import Data.List (sort, group)
13+import Hakyll.Core.Compiler
14+import Hakyll.Core.Identifier
15+import Hakyll.Core.Routes
16+import Hakyll.Core.Rules
17+import Hakyll.Core.Writable (Writable (..))
18+
19+-- | This function exposes a higher-level interface compared to using the
20+-- 'Redirect' type manually.
21+--
22+-- This creates, using a database mapping broken URLs to working ones, HTML
23+-- files which will do HTML META tag redirect pages (since, as a static site, we
24+-- can't use web-server-level 301 redirects, and using JS is gross).
25+--
26+-- This is useful for sending people using old URLs to renamed versions, dealing
27+-- with common typos etc, and will increase site traffic. Such broken URLs can
28+-- be found by looking at server logs or by using Google Webmaster Tools.
29+-- Broken URLs must be valid Haskell strings, non-URL-escaped valid POSIX
30+-- filenames, and relative links, since they will be defined in a @hakyll.hs@
31+-- and during generation, written to disk with the filename corresponding to the
32+-- broken URLs. (Target URLs can be absolute or relative, but should be
33+-- URL-escaped.) So broken incoming links like <http://www.gwern.net/foo/> which
34+-- should be <http://www.gwern.net/foobar> cannot be fixed (since you cannot
35+-- create a HTML file named @"foo/"@ on disk, as that would be a directory).
36+--
37+-- An example of a valid association list would be:
38+--
39+-- > brokenLinks =
40+-- > [ ("projects.html", "http://github.com/gwern")
41+-- > , ("/Black-market archive", "Black-market%20archives")
42+-- > ]
43+--
44+-- In which case the functionality can then be used in `main` with a line like:
45+--
46+-- > version "redirects" $ createRedirects brokenLinks
47+--
48+-- The 'version' is recommended to separate these items from your other pages.
49+--
50+-- The on-disk files can then be uploaded with HTML mimetypes
51+-- (either explicitly by generating and uploading them separately, by
52+-- auto-detection of the filetype, or an upload tool defaulting to HTML
53+-- mimetype, such as calling @s3cmd@ with @--default-mime-type=text/html@) and
54+-- will redirect browsers and search engines going to the old/broken URLs.
55+--
56+-- See also <https://groups.google.com/d/msg/hakyll/sWc6zxfh-uM/fUpZPsFNDgAJ>.
57+createRedirects :: [(Identifier, String)] -> Rules ()
58+createRedirects redirects =
59+ do -- redirects are many-to-fewer; keys must be unique, and must point somewhere else:
60+ let gkeys = group $ sort $ map fst redirects
61+ forM_ gkeys $ \gkey -> case gkey of
62+ (k : _ : _) -> fail $
63+ "Duplicate 301 redirects; " ++ show k ++ " is ambiguous."
64+ _ -> return ()
65+
66+ forM_ redirects $ \(r, t) ->
67+ when (toFilePath r == t) $ fail $
68+ "Self-redirect detected: " ++ show r ++ " points to itself."
69+
70+ forM_ redirects $ \(ident, to) ->
71+ create [ident] $ do
72+ route idRoute
73+ compile $ makeItem $! Redirect to
74+
75+-- | This datatype can be used directly if you want a lower-level interface to
76+-- generate redirects. For example, if you want to redirect @foo.html@ to
77+-- @bar.jpg@, you can use:
78+--
79+-- > create ["foo.html"] $ do
80+-- > route idRoute
81+-- > compile $ makeItem $ Redirect "bar.jpg"
82+data Redirect = Redirect
83+ { redirectTo :: String
84+ } deriving (Eq, Ord, Show)
85+
86+instance Binary Redirect where
87+ put (Redirect to) = put to
88+ get = Redirect <$> get
89+
90+instance Writable Redirect where
91+ write path = write path . fmap redirectToHtml
92+
93+redirectToHtml :: Redirect -> String
94+redirectToHtml (Redirect working) =
95+ "<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><meta name=\"generator\" content=\"hakyll\"/>" ++
96+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" ++
97+ "<meta http-equiv=\"refresh\" content=\"0; url=" ++ working ++
98+ "\"><link rel=\"canonical\" href=\"" ++ working ++
99+ "\"><title>Permanent Redirect</title></head><body><p>The page has moved to: <a href=\"" ++ working ++
100+ "\">this page</a></p></body></html>"
src/Hakyll/Web/Tags.hs → lib/Hakyll/Web/Tags.hsrenamed+2−1
@@ -43,6 +43,7 @@
43module Hakyll.Web.Tags
43module Hakyll.Web.Tags
44 ( Tags (..)
44 ( Tags (..)
45 , getTags
45 , getTags
46+ , getCategory
46 , buildTagsWith
47 , buildTagsWith
47 , buildTags
48 , buildTags
48 , buildCategories
49 , buildCategories
@@ -112,7 +113,7 @@ getTags identifier = do
112
113
113
114
114--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
115−-- | Obtain categories from a page.
116+-- | Obtain category from a page.
116getCategory :: MonadMetadata m => Identifier -> m [String]
117getCategory :: MonadMetadata m => Identifier -> m [String]
117getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118
119
src/Hakyll/Web/Template.hs → lib/Hakyll/Web/Template.hsrenamed+24
@@ -138,6 +138,7 @@
138-- > 3...2...1
138-- > 3...2...1
139-- > </p>
139-- > </p>
140--
140--
141+{-# LANGUAGE TemplateHaskell #-}
141module Hakyll.Web.Template
142module Hakyll.Web.Template
142 ( Template
143 ( Template
143 , templateBodyCompiler
144 , templateBodyCompiler
@@ -146,9 +147,32 @@ module Hakyll.Web.Template
146 , loadAndApplyTemplate
147 , loadAndApplyTemplate
147 , applyAsTemplate
148 , applyAsTemplate
148 , readTemplate
149 , readTemplate
150+ , compileTemplateItem
149 , unsafeReadTemplateFile
151 , unsafeReadTemplateFile
152+ , embedTemplate
150 ) where
153 ) where
151
154
152
155
153--------------------------------------------------------------------------------
156--------------------------------------------------------------------------------
154import Hakyll.Web.Template.Internal
157import Hakyll.Web.Template.Internal
158+
159+
160+--------------------------------------------------------------------------------
161+import Data.FileEmbed (embedFile)
162+import qualified Data.Text as T
163+import qualified Data.Text.Encoding as T
164+import Language.Haskell.TH (Exp, Q)
165+
166+
167+--------------------------------------------------------------------------------
168+-- | Embed template allows you embed a template within the Haskell binary.
169+-- Example:
170+--
171+-- > myTemplate :: Template
172+-- > myTemplate = $(embedTemplate "test.html")
173+embedTemplate :: FilePath -> Q Exp
174+embedTemplate filePath = [|
175+ let source = T.unpack $ T.decodeUtf8 $(embedFile filePath) in
176+ case parseTemplateElemsFile filePath source of
177+ Left err -> error err
178+ Right tpl -> template filePath tpl |]
src/Hakyll/Web/Template/Context.hs → lib/Hakyll/Web/Template/Context.hsrenamed+135−33
@@ -1,6 +1,27 @@
1+-- | This module provides 'Context's which are used to expand expressions in
2+-- templates and allow for arbitrary customisation.
3+--
4+-- 'Template's define a small expression DSL which consists of strings,
5+-- identifiers and function application. There is no type system, every value is
6+-- a string and on the top level they get substituted verbatim into the page.
7+--
8+-- For example, you can build a context that contains
9+--
10+-- > … <> functionField "concat" (const . concat) <> …
11+--
12+-- which will allow you to use the @concat@ identifier as a function that takes
13+-- arbitrarily many stings and concatenates them to a new string:
14+--
15+-- > $partial(concat("templates/categories/", category))$
16+--
17+-- This will evaluate the @category@ field in the context, then prepend he path,
18+-- and include the referenced file as a template.
19+
20+
1--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
2{-# LANGUAGE CPP #-}
22{-# LANGUAGE CPP #-}
3{-# LANGUAGE ExistentialQuantification #-}
23{-# LANGUAGE ExistentialQuantification #-}
24+{-# LANGUAGE RankNTypes #-}
4module Hakyll.Web.Template.Context
25module Hakyll.Web.Template.Context
5 ( ContextField (..)
26 ( ContextField (..)
6 , Context (..)
27 , Context (..)
@@ -28,13 +49,17 @@ module Hakyll.Web.Template.Context
28 , teaserField
49 , teaserField
29 , teaserFieldWithSeparator
50 , teaserFieldWithSeparator
30 , missingField
51 , missingField
52+ , bindItem
31 ) where
53 ) where
32
54
33
55
34--------------------------------------------------------------------------------
56--------------------------------------------------------------------------------
35import Control.Applicative (Alternative (..))
57import Control.Applicative (Alternative (..))
36import Control.Monad (msum)
58import Control.Monad (msum)
37−import Data.List (intercalate)
59+import Data.List (intercalate, tails)
60+#if MIN_VERSION_base(4,9,0)
61+import Data.Semigroup (Semigroup (..))
62+#endif
38import Data.Time.Clock (UTCTime (..))
63import Data.Time.Clock (UTCTime (..))
39import Data.Time.Format (formatTime)
64import Data.Time.Format (formatTime)
40import qualified Data.Time.Format as TF
65import qualified Data.Time.Format as TF
@@ -47,13 +72,16 @@ import Hakyll.Core.Metadata
47import Hakyll.Core.Provider
72import Hakyll.Core.Provider
48import Hakyll.Core.Util.String (needlePrefix, splitAll)
73import Hakyll.Core.Util.String (needlePrefix, splitAll)
49import Hakyll.Web.Html
74import Hakyll.Web.Html
75+import Prelude hiding (id)
50−import System.FilePath (splitDirectories, takeBaseName)
76+import System.FilePath (dropExtension, splitDirectories,
77+ takeBaseName)
51
78
52
79
53--------------------------------------------------------------------------------
80--------------------------------------------------------------------------------
54-- | Mostly for internal usage
81-- | Mostly for internal usage
55data ContextField
82data ContextField
83+ = EmptyField
56− = StringField String
84+ | StringField String
57 | forall a. ListField (Context a) [Item a]
85 | forall a. ListField (Context a) [Item a]
58
86
59
87
@@ -78,71 +106,123 @@ newtype Context a = Context
78
106
79
107
80--------------------------------------------------------------------------------
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
81instance Monoid (Context a) where
119instance Monoid (Context a) where
82 mempty = missingField
120 mempty = missingField
83 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
121 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122+#endif
84
123
85
124
86--------------------------------------------------------------------------------
125--------------------------------------------------------------------------------
87field' :: String -> (Item a -> Compiler ContextField) -> Context a
126field' :: String -> (Item a -> Compiler ContextField) -> Context a
88−field' key value = Context $ \k _ i -> if k == key then value i else empty
127+field' key value = Context $ \k _ i ->
128+ if k == key
129+ then value i
130+ else noResult $ "Tried field " ++ key
89
131
90
132
91--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
92−-- | Constructs a new field in the 'Context.'
134+-- | Constructs a new field for a 'Context'.
135+-- If the key matches, the compiler is run and its result is substituted in the
136+-- template.
137+--
138+-- If the compiler fails, the field will be considered non-existent
139+-- in an @$if()$@ macro or ultimately break the template application
140+-- (unless the key is found in another context when using '<>').
141+-- Use 'empty' or 'noResult' for intentional failures of fields used in
142+-- @$if()$@, to distinguish them from exceptions thrown with 'fail'.
93field
143field
94 :: String -- ^ Key
144 :: String -- ^ Key
95 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
145 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
96− -- on the item
146+ -- on the item (e.g. accessing metadata)
97 -> Context a
147 -> Context a
98field key value = field' key (fmap StringField . value)
148field key value = field' key (fmap StringField . value)
99
149
100
150
101--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
102-- | Creates a 'field' to use with the @$if()$@ template macro.
152-- | Creates a 'field' to use with the @$if()$@ template macro.
153+-- Attempting to substitute the field into the template will cause an error.
103boolField
154boolField
104 :: String
155 :: String
105 -> (Item a -> Bool)
156 -> (Item a -> Bool)
106 -> Context a
157 -> Context a
107−boolField name f = field name (\i -> if f i
158+boolField name f = field' name (\i -> if f i
108− then pure (error $ unwords ["no string value for bool field:",name])
159+ then return EmptyField
109− else empty)
160+ else noResult $ "Field " ++ name ++ " is false")
110
161
111
162
112--------------------------------------------------------------------------------
163--------------------------------------------------------------------------------
113−-- | Creates a 'field' that does not depend on the 'Item'
164+-- | Creates a 'field' that does not depend on the 'Item' but always yields
165+-- the same string
114−constField :: String -> String -> Context a
166+constField :: String -- ^ Key
167+ -> String -- ^ Value
168+ -> Context a
115constField key = field key . const . return
169constField key = field key . const . return
116
170
117
171
118--------------------------------------------------------------------------------
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.
119listField :: String -> Context a -> Compiler [Item a] -> Context b
176listField :: String -> Context a -> Compiler [Item a] -> Context b
120listField key c xs = listFieldWith key c (const xs)
177listField key c xs = listFieldWith key c (const xs)
121
178
122
179
123--------------------------------------------------------------------------------
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.
124listFieldWith
187listFieldWith
125 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
188 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
126listFieldWith key c f = field' key $ fmap (ListField c) . f
189listFieldWith key c f = field' key $ fmap (ListField c) . f
127
190
128
191
129--------------------------------------------------------------------------------
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
130−functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
198+ -> ([String] -> Item a -> Compiler String) -- ^ Function
199+ -> Context a
131functionField name value = Context $ \k args i ->
200functionField name value = Context $ \k args i ->
132 if k == name
201 if k == name
133 then StringField <$> value args i
202 then StringField <$> value args i
134− else empty
203+ else noResult $ "Tried function field " ++ name
135
204
136
205
137--------------------------------------------------------------------------------
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+--
138mapContext :: (String -> String) -> Context a -> Context a
216mapContext :: (String -> String) -> Context a -> Context a
139mapContext f (Context c) = Context $ \k a i -> do
217mapContext f (Context c) = Context $ \k a i -> do
140 fld <- c k a i
218 fld <- c k a i
141 case fld of
219 case fld of
220+ EmptyField -> wrongType "boolField"
142 StringField str -> return $ StringField (f str)
221 StringField str -> return $ StringField (f str)
143− ListField _ _ -> fail $
222+ _ -> wrongType "ListField"
223+ where
144− "Hakyll.Web.Template.Context.mapContext: " ++
224+ wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
145− "can't map over a ListField!"
225+ "can't map over a " ++ typ ++ "!"
146
226
147--------------------------------------------------------------------------------
227--------------------------------------------------------------------------------
148-- | A context that allows snippet inclusion. In processed file, use as:
228-- | A context that allows snippet inclusion. In processed file, use as:
@@ -151,15 +231,15 @@ mapContext f (Context c) = Context $ \k a i -> do
151-- > $snippet("path/to/snippet/")$
231-- > $snippet("path/to/snippet/")$
152-- > ...
232-- > ...
153--
233--
154−-- The contents of the included file will not be interpolated.
234+-- The contents of the included file will not be interpolated like @partial@
235+-- does it.
155--
236--
156snippetField :: Context String
237snippetField :: Context String
157snippetField = functionField "snippet" f
238snippetField = functionField "snippet" f
158 where
239 where
159 f [contentsPath] _ = loadBody (fromFilePath contentsPath)
240 f [contentsPath] _ = loadBody (fromFilePath contentsPath)
160− f _ i = error $
241+ f [] _ = fail "No argument to function 'snippet()'"
161− "Too many arguments to function 'snippet()' in item " ++
242+ f _ _ = fail "Too many arguments to function 'snippet()'"
162− show (itemIdentifier i)
163
243
164--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
165-- | A context that contains (in that order)
245-- | A context that contains (in that order)
@@ -179,8 +259,7 @@ defaultContext =
179 metadataField `mappend`
259 metadataField `mappend`
180 urlField "url" `mappend`
260 urlField "url" `mappend`
181 pathField "path" `mappend`
261 pathField "path" `mappend`
182− titleField "title" `mappend`
262+ titleField "title"
183− missingField
184
263
185
264
186--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
@@ -198,15 +277,20 @@ bodyField key = field key $ return . itemBody
198-- | Map any field to its metadata value, if present
277-- | Map any field to its metadata value, if present
199metadataField :: Context a
278metadataField :: Context a
200metadataField = Context $ \k _ i -> do
279metadataField = Context $ \k _ i -> do
201− value <- getMetadataField (itemIdentifier i) k
280+ let id = itemIdentifier i
281+ empty' = noResult $ "No '" ++ k ++ "' field in metadata " ++
282+ "of item " ++ show id
283+ value <- getMetadataField id k
202− maybe empty (return . StringField) value
284+ maybe empty' (return . StringField) value
203
285
204
286
205--------------------------------------------------------------------------------
287--------------------------------------------------------------------------------
206-- | Absolute url to the resulting item
288-- | Absolute url to the resulting item
207urlField :: String -> Context a
289urlField :: String -> Context a
208−urlField key = field key $
290+urlField key = field key $ \i -> do
291+ let id = itemIdentifier i
292+ empty' = fail $ "No route url found for item " ++ show id
209− fmap (maybe empty toUrl) . getRoute . itemIdentifier
293+ fmap (maybe empty' toUrl) $ getRoute id
210
294
211
295
212--------------------------------------------------------------------------------
296--------------------------------------------------------------------------------
@@ -257,6 +341,12 @@ titleField = mapContext takeBaseName . pathField
257-- that begins with @yyyy-mm-dd@ . For example:
341-- that begins with @yyyy-mm-dd@ . For example:
258-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
342-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
259-- In case of multiple matches, the rightmost one is used.
343-- In case of multiple matches, the rightmost one is used.
344+--
345+-- As another alternative, if none of the above matches, and the file has a
346+-- path which contains nested directories specifying a date, then that date
347+-- will be used. In other words, if the path is of the form
348+-- @**//yyyy//mm//dd//**//main.extension@ .
349+-- As above, in case of multiple matches, the rightmost one is used.
260
350
261dateField :: String -- ^ Key in which the rendered date should be placed
351dateField :: String -- ^ Key in which the rendered date should be placed
262 -> String -- ^ Format to use on the date
352 -> String -- ^ Format to use on the date
@@ -267,7 +357,7 @@ dateField = dateFieldWith defaultTimeLocale
267--------------------------------------------------------------------------------
357--------------------------------------------------------------------------------
268-- | This is an extended version of 'dateField' that allows you to
358-- | This is an extended version of 'dateField' that allows you to
269-- specify a time locale that is used for outputting the date. For more
359-- specify a time locale that is used for outputting the date. For more
270−-- details, see 'dateField'.
360+-- details, see 'dateField' and 'formatTime'.
271dateFieldWith :: TimeLocale -- ^ Output time locale
361dateFieldWith :: TimeLocale -- ^ Output time locale
272 -> String -- ^ Destination key
362 -> String -- ^ Destination key
273 -> String -- ^ Format to use on the date
363 -> String -- ^ Format to use on the date
@@ -288,12 +378,13 @@ getItemUTC :: MonadMetadata m
288getItemUTC locale id' = do
378getItemUTC locale id' = do
289 metadata <- getMetadata id'
379 metadata <- getMetadata id'
290 let tryField k fmt = lookupString k metadata >>= parseTime' fmt
380 let tryField k fmt = lookupString k metadata >>= parseTime' fmt
291− paths = splitDirectories $ toFilePath id'
381+ paths = splitDirectories $ (dropExtension . toFilePath) id'
292
382
293 maybe empty' return $ msum $
383 maybe empty' return $ msum $
294 [tryField "published" fmt | fmt <- formats] ++
384 [tryField "published" fmt | fmt <- formats] ++
295 [tryField "date" fmt | fmt <- formats] ++
385 [tryField "date" fmt | fmt <- formats] ++
296− [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths]
386+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths] ++
387+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ fnCand | fnCand <- map (take 3) $ reverse . tails $ paths]
297 where
388 where
298 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
389 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
299 "could not parse time for " ++ show id'
390 "could not parse time for " ++ show id'
@@ -321,13 +412,16 @@ getItemModificationTime identifier = do
321
412
322
413
323--------------------------------------------------------------------------------
414--------------------------------------------------------------------------------
415+-- | Creates a field with the last modification date of the underlying item.
324modificationTimeField :: String -- ^ Key
416modificationTimeField :: String -- ^ Key
325 -> String -- ^ Format
417 -> String -- ^ Format
326− -> Context a -- ^ Resuting context
418+ -> Context a -- ^ Resulting context
327modificationTimeField = modificationTimeFieldWith defaultTimeLocale
419modificationTimeField = modificationTimeFieldWith defaultTimeLocale
328
420
329
421
330--------------------------------------------------------------------------------
422--------------------------------------------------------------------------------
423+-- | Creates a field with the last modification date of the underlying item
424+-- in a custom localisation format (see 'formatTime').
331modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
425modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
332 -> String -- ^ Key
426 -> String -- ^ Key
333 -> String -- ^ Format
427 -> String -- ^ Format
@@ -366,10 +460,11 @@ teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
366
460
367
461
368--------------------------------------------------------------------------------
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.
369missingField :: Context a
465missingField :: Context a
370−missingField = Context $ \k _ i -> fail $
466+missingField = Context $ \k _ _ -> noResult $
371− "Missing field $" ++ k ++ "$ in context for item " ++
467+ "Missing field '" ++ k ++ "' in context"
372− show (itemIdentifier i)
373
468
374parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
469parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
375#if MIN_VERSION_time(1,5,0)
470#if MIN_VERSION_time(1,5,0)
@@ -377,3 +472,10 @@ parseTimeM = TF.parseTimeM
377#else
472#else
378parseTimeM _ = TF.parseTime
473parseTimeM _ = TF.parseTime
379#endif
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
src/Hakyll/Web/Template/Internal.hs → lib/Hakyll/Web/Template/Internal.hsrenamed+103−45
@@ -1,15 +1,18 @@
1+{-# LANGUAGE DeriveAnyClass #-}
2+{-# LANGUAGE DeriveGeneric #-}
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE ScopedTypeVariables #-}
5+
3module Hakyll.Web.Template.Internal
6module Hakyll.Web.Template.Internal
4 ( Template (..)
7 ( Template (..)
5 , template
8 , template
6 , templateBodyCompiler
9 , templateBodyCompiler
7 , templateCompiler
10 , templateCompiler
8 , applyTemplate
11 , applyTemplate
9− , applyTemplate'
10 , loadAndApplyTemplate
12 , loadAndApplyTemplate
11 , applyAsTemplate
13 , applyAsTemplate
12 , readTemplate
14 , readTemplate
15+ , compileTemplateItem
13 , unsafeReadTemplateFile
16 , unsafeReadTemplateFile
14
17
15 , module Hakyll.Web.Template.Internal.Element
18 , module Hakyll.Web.Template.Internal.Element
@@ -18,16 +21,19 @@ module Hakyll.Web.Template.Internal
18
21
19
22
20--------------------------------------------------------------------------------
23--------------------------------------------------------------------------------
21−import Control.Monad.Except (MonadError (..))
24+import Control.Monad (forM)
25+import Control.Monad.Except (MonadError (..), catchError)
22import Data.Binary (Binary)
26import Data.Binary (Binary)
23import Data.List (intercalate)
27import Data.List (intercalate)
28+import qualified Data.List.NonEmpty as NonEmpty
24import Data.Typeable (Typeable)
29import Data.Typeable (Typeable)
25import GHC.Exts (IsString (..))
30import GHC.Exts (IsString (..))
26−import Prelude hiding (id)
31+import GHC.Generics (Generic)
27
32
28
33
29--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
30import Hakyll.Core.Compiler
35import Hakyll.Core.Compiler
36+import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Identifier
37import Hakyll.Core.Identifier
32import Hakyll.Core.Item
38import Hakyll.Core.Item
33import Hakyll.Core.Writable
39import Hakyll.Core.Writable
@@ -38,9 +44,10 @@ import Hakyll.Web.Template.Internal.Trim
38
44
39--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
40-- | Datatype used for template substitutions.
46-- | Datatype used for template substitutions.
41−newtype Template = Template
47+data Template = Template
42− { unTemplate :: [TemplateElement]
48+ { tplElements :: [TemplateElement]
49+ , tplOrigin :: FilePath -- Only for error messages.
43− } deriving (Show, Eq, Binary, Typeable)
50+ } deriving (Show, Eq, Generic, Binary, Typeable)
44
51
45
52
46--------------------------------------------------------------------------------
53--------------------------------------------------------------------------------
@@ -56,39 +63,68 @@ instance IsString Template where
56
63
57--------------------------------------------------------------------------------
64--------------------------------------------------------------------------------
58-- | Wrap the constructor to ensure trim is called.
65-- | Wrap the constructor to ensure trim is called.
59−template :: [TemplateElement] -> Template
66+template :: FilePath -> [TemplateElement] -> Template
60−template = Template . trim
67+template p = flip Template p . trim
61
68
62
69
63--------------------------------------------------------------------------------
70--------------------------------------------------------------------------------
71+-- | Parse a string into a template.
72+-- You should prefer 'compileTemplateItem' over this.
64readTemplate :: String -> Template
73readTemplate :: String -> Template
65−readTemplate = Template . trim . readTemplateElems
74+readTemplate = either error (template origin) . parseTemplateElemsFile origin
75+ where
76+ origin = "{literal}"
77+{-# DEPRECATED readTemplate "Use templateCompiler instead" #-}
78+
79+--------------------------------------------------------------------------------
80+-- | Parse an item body into a template.
81+-- Provides useful error messages in the 'Compiler' monad.
82+compileTemplateItem :: Item String -> Compiler Template
83+compileTemplateItem item = let file = itemIdentifier item
84+ in compileTemplateFile file (itemBody item)
85+
86+--------------------------------------------------------------------------------
87+compileTemplateFile :: Identifier -> String -> Compiler Template
88+compileTemplateFile file = either fail (return . template origin)
89+ . parseTemplateElemsFile origin
90+ where
91+ origin = show file
66
92
67--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
68-- | Read a template, without metadata header
94-- | Read a template, without metadata header
69templateBodyCompiler :: Compiler (Item Template)
95templateBodyCompiler :: Compiler (Item Template)
70templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
96templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
71 item <- getResourceBody
97 item <- getResourceBody
72− file <- getResourceFilePath
98+ file <- getUnderlying
73− return $ fmap (template . readTemplateElemsFile file) item
99+ withItemBody (compileTemplateFile file) item
74
100
75--------------------------------------------------------------------------------
101--------------------------------------------------------------------------------
76-- | Read complete file contents as a template
102-- | Read complete file contents as a template
77templateCompiler :: Compiler (Item Template)
103templateCompiler :: Compiler (Item Template)
78templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
104templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
79 item <- getResourceString
105 item <- getResourceString
80− file <- getResourceFilePath
106+ file <- getUnderlying
81− return $ fmap (template . readTemplateElemsFile file) item
107+ withItemBody (compileTemplateFile file) item
82
108
83
109
84--------------------------------------------------------------------------------
110--------------------------------------------------------------------------------
111+-- | Interpolate template expressions from context values in a page
85applyTemplate :: Template -- ^ Template
112applyTemplate :: Template -- ^ Template
86 -> Context a -- ^ Context
113 -> Context a -- ^ Context
87 -> Item a -- ^ Page
114 -> Item a -- ^ Page
88 -> Compiler (Item String) -- ^ Resulting item
115 -> Compiler (Item String) -- ^ Resulting item
89applyTemplate tpl context item = do
116applyTemplate tpl context item = do
90− body <- applyTemplate' (unTemplate tpl) context item
117+ body <- applyTemplate' (tplElements tpl) context item `catchError` handler
91 return $ itemSetBody body item
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+
92
128
93
129
94--------------------------------------------------------------------------------
130--------------------------------------------------------------------------------
@@ -105,9 +141,6 @@ applyTemplate' tes context x = go tes
105
141
106 go = fmap concat . mapM applyElem
142 go = fmap concat . mapM applyElem
107
143
108− trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
109− "fully trimmed."
110−
111 ---------------------------------------------------------------------------
144 ---------------------------------------------------------------------------
112
145
113 applyElem :: TemplateElement -> Compiler String
146 applyElem :: TemplateElement -> Compiler String
@@ -118,29 +151,43 @@ applyTemplate' tes context x = go tes
118
151
119 applyElem (Chunk c) = return c
152 applyElem (Chunk c) = return c
120
153
121− applyElem (Expr e) = applyExpr e >>= getString e
154+ applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)
155+ where
156+ evalMsg = "In expr '$" ++ show e ++ "$'"
157+ typeMsg = "expr '$" ++ show e ++ "$'"
122
158
123 applyElem Escaped = return "$"
159 applyElem Escaped = return "$"
124
160
125− applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
161+ applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
126 where
162 where
127− handler _ = case mf of
163+ f = maybe (return "") go mf
128− Nothing -> return ""
164+ handle (Right _) = go t
129− Just f -> go f
165+ handle (Left (CompilationNoResult _)) = f
130−
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+
131− applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
170+ applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of
132− StringField _ -> fail $
171+ EmptyField -> expected "list" "boolean" typeMsg
133− "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
172+ StringField _ -> expected "list" "string" typeMsg
134− "got StringField for expr " ++ show e
135− ListField c xs -> do
173+ ListField c xs -> withErrorMessage bodyMsg $ do
136 sep <- maybe (return "") go s
174 sep <- maybe (return "") go s
137− bs <- mapM (applyTemplate' b c) xs
175+ bs <- forM xs $ applyTemplate' b $ c <> bindItem context x
138 return $ intercalate sep bs
176 return $ intercalate sep bs
139−
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+
140− applyElem (Partial e) = do
182+ applyElem (Partial e) = withErrorMessage headMsg $
141− p <- applyExpr e >>= getString e
183+ applyStringExpr typeMsg e >>= \p ->
184+ withErrorMessage inclMsg $ do
142− Template tpl' <- loadBody (fromFilePath p)
185+ tpl' <- loadBody (fromFilePath p)
143− applyTemplate' tpl' context x
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 ++ ")$'"
144
191
145 ---------------------------------------------------------------------------
192 ---------------------------------------------------------------------------
146
193
@@ -149,17 +196,29 @@ applyTemplate' tes context x = go tes
149 applyExpr (Ident (TemplateKey k)) = context' k [] x
196 applyExpr (Ident (TemplateKey k)) = context' k [] x
150
197
151 applyExpr (Call (TemplateKey k) args) = do
198 applyExpr (Call (TemplateKey k) args) = do
152− args' <- mapM (\e -> applyExpr e >>= getString e) args
199+ args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
153 context' k args' x
200 context' k args' x
201+ where
202+ typeMsg e = "argument '" ++ show e ++ "'"
154
203
155 applyExpr (StringLiteral s) = return (StringField s)
204 applyExpr (StringLiteral s) = return (StringField s)
156
205
157 ----------------------------------------------------------------------------
206 ----------------------------------------------------------------------------
158
207
208+ applyStringExpr :: String -> TemplateExpr -> Compiler String
209+ applyStringExpr msg expr =
210+ applyExpr expr >>= getString
211+ where
212+ getString EmptyField = expected "string" "boolean" msg
159− getString _ (StringField s) = return s
213+ getString (StringField s) = return s
160− getString e (ListField _ _) = fail $
214+ getString (ListField _ _) = expected "string" "list" msg
161− "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
215+
162− "got ListField for expr " ++ show e
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."
163
222
164
223
165--------------------------------------------------------------------------------
224--------------------------------------------------------------------------------
@@ -189,15 +248,14 @@ loadAndApplyTemplate identifier context item = do
189applyAsTemplate :: Context String -- ^ Context
248applyAsTemplate :: Context String -- ^ Context
190 -> Item String -- ^ Item and template
249 -> Item String -- ^ Item and template
191 -> Compiler (Item String) -- ^ Resulting item
250 -> Compiler (Item String) -- ^ Resulting item
192−applyAsTemplate context item =
251+applyAsTemplate context item = do
193− let tpl = template $ readTemplateElemsFile file (itemBody item)
252+ tpl <- compileTemplateItem item
194− file = toFilePath $ itemIdentifier item
195− in applyTemplate tpl context item
253+ applyTemplate tpl context item
196
254
197
255
198--------------------------------------------------------------------------------
256--------------------------------------------------------------------------------
199unsafeReadTemplateFile :: FilePath -> Compiler Template
257unsafeReadTemplateFile :: FilePath -> Compiler Template
200unsafeReadTemplateFile file = do
258unsafeReadTemplateFile file = do
201 tpl <- unsafeCompiler $ readFile file
259 tpl <- unsafeCompiler $ readFile file
202− pure $ template $ readTemplateElemsFile file tpl
260+ compileTemplateFile (fromFilePath file) tpl
203−
261+{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
src/Hakyll/Web/Template/Internal/Element.hs → lib/Hakyll/Web/Template/Internal/Element.hsrenamed+7−14
@@ -7,14 +7,14 @@ module Hakyll.Web.Template.Internal.Element
7 , TemplateExpr (..)
7 , TemplateExpr (..)
8 , TemplateElement (..)
8 , TemplateElement (..)
9 , templateElems
9 , templateElems
10− , readTemplateElems
10+ , parseTemplateElemsFile
11− , readTemplateElemsFile
12 ) where
11 ) where
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16−import Control.Applicative ((<|>))
15+import Control.Applicative ((<|>), (<*))
17import Control.Monad (void)
16import Control.Monad (void)
17+import Control.Arrow (left)
18import Data.Binary (Binary, get, getWord8, put, putWord8)
18import Data.Binary (Binary, get, getWord8, put, putWord8)
19import Data.List (intercalate)
19import Data.List (intercalate)
20import Data.Maybe (isJust)
20import Data.Maybe (isJust)
@@ -107,17 +107,10 @@ instance Binary TemplateExpr where
107 2 -> StringLiteral <$> get
107 2 -> StringLiteral <$> get
108 _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
108 _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109
109
110−
111−--------------------------------------------------------------------------------
112−readTemplateElems :: String -> [TemplateElement]
113−readTemplateElems = readTemplateElemsFile "{literal}"
114−
115−
116--------------------------------------------------------------------------------
110--------------------------------------------------------------------------------
117−readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
111+parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
118−readTemplateElemsFile file input = case P.parse templateElems file input of
119− Left err -> error $ "Cannot parse template: " ++ show err
112+parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
120− Right t -> t
113+ . P.parse (templateElems <* P.eof) file
121
114
122
115
123--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
@@ -167,7 +160,7 @@ trimOpen = do
167--------------------------------------------------------------------------------
160--------------------------------------------------------------------------------
168trimClose :: P.Parser Bool
161trimClose :: P.Parser Bool
169trimClose = do
162trimClose = do
170− trimIfR <- P.optionMaybe $ P.try (P.char '-')
163+ trimIfR <- P.optionMaybe $ (P.char '-')
171 void $ P.char '$'
164 void $ P.char '$'
172 pure $ isJust trimIfR
165 pure $ isJust trimIfR
173
166
src/Hakyll/Web/Template/Internal/Trim.hs → lib/Hakyll/Web/Template/Internal/Trim.hsrenamed

The content is the same.

src/Hakyll/Web/Template/List.hs → lib/Hakyll/Web/Template/List.hsrenamed

The content is the same.

src/Hakyll/Main.hsdeleted−110
@@ -1,110 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module providing the main hakyll function and command-line argument parsing
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveDataTypeable #-}
5−module Hakyll.Main
6− ( hakyll
7− , hakyllWith
8− , hakyllWithExitCode
9− ) where
10−
11−
12−--------------------------------------------------------------------------------
13−import System.Environment (getProgName)
14−import System.IO.Unsafe (unsafePerformIO)
15−import System.Exit (ExitCode(ExitSuccess), exitWith)
16−
17−
18−--------------------------------------------------------------------------------
19−import Data.Monoid ((<>))
20−import Options.Applicative
21−
22−
23−--------------------------------------------------------------------------------
24−import qualified Hakyll.Check as Check
25−import qualified Hakyll.Commands as Commands
26−import qualified Hakyll.Core.Configuration as Config
27−import qualified Hakyll.Core.Logger as Logger
28−import Hakyll.Core.Rules
29−
30−
31−--------------------------------------------------------------------------------
32−-- | This usualy is the function with which the user runs the hakyll compiler
33−hakyll :: Rules a -> IO ()
34−hakyll = hakyllWith Config.defaultConfiguration
35−
36−--------------------------------------------------------------------------------
37−-- | A variant of 'hakyll' which allows the user to specify a custom
38−-- configuration
39−hakyllWith :: Config.Configuration -> Rules a -> IO ()
40−hakyllWith conf rules = hakyllWithExitCode conf rules >>= exitWith
41−
42−hakyllWithExitCode :: Config.Configuration -> Rules a -> IO ExitCode
43−hakyllWithExitCode conf rules = do
44− args' <- customExecParser (prefs showHelpOnError) (info (helper <*> optionParser conf) (fullDesc <> progDesc (progName ++ " - Static site compiler created with Hakyll")))
45− let args'' = optCommand args'
46−
47− let verbosity' = if verbosity args' then Logger.Debug else Logger.Message
48− check' =
49− if internal_links args'' then Check.InternalLinks else Check.All
50−
51− logger <- Logger.new verbosity'
52−
53− case args'' of
54− Build -> Commands.build conf logger rules
55− Check _ -> Commands.check conf logger check'
56− Clean -> Commands.clean conf logger >> ok
57− Deploy -> Commands.deploy conf
58− Preview p -> Commands.preview conf logger rules p >> ok
59− Rebuild -> Commands.rebuild conf logger rules
60− Server _ _ -> Commands.server conf logger (host args'') (port args'') >> ok
61− Watch _ p s -> Commands.watch conf logger (host args'') p (not s) rules >> ok
62− where
63− ok = return ExitSuccess
64−
65−
66−--------------------------------------------------------------------------------
67−
68−data Options = Options {verbosity :: Bool, optCommand :: Command}
69− deriving (Show)
70−
71−data Command
72− = Build
73− | Check {internal_links :: Bool}
74− | Clean
75− | Deploy
76− | Preview {port :: Int}
77− | Rebuild
78− | Server {host :: String, port :: Int}
79− | Watch {host :: String, port :: Int, no_server :: Bool }
80− deriving (Show)
81−
82−optionParser :: Config.Configuration -> Parser Options
83−optionParser conf = Options <$> verboseParser <*> (commandParser conf)
84− where
85− verboseParser = switch (long "verbose" <> short 'v' <> help "Run in verbose mode")
86−
87−
88−commandParser :: Config.Configuration -> Parser Command
89−commandParser conf = subparser $ foldr ((<>) . produceCommand) mempty commands
90− where
91− produceCommand (a,b) = command a (info (helper <*> (fst b)) (snd b))
92− portParser = option auto (long "port" <> help "Port to listen on" <> value (Config.previewPort conf))
93− hostParser = strOption (long "host" <> help "Host to bind on" <> value (Config.previewHost conf))
94− commands = [
95− ("build",(pure Build,fullDesc <> progDesc "Generate the site")),
96− ("check",(pure Check <*> switch (long "internal-links" <> help "Check internal links only"), fullDesc <> progDesc "Validate the site output")),
97− ("clean",(pure Clean,fullDesc <> progDesc "Clean up and remove cache")),
98− ("deploy",(pure Deploy,fullDesc <> progDesc "Upload/deploy your site")),
99− ("preview",(pure Preview <*> portParser,fullDesc <> progDesc "[DEPRECATED] Please use the watch command")),
100− ("rebuild",(pure Rebuild,fullDesc <> progDesc "Clean and build again")),
101− ("server",(pure Server <*> hostParser <*> portParser,fullDesc <> progDesc "Start a preview server")),
102− ("watch",(pure Watch <*> hostParser <*> portParser <*> switch (long "no-server" <> help "Disable the built-in web server"),fullDesc <> progDesc "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server."))
103− ]
104−
105−
106−--------------------------------------------------------------------------------
107−-- | This is necessary because not everyone calls their program the same...
108−progName :: String
109−progName = unsafePerformIO getProgName
110−{-# NOINLINE progName #-}
src/Hakyll/Web/CompressCss.hsdeleted−85
@@ -1,85 +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.List (isPrefixOf)
12−
13−
14−--------------------------------------------------------------------------------
15−import Hakyll.Core.Compiler
16−import Hakyll.Core.Item
17−
18−
19−--------------------------------------------------------------------------------
20−-- | Compiler form of 'compressCss'
21−compressCssCompiler :: Compiler (Item String)
22−compressCssCompiler = fmap compressCss <$> getResourceString
23−
24−
25−--------------------------------------------------------------------------------
26−-- | Compress CSS to speed up your site.
27−compressCss :: String -> String
28−compressCss = compressSeparators . stripComments . compressWhitespace
29−
30−
31−--------------------------------------------------------------------------------
32−-- | Compresses certain forms of separators.
33−compressSeparators :: String -> String
34−compressSeparators [] = []
35−compressSeparators str
36− | isConstant = head str : retainConstants compressSeparators (head str) (drop 1 str)
37− | stripFirst = compressSeparators (drop 1 str)
38− | stripSecond = compressSeparators (head str : (drop 2 str))
39− | otherwise = head str : compressSeparators (drop 1 str)
40− where
41− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
42− stripFirst = or $ map (isOfPrefix str) [" ", " {", " }", " :", ";;", ";}"]
43− stripSecond = or $ map (isOfPrefix str) ["{ ", "} ", ": ", "; "]
44−
45−--------------------------------------------------------------------------------
46−-- | Compresses all whitespace.
47−compressWhitespace :: String -> String
48−compressWhitespace [] = []
49−compressWhitespace str
50− | isConstant = head str : retainConstants compressWhitespace (head str) (drop 1 str)
51− | replaceOne = compressWhitespace (' ' : (drop 1 str))
52− | replaceTwo = compressWhitespace (' ' : (drop 2 str))
53− | otherwise = head str : compressWhitespace (drop 1 str)
54− where
55− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
56− replaceOne = or $ map (isOfPrefix str) ["\t", "\n", "\r"]
57− replaceTwo = or $ map (isOfPrefix str) [" \t", " \n", " \r", " "]
58−
59−--------------------------------------------------------------------------------
60−-- | Function that strips CSS comments away.
61−stripComments :: String -> String
62−stripComments [] = []
63−stripComments str
64− | isConstant = head str : retainConstants stripComments (head str) (drop 1 str)
65− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
66− | otherwise = head str : stripComments (drop 1 str)
67− where
68− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
69− eatComments str'
70− | null str' = []
71− | isPrefixOf "*/" str' = drop 2 str'
72− | otherwise = eatComments $ drop 1 str'
73−
74−--------------------------------------------------------------------------------
75−-- | Helper function to handle string constants correctly.
76−retainConstants :: (String -> String) -> Char -> String -> String
77−retainConstants f delim str
78− | null str = []
79− | isPrefixOf [delim] str = head str : f (drop 1 str)
80− | otherwise = head str : retainConstants f delim (drop 1 str)
81−
82−--------------------------------------------------------------------------------
83−-- | Helper function to determine whether a string is a substring.
84−isOfPrefix :: String -> String -> Bool
85−isOfPrefix = flip isPrefixOf
src/Hakyll/Init.hs → src/Init.hsrenamed+44−17
@@ -6,12 +6,12 @@ module Main
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8import Control.Arrow (first)
8import Control.Arrow (first)
9−import Control.Monad (forM_)
9+import Control.Monad (forM, forM_)
10import Data.Char (isAlphaNum, isNumber)
10import Data.Char (isAlphaNum, isNumber)
11−import Data.List (foldl')
11+import Data.List (foldl', intercalate, isPrefixOf)
12−import Data.List (intercalate, isPrefixOf)
13import Data.Version (Version (..))
12import Data.Version (Version (..))
14−import System.Directory (canonicalizePath, copyFile)
13+import System.Directory (canonicalizePath, copyFile,
14+ doesFileExist)
15import System.Environment (getArgs, getProgName)
15import System.Environment (getArgs, getProgName)
16import System.Exit (exitFailure)
16import System.Exit (exitFailure)
17import System.FilePath (splitDirectories, (</>))
17import System.FilePath (splitDirectories, (</>))
@@ -22,6 +22,10 @@ import Hakyll.Core.Util.File
22import Paths_hakyll
22import Paths_hakyll
23
23
24
24
25+--------------------------------------------------------------------------------
26+import Prelude
27+
28+
25--------------------------------------------------------------------------------
29--------------------------------------------------------------------------------
26main :: IO ()
30main :: IO ()
27main = do
31main = do
@@ -37,21 +41,44 @@ main = do
37 -- If dstDir begins with hyphens, the guard will prevent it from creating
41 -- If dstDir begins with hyphens, the guard will prevent it from creating
38 -- directory with that name so we can fall to the second alternative
42 -- directory with that name so we can fall to the second alternative
39 -- which prints a usage info for user.
43 -- which prints a usage info for user.
40− [dstDir] | not ("-" `isPrefixOf` dstDir) -> do
44+ [dstDir] | not ("-" `isPrefixOf` dstDir) ->
41− forM_ files $ \file -> do
45+ createFiles False srcDir files dstDir
42− let dst = dstDir </> file
46+ ["-f", dstDir] ->
43− src = srcDir </> file
47+ createFiles True srcDir files dstDir
44− putStrLn $ "Creating " ++ dst
48+ _ -> do
45− makeDirectories dst
49+ putStrLn $ "Usage: " ++ progName ++ "[-f] <directory>"
46− copyFile src dst
50+ exitFailure
47
51
52+ where
53+ createFiles force srcDir files dstDir = do
48 name <- makeName dstDir
54 name <- makeName dstDir
49 let cabalPath = dstDir </> name ++ ".cabal"
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
50− putStrLn $ "Creating " ++ cabalPath
65+ putStrLn $ "Creating " ++ dst
66+ makeDirectories dst
67+ copyFile src dst
68+
69+ putStrLn $ "Creating " ++ cabalPath
51− createCabal cabalPath name
70+ createCabal cabalPath name
52− _ -> do
71+ fs -> do
53− putStrLn $ "Usage: " ++ progName ++ " <directory>"
72+ putStrLn $ "The following files will be overwritten:"
73+ mapM_ putStrLn fs
74+ putStrLn $ "Use -f to overwrite them"
54− exitFailure
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 []
55
82
56-- | Figure out a good cabal package name from the given (existing) directory
83-- | Figure out a good cabal package name from the given (existing) directory
57-- name
84-- name
@@ -77,7 +104,7 @@ makeName dstDir = do
77 safeLast = foldl' (\_ x -> Just x) Nothing
104 safeLast = foldl' (\_ x -> Just x) Nothing
78
105
79createCabal :: FilePath -> String -> IO ()
106createCabal :: FilePath -> String -> IO ()
80−createCabal path name = do
107+createCabal path name =
81 writeFile path $ unlines [
108 writeFile path $ unlines [
82 "name: " ++ name
109 "name: " ++ name
83 , "version: 0.1.0.0"
110 , "version: 0.1.0.0"
stack.yaml+27−7
@@ -1,13 +1,33 @@
1−resolver: nightly-2016-10-31
1+resolver: lts-13.29
2−extra-package-dbs: []
2+save-hackage-creds: false
3−extra-deps:
4− - 'QuickCheck-2.9.2'
5
3
6flags:
4flags:
7 hakyll:
5 hakyll:
8− previewServer: True
6+ previewServer: true
9− watchServer: True
7+ watchServer: true
10− checkExternal: True
8+ checkExternal: true
9+ usePandoc: true
10+ buildWebsite: true
11
11
12packages:
12packages:
13 - '.'
13 - '.'
14+
15+nix:
16+ enable: false
17+ packages:
18+ - zlib
19+
20+build:
21+ haddock: true
22+ haddock-hyperlink-source: true
23+ haddock-deps: false
24+
25+extra-deps:
26+- 'cmark-gfm-0.2.0'
27+- 'hslua-module-system-0.2.1'
28+- 'ipynb-0.1'
29+- 'lrucache-1.2.0.1'
30+- 'pandoc-2.7.3'
31+- 'pandoc-citeproc-0.16.2'
32+- 'skylighting-0.8.2'
33+- 'skylighting-core-0.8.2'
test.hsnew file+30
@@ -0,0 +1,30 @@
1+{-# LANGUAGE BangPatterns #-}
2+import Control.Monad (forM)
3+import qualified Crypto.Hash.SHA256 as SHA256
4+import qualified Data.ByteString.Base16 as Base16
5+import qualified Data.ByteString.Char8 as BS8
6+import qualified Data.ByteString.Lazy as BSL
7+import Data.Map (Map)
8+import qualified Data.Map as Map
9+import Hakyll
10+import System.FilePath ((</>))
11+
12+type FileHashes = Map Identifier String
13+
14+mkFileHashes :: FilePath -> IO FileHashes
15+mkFileHashes dir = do
16+ allFiles <- getRecursiveContents (\_ -> return False) dir
17+ fmap Map.fromList $ forM allFiles $ \path0 -> do
18+ let path1 = dir </> path0
19+ !h <- hash path1
20+ return (fromFilePath path1, h)
21+ where
22+ hash :: FilePath -> IO String
23+ hash fp = do
24+ !h <- SHA256.hashlazy <$> BSL.readFile fp
25+ return $! BS8.unpack $! Base16.encode h
26+
27+main :: IO ()
28+main = hakyll $ do
29+ fileHashes <- preprocess (mkFileHashes "images")
30+ undefined
tests/Hakyll/Core/Identifier/Tests.hs+1
@@ -42,6 +42,7 @@ captureTests = fromAssertions "capture"
42 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
42 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
43 , Just [] @=? capture "\\*.jpg" "*.jpg"
43 , Just [] @=? capture "\\*.jpg" "*.jpg"
44 , Nothing @=? capture "\\*.jpg" "foo.jpg"
44 , Nothing @=? capture "\\*.jpg" "foo.jpg"
45+ , Just ["xyz","42"] @=? capture (fromRegex "cat-([a-z]+)/foo([0-9]+).jpg") "cat-xyz/foo42.jpg"
45 ]
46 ]
46
47
47
48
tests/Hakyll/Core/Provider/Tests.hs+2−2
@@ -9,7 +9,7 @@ module Hakyll.Core.Provider.Tests
9import Hakyll.Core.Metadata
9import Hakyll.Core.Metadata
10import Hakyll.Core.Provider
10import Hakyll.Core.Provider
11import Test.Tasty (TestTree, testGroup)
11import Test.Tasty (TestTree, testGroup)
12−import Test.Tasty.HUnit (Assertion, assert, testCase, (@=?))
12+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
13import TestSuite.Util
13import TestSuite.Util
14
14
15
15
@@ -25,7 +25,7 @@ case01 :: Assertion
25case01 = do
25case01 = do
26 store <- newTestStore
26 store <- newTestStore
27 provider <- newTestProvider store
27 provider <- newTestProvider store
28− assert $ resourceExists provider "example.md"
28+ True @=? resourceExists provider "example.md"
29
29
30 metadata <- resourceMetadata provider "example.md"
30 metadata <- resourceMetadata provider "example.md"
31 Just "An example" @=? lookupString "title" metadata
31 Just "An example" @=? lookupString "title" metadata
tests/Hakyll/Core/Rules/Tests.hs+3−4
@@ -17,10 +17,9 @@ import Hakyll.Core.Metadata
17import Hakyll.Core.Routes
17import Hakyll.Core.Routes
18import Hakyll.Core.Rules
18import Hakyll.Core.Rules
19import Hakyll.Core.Rules.Internal
19import Hakyll.Core.Rules.Internal
20−import Hakyll.Web.Pandoc
21import System.FilePath ((</>))
20import System.FilePath ((</>))
22import Test.Tasty (TestTree, testGroup)
21import Test.Tasty (TestTree, testGroup)
23−import Test.Tasty.HUnit (Assertion, assert, (@=?))
22+import Test.Tasty.HUnit (Assertion, (@=?))
24import TestSuite.Util
23import TestSuite.Util
25
24
26
25
@@ -50,7 +49,7 @@ case01 = do
50 checkRoute "example.mv1" (sv "mv1" "example.md")
49 checkRoute "example.mv1" (sv "mv1" "example.md")
51 checkRoute "example.mv2" (sv "mv2" "example.md")
50 checkRoute "example.mv2" (sv "mv2" "example.md")
52 checkRoute "food/example.md" (sv "metadataMatch" "example.md")
51 checkRoute "food/example.md" (sv "metadataMatch" "example.md")
53− readIORef ioref >>= assert
52+ readIORef ioref >>= (True @=?)
54 cleanTestEnv
53 cleanTestEnv
55 where
54 where
56 sv g = setVersion (Just g)
55 sv g = setVersion (Just g)
@@ -75,7 +74,7 @@ rules01 ioref = do
75 -- Compile some posts
74 -- Compile some posts
76 match "*.md" $ do
75 match "*.md" $ do
77 route $ setExtension "html"
76 route $ setExtension "html"
78− compile pandocCompiler
77+ compile copyFileCompiler
79
78
80 -- Yeah. I don't know how else to test this stuff?
79 -- Yeah. I don't know how else to test this stuff?
81 preprocess $ writeIORef ioref True
80 preprocess $ writeIORef ioref True
tests/Hakyll/Core/Runtime/Tests.hs+1−1
@@ -39,7 +39,7 @@ case01 = do
39 compile $ do
39 compile $ do
40 getResourceBody
40 getResourceBody
41 >>= saveSnapshot "raw"
41 >>= saveSnapshot "raw"
42− >>= renderPandoc
42+ >>= renderParagraphs
43
43
44 match (fromList ["partial.html", "partial-helper.html"]) $
44 match (fromList ["partial.html", "partial-helper.html"]) $
45 compile templateCompiler
45 compile templateCompiler
tests/Hakyll/Core/Store/Tests.hs+7−7
@@ -63,11 +63,11 @@ wrongType = do
63 -- Store a string and try to fetch an int
63 -- Store a string and try to fetch an int
64 Store.set store ["foo", "bar"] ("qux" :: String)
64 Store.set store ["foo", "bar"] ("qux" :: String)
65 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
65 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
66− H.assert $ case value of
66+ case value of
67− Store.WrongType e t ->
67+ Store.WrongType e t -> do
68− e == typeOf (undefined :: Int) &&
68+ typeOf (undefined :: Int) H.@=? e
69− t == typeOf (undefined :: String)
69+ typeOf (undefined :: String) H.@=? t
70− _ -> False
70+ _ -> H.assertFailure "Expecting WrongType"
71 cleanTestEnv
71 cleanTestEnv
72
72
73
73
@@ -78,6 +78,6 @@ isMembertest = do
78 Store.set store ["foo", "bar"] ("qux" :: String)
78 Store.set store ["foo", "bar"] ("qux" :: String)
79 good <- Store.isMember store ["foo", "bar"]
79 good <- Store.isMember store ["foo", "bar"]
80 bad <- Store.isMember store ["foo", "baz"]
80 bad <- Store.isMember store ["foo", "baz"]
81− H.assert good
81+ True H.@=? good
82− H.assert (not bad)
82+ False H.@=? bad
83 cleanTestEnv
83 cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hs+22−12
@@ -6,15 +6,14 @@ module Hakyll.Core.UnixFilter.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Data.List (isInfixOf)
10−import Test.Tasty (TestTree, testGroup)
9+import Test.Tasty (TestTree, testGroup)
11−import Test.Tasty.HUnit (testCase)
10+import Test.Tasty.HUnit (testCase)
12−import qualified Test.Tasty.HUnit as H
11+import qualified Test.Tasty.HUnit as H
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16import Hakyll.Core.Compiler
15import Hakyll.Core.Compiler
17−import Hakyll.Core.Compiler.Internal
16+import Hakyll.Core.Identifier
18import Hakyll.Core.Item
17import Hakyll.Core.Item
19import Hakyll.Core.UnixFilter
18import Hakyll.Core.UnixFilter
20import TestSuite.Util
19import TestSuite.Util
@@ -25,17 +24,20 @@ tests :: TestTree
25tests = testGroup "Hakyll.Core.UnixFilter.Tests"
24tests = testGroup "Hakyll.Core.UnixFilter.Tests"
26 [ testCase "unixFilter rev" unixFilterRev
25 [ testCase "unixFilter rev" unixFilterRev
27 , testCase "unixFilter false" unixFilterFalse
26 , testCase "unixFilter false" unixFilterFalse
27+ , testCase "unixFilter error" unixFilterError
28 ]
28 ]
29
29
30+testMarkdown :: Identifier
31+testMarkdown = "russian.md"
30
32
31--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
32unixFilterRev :: H.Assertion
34unixFilterRev :: H.Assertion
33unixFilterRev = do
35unixFilterRev = do
34 store <- newTestStore
36 store <- newTestStore
35 provider <- newTestProvider store
37 provider <- newTestProvider store
36− output <- testCompilerDone store provider "russian.md" compiler
38+ output <- testCompilerDone store provider testMarkdown compiler
37− expected <- testCompilerDone store provider "russian.md" getResourceString
39+ expected <- testCompilerDone store provider testMarkdown getResourceString
38− H.assert $ rev (itemBody expected) == lines (itemBody output)
40+ rev (itemBody expected) H.@=? lines (itemBody output)
39 cleanTestEnv
41 cleanTestEnv
40 where
42 where
41 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
43 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
@@ -47,10 +49,18 @@ unixFilterFalse :: H.Assertion
47unixFilterFalse = do
49unixFilterFalse = do
48 store <- newTestStore
50 store <- newTestStore
49 provider <- newTestProvider store
51 provider <- newTestProvider store
50− result <- testCompiler store provider "russian.md" compiler
52+ testCompilerError store provider testMarkdown compiler "exit code"
51− H.assert $ case result of
52− CompilerError es -> any ("exit code" `isInfixOf`) es
53− _ -> False
54 cleanTestEnv
53 cleanTestEnv
55 where
54 where
56 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
55 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
56+
57+
58+--------------------------------------------------------------------------------
59+unixFilterError :: H.Assertion
60+unixFilterError = do
61+ store <- newTestStore
62+ provider <- newTestProvider store
63+ testCompilerError store provider testMarkdown compiler "option"
64+ cleanTestEnv
65+ where
66+ compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])
tests/Hakyll/Web/CompressCss/Tests.hs+18−13
@@ -5,9 +5,8 @@ module Hakyll.Web.CompressCss.Tests
5
5
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8−import Data.Char (toUpper)
9import Test.Tasty (TestTree, testGroup)
8import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (assert, (@=?))
9+import Test.Tasty.HUnit ((@=?))
11
10
12
11
13--------------------------------------------------------------------------------
12--------------------------------------------------------------------------------
@@ -21,9 +20,9 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
21 [ fromAssertions "compressCss"
20 [ fromAssertions "compressCss"
22 [
21 [
23 -- compress whitespace
22 -- compress whitespace
24− " something something " @=?
23+ "something something" @=?
25 compressCss " something \n\t\r something "
24 compressCss " something \n\t\r something "
26− -- do not compress whitespace in constants
25+ -- do not compress whitespace in string tokens
27 , "abc \" \t\n\r \" xyz" @=?
26 , "abc \" \t\n\r \" xyz" @=?
28 compressCss "abc \" \t\n\r \" xyz"
27 compressCss "abc \" \t\n\r \" xyz"
29 , "abc ' \t\n\r ' xyz" @=?
28 , "abc ' \t\n\r ' xyz" @=?
@@ -31,28 +30,34 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
31
30
32 -- strip comments
31 -- strip comments
33 , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
32 , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
34− -- don't strip comments inside constants
33+ -- don't strip comments inside string tokens
35 , "before \"/* abc { } ;; \n\t\r */\" after"
34 , "before \"/* abc { } ;; \n\t\r */\" after"
36 @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
35 @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
37
36
38 -- compress separators
37 -- compress separators
39 , "}" @=? compressCss "; }"
38 , "}" @=? compressCss "; }"
40− , "{};" @=? 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 )"
41 -- compress whitespace even after this curly brace
46 -- compress whitespace even after this curly brace
42 , "}" @=? compressCss "; } "
47 , "}" @=? compressCss "; } "
43− -- but do not compress separators inside of constants
48+ -- but do not compress separators inside string tokens
44− , "\" { } ; \"" @=? compressCss "\" { } ; \""
49+ , "\" { } ; , \"" @=? compressCss "\" { } ; , \""
45− -- don't compress separators at the start or end of constants
50+ -- don't compress separators at the start or end of string tokens
46 , "\" }\"" @=? compressCss "\" }\""
51 , "\" }\"" @=? compressCss "\" }\""
47 , "\"{ \"" @=? compressCss "\"{ \""
52 , "\"{ \"" @=? compressCss "\"{ \""
48− -- don't get irritated by the wrong constant terminator
53+ -- don't get irritated by the wrong token delimiter
49 , "\" ' \"" @=? compressCss "\" ' \""
54 , "\" ' \"" @=? compressCss "\" ' \""
50 , "' \" '" @=? compressCss "' \" '"
55 , "' \" '" @=? compressCss "' \" '"
51− -- don't compress whitespace around separators in constants in the middle of a string
56+ -- don't compress whitespace in the middle of a string
52 , "abc '{ '" @=? compressCss "abc '{ '"
57 , "abc '{ '" @=? compressCss "abc '{ '"
53 , "abc \"{ \"" @=? compressCss "abc \"{ \""
58 , "abc \"{ \"" @=? compressCss "abc \"{ \""
54− -- compress whitespace after colons
59+ -- compress whitespace after colons (but not before)
55− , "abc:xyz" @=? compressCss "abc : xyz"
60+ , "abc :xyz" @=? compressCss "abc : xyz"
56 -- compress multiple semicolons
61 -- compress multiple semicolons
57 , ";" @=? compressCss ";;;;;;;"
62 , ";" @=? compressCss ";;;;;;;"
58 ]
63 ]
tests/Hakyll/Web/Html/Tests.hs+6−6
@@ -7,7 +7,7 @@ module Hakyll.Web.Html.Tests
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8import Data.Char (toUpper)
8import Data.Char (toUpper)
9import Test.Tasty (TestTree, testGroup)
9import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (assert, (@=?))
10+import Test.Tasty.HUnit ((@=?))
11
11
12
12
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
@@ -64,11 +64,11 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
64 ]
64 ]
65
65
66 , fromAssertions "isExternal"
66 , fromAssertions "isExternal"
67− [ assert (isExternal "http://reddit.com")
67+ [ True @=? isExternal "http://reddit.com"
68− , assert (isExternal "https://mail.google.com")
68+ , True @=? isExternal "https://mail.google.com"
69− , assert (isExternal "//ajax.googleapis.com")
69+ , True @=? isExternal "//ajax.googleapis.com"
70− , assert (not (isExternal "../header.png"))
70+ , False @=? isExternal "../header.png"
71− , assert (not (isExternal "/foo/index.html"))
71+ , False @=? isExternal "/foo/index.html"
72 ]
72 ]
73
73
74 , fromAssertions "stripTags"
74 , fromAssertions "stripTags"
tests/Hakyll/Web/Template/Context/Tests.hs+40−3
@@ -13,16 +13,19 @@ import Test.Tasty.HUnit (Assertion, testCase, (@=?))
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
14import Hakyll.Core.Compiler
14import Hakyll.Core.Compiler
15import Hakyll.Core.Identifier
15import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
16import Hakyll.Core.Provider
17import Hakyll.Core.Provider
17import Hakyll.Core.Store (Store)
18import Hakyll.Core.Store (Store)
18import Hakyll.Web.Template.Context
19import Hakyll.Web.Template.Context
20+import Hakyll.Web.Template.Internal
19import TestSuite.Util
21import TestSuite.Util
20
22
21
23
22--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
23tests :: TestTree
25tests :: TestTree
24−tests = testGroup "Hakyll.Core.Template.Context.Tests"
26+tests = testGroup "Hakyll.Web.Template.Context.Tests"
25 [ testCase "testDateField" testDateField
27 [ testCase "testDateField" testDateField
28+ , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
26 ]
29 ]
27
30
28
31
@@ -41,6 +44,15 @@ testDateField = do
41 dateField "date" "%B %e, %Y"
44 dateField "date" "%B %e, %Y"
42 date2 @=? "August 26, 2010"
45 date2 @=? "August 26, 2010"
43
46
47+ date3 <- testContextDone store provider
48+ "posts/2018-09-26.md" "date" $
49+ dateField "date" "%B %e, %Y"
50+ date3 @=? "September 26, 2018"
51+
52+ date4 <- testContextDone store provider
53+ "posts/2019/05/10/tomorrow.md" "date" $
54+ dateField "date" "%B %e, %Y"
55+ date4 @=? "May 10, 2019"
44 cleanTestEnv
56 cleanTestEnv
45
57
46
58
@@ -53,6 +65,31 @@ testContextDone store provider identifier key context =
53 cf <- unContext context key [] item
65 cf <- unContext context key [] item
54 case cf of
66 case cf of
55 StringField str -> return str
67 StringField str -> return str
56− ListField _ _ -> error $
68+ _ -> error $
57 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
69 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
58− "Didn't expect ListField"
70+ "expected StringField"
71+
72+--------------------------------------------------------------------------------
73+
74+testOuerLoopContextAccess :: Assertion
75+testOuerLoopContextAccess = do
76+ store <- newTestStore
77+ provider <- newTestProvider store
78+ test store provider ctx "baz"
79+ test store provider (ctx' <> ctx) "not baz"
80+ test store provider (ctx <> ctx') "baz"
81+
82+ cleanTestEnv
83+ where
84+ tpl = readTemplate "$for(foo)$$for(bar)$$qux$$endfor$$endfor$"
85+ ctx = mconcat [
86+ field "qux" $ const $ return "baz"
87+ , listField "foo" (listField "bar" mempty $ return [mockItem])
88+ $ return [mockItem]
89+ ]
90+ ctx' = field "qux" $ const $ return "not baz"
91+ mockItem = Item "" ()
92+ test store provider context str = do
93+ str' <- testCompilerDone store provider ""
94+ $ applyTemplate tpl context mockItem
95+ str @=? itemBody str'
tests/Hakyll/Web/Template/Tests.hs+52−19
@@ -1,5 +1,6 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
3module Hakyll.Web.Template.Tests
4module Hakyll.Web.Template.Tests
4 ( tests
5 ( tests
5 ) where
6 ) where
@@ -7,16 +8,16 @@ module Hakyll.Web.Template.Tests
7
8
8--------------------------------------------------------------------------------
9--------------------------------------------------------------------------------
9import Test.Tasty (TestTree, testGroup)
10import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (Assertion, testCase, (@=?),
11+import Test.Tasty.HUnit (Assertion, assertBool, testCase,
11− (@?=))
12+ (@=?), (@?=))
12
13
14+import Data.Either (isLeft)
13
15
14--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
15import Hakyll.Core.Compiler
17import Hakyll.Core.Compiler
16import Hakyll.Core.Identifier
18import Hakyll.Core.Identifier
17import Hakyll.Core.Item
19import Hakyll.Core.Item
18import Hakyll.Core.Provider
20import Hakyll.Core.Provider
19−import Hakyll.Web.Pandoc
20import Hakyll.Web.Template
21import Hakyll.Web.Template
21import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Internal
23import Hakyll.Web.Template.Internal
@@ -26,20 +27,20 @@ import TestSuite.Util
26
27
27--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
28tests :: TestTree
29tests :: TestTree
29−tests = testGroup "Hakyll.Core.Template.Tests" $ concat
30+tests = testGroup "Hakyll.Web.Template.Tests" $ concat
30 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
31 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
31 , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
32 , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
32 , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
33 , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
33 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
34 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
34 ]
35 ]
35
36
36− , fromAssertions "readTemplate"
37+ , fromAssertions "parseTemplate"
37− [ [Chunk "Hello ", Expr (Call "guest" [])]
38+ [ Right [Chunk "Hello ", Expr (Call "guest" [])]
38− @=? readTemplateElems "Hello $guest()$"
39+ @=? parse "Hello $guest()$"
39− , [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
40+ , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
40− @=? readTemplateElems "$if(a(\"bar\"))$foo$endif$"
41+ @=? parse "$if(a(\"bar\"))$foo$endif$"
41 -- 'If' trim check.
42 -- 'If' trim check.
42− , [ TrimL
43+ , Right [ TrimL
43 , If (Ident (TemplateKey "body"))
44 , If (Ident (TemplateKey "body"))
44 [ TrimR
45 [ TrimR
45 , Chunk "\n"
46 , Chunk "\n"
@@ -55,29 +56,39 @@ tests = testGroup "Hakyll.Core.Template.Tests" $ concat
55 ])
56 ])
56 , TrimR
57 , TrimR
57 ]
58 ]
58− @=? readTemplateElems "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
59+ @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
59 -- 'For' trim check.
60 -- 'For' trim check.
60− , [ TrimL
61+ , Right [ TrimL
61 , For (Ident (TemplateKey "authors"))
62 , For (Ident (TemplateKey "authors"))
62 [TrimR, Chunk "\n body \n", TrimL]
63 [TrimR, Chunk "\n body \n", TrimL]
63 Nothing
64 Nothing
64 , TrimR
65 , TrimR
65 ]
66 ]
66− @=? readTemplateElems "$-for(authors)-$\n body \n$-endfor-$"
67+ @=? parse "$-for(authors)-$\n body \n$-endfor-$"
67 -- 'Partial' trim check.
68 -- 'Partial' trim check.
68− , [ TrimL
69+ , Right [ TrimL
69 , Partial (StringLiteral "path")
70 , Partial (StringLiteral "path")
70 , TrimR
71 , TrimR
71 ]
72 ]
72− @=? readTemplateElems "$-partial(\"path\")-$"
73+ @=? parse "$-partial(\"path\")-$"
73 -- 'Expr' trim check.
74 -- 'Expr' trim check.
74− , [ TrimL
75+ , Right [ TrimL
75 , Expr (Ident (TemplateKey "foo"))
76 , Expr (Ident (TemplateKey "foo"))
76 , TrimR
77 , TrimR
77 ]
78 ]
78− @=? readTemplateElems "$-foo-$"
79+ @=? parse "$-foo-$"
80+ -- fail on incomplete template.
81+ , assertBool "did not yield error" $ isLeft $
82+ parse "a$b"
83+ -- fail on mismatched template syntax.
84+ , assertBool "did not fail to parse" $ isLeft $
85+ parse "$for(xs)$\n <p>foo</p>\n$endif$"
79 ]
86 ]
87+
88+ , [testCase "embeddedTemplate" testEmbeddedTemplate]
80 ]
89 ]
90+ where
91+ parse = parseTemplateElemsFile ""
81
92
82
93
83--------------------------------------------------------------------------------
94--------------------------------------------------------------------------------
@@ -89,7 +100,9 @@ test (outf, tplf, itemf) = do
89 out <- resourceString provider outf
100 out <- resourceString provider outf
90 tpl <- testCompilerDone store provider tplf templateBodyCompiler
101 tpl <- testCompilerDone store provider tplf templateBodyCompiler
91 item <- testCompilerDone store provider itemf $
102 item <- testCompilerDone store provider itemf $
103+ getResourceBody
104+ >>= renderParagraphs
92− pandocCompiler >>= applyTemplate (itemBody tpl) testContext
105+ >>= applyTemplate (itemBody tpl) testContext
93
106
94 out @=? itemBody item
107 out @=? itemBody item
95 cleanTestEnv
108 cleanTestEnv
@@ -112,6 +125,8 @@ testApplyJoinTemplateList :: Assertion
112testApplyJoinTemplateList = do
125testApplyJoinTemplateList = do
113 store <- newTestStore
126 store <- newTestStore
114 provider <- newTestProvider store
127 provider <- newTestProvider store
128+ tpl <- testCompilerDone store provider "tpl" $
129+ compileTemplateItem (Item "tpl" "<b>$body$</b>")
115 str <- testCompilerDone store provider "item3" $
130 str <- testCompilerDone store provider "item3" $
116 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
131 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
117
132
@@ -120,4 +135,22 @@ testApplyJoinTemplateList = do
120 where
135 where
121 i1 = Item "item1" "Hello"
136 i1 = Item "item1" "Hello"
122 i2 = Item "item2" "World"
137 i2 = Item "item2" "World"
123− tpl = readTemplate "<b>$body$</b>"
138+
139+
140+--------------------------------------------------------------------------------
141+embeddedTemplate :: Template
142+embeddedTemplate = $(embedTemplate "tests/data/embed.html")
143+
144+--------------------------------------------------------------------------------
145+testEmbeddedTemplate :: Assertion
146+testEmbeddedTemplate = do
147+ store <- newTestStore
148+ provider <- newTestProvider store
149+ str <- testCompilerDone store provider "item3" $
150+ applyTemplate embeddedTemplate defaultContext item
151+
152+ itemBody str @?= "<p>Hello, world</p>\n"
153+ cleanTestEnv
154+ where
155+ item = Item "item1" "Hello, world"
156+
tests/TestSuite.hs+7
@@ -1,4 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2module Main
3module Main
3 ( main
4 ( main
4 ) where
5 ) where
@@ -22,9 +23,12 @@ import qualified Hakyll.Core.Util.String.Tests
22import qualified Hakyll.Web.CompressCss.Tests
23import qualified Hakyll.Web.CompressCss.Tests
23import qualified Hakyll.Web.Html.RelativizeUrls.Tests
24import qualified Hakyll.Web.Html.RelativizeUrls.Tests
24import qualified Hakyll.Web.Html.Tests
25import qualified Hakyll.Web.Html.Tests
26+#ifdef USE_PANDOC
25import qualified Hakyll.Web.Pandoc.FileType.Tests
27import qualified Hakyll.Web.Pandoc.FileType.Tests
28+#endif
26import qualified Hakyll.Web.Template.Context.Tests
29import qualified Hakyll.Web.Template.Context.Tests
27import qualified Hakyll.Web.Template.Tests
30import qualified Hakyll.Web.Template.Tests
31+import qualified Hakyll.Web.Tags.Tests
28
32
29
33
30--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
@@ -43,7 +47,10 @@ main = defaultMain $ testGroup "Hakyll"
43 , Hakyll.Web.CompressCss.Tests.tests
47 , Hakyll.Web.CompressCss.Tests.tests
44 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
48 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
45 , Hakyll.Web.Html.Tests.tests
49 , Hakyll.Web.Html.Tests.tests
50+#ifdef USE_PANDOC
46 , Hakyll.Web.Pandoc.FileType.Tests.tests
51 , Hakyll.Web.Pandoc.FileType.Tests.tests
52+#endif
53+ , Hakyll.Web.Tags.Tests.tests
47 , Hakyll.Web.Template.Context.Tests.tests
54 , Hakyll.Web.Template.Context.Tests.tests
48 , Hakyll.Web.Template.Tests.tests
55 , Hakyll.Web.Template.Tests.tests
49 ]
56 ]
tests/TestSuite/Util.hs+27−5
@@ -6,13 +6,15 @@ module TestSuite.Util
6 , newTestProvider
6 , newTestProvider
7 , testCompiler
7 , testCompiler
8 , testCompilerDone
8 , testCompilerDone
9+ , testCompilerError
9 , testConfiguration
10 , testConfiguration
10 , cleanTestEnv
11 , cleanTestEnv
12+ , renderParagraphs
11 ) where
13 ) where
12
14
13
15
14--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
15−import Data.List (intercalate)
17+import Data.List (intercalate, isInfixOf)
16import Data.Monoid (mempty)
18import Data.Monoid (mempty)
17import qualified Data.Set as S
19import qualified Data.Set as S
18import Test.Tasty
20import Test.Tasty
@@ -29,6 +31,7 @@ import Hakyll.Core.Provider
29import Hakyll.Core.Store (Store)
31import Hakyll.Core.Store (Store)
30import qualified Hakyll.Core.Store as Store
32import qualified Hakyll.Core.Store as Store
31import Hakyll.Core.Util.File
33import Hakyll.Core.Util.File
34+import Hakyll.Core.Item
32
35
33
36
34--------------------------------------------------------------------------------
37--------------------------------------------------------------------------------
@@ -76,14 +79,23 @@ testCompilerDone store provider underlying compiler = do
76 result <- testCompiler store provider underlying compiler
79 result <- testCompiler store provider underlying compiler
77 case result of
80 case result of
78 CompilerDone x _ -> return x
81 CompilerDone x _ -> return x
79− CompilerError e -> error $
82+ CompilerError e -> fail $
80 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
81− " threw: " ++ intercalate "; " e
84+ " threw: " ++ intercalate "; " (compilerErrorMessages e)
82− CompilerRequire i _ -> error $
85+ CompilerRequire i _ -> fail $
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
86 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
84 " requires: " ++ show i
87 " requires: " ++ show i
88+ CompilerSnapshot _ _ -> fail
89+ "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
85
90
86−
91+testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()
92+testCompilerError store provider underlying compiler expectedMessage = do
93+ result <- testCompiler store provider underlying compiler
94+ case result of
95+ CompilerError e ->
96+ any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?
97+ "Expecting '" ++ expectedMessage ++ "' error"
98+ _ -> assertFailure "Expecting CompilerError"
87
99
88--------------------------------------------------------------------------------
100--------------------------------------------------------------------------------
89testConfiguration :: Configuration
101testConfiguration :: Configuration
@@ -101,3 +113,13 @@ cleanTestEnv = do
101 removeDirectory $ destinationDirectory testConfiguration
113 removeDirectory $ destinationDirectory testConfiguration
102 removeDirectory $ storeDirectory testConfiguration
114 removeDirectory $ storeDirectory testConfiguration
103 removeDirectory $ tmpDirectory testConfiguration
115 removeDirectory $ tmpDirectory testConfiguration
116+
117+
118+--------------------------------------------------------------------------------
119+-- | like 'Hakyll.Web.Pandoc.renderPandoc'
120+-- | but allowing to test without the @usePandoc@ flag
121+renderParagraphs :: Item String -> Compiler (Item String)
122+renderParagraphs = withItemBody (return
123+ . intercalate "\n" -- no trailing line
124+ . map (("<p>"++) . (++"</p>"))
125+ . lines)
tests/data/embed.htmlnew file+1
@@ -0,0 +1 @@
1+<p>$body$</p>
tests/data/posts/2018-09-26.mdnew file+1
@@ -0,0 +1 @@
1+Something happened today.
tests/data/posts/2019/05/10/tomorrow.mdnew file+1
@@ -0,0 +1 @@
1+This day hasn't happened yet (as of writing this).
web/css/syntax.css+61−18
@@ -1,18 +1,61 @@
1−/* Generated by pandoc. */
1+a.sourceLine { display: inline-block; line-height: 1.25; }
2−table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
2+a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
3− { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
3+a.sourceLine:empty { height: 1.2em; }
4−td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
4+.sourceCode { overflow: visible; }
5−td.sourceCode { padding-left: 5px; }
5+code.sourceCode { white-space: pre; position: relative; }
6−.sourceCode span.kw { color: #007020; font-weight: bold; }
6+div.sourceCode { margin: 1em 0; }
7+pre.sourceCode { margin: 0; }
8+@media screen {
9+div.sourceCode { overflow: auto; }
10+}
11+@media print {
12+code.sourceCode { white-space: pre-wrap; }
13+a.sourceLine { text-indent: -1em; padding-left: 1em; }
14+}
15+pre.numberSource a.sourceLine
16+ { position: relative; left: -4em; }
17+pre.numberSource a.sourceLine::before
18+ { content: attr(title);
19+ position: relative; left: -1em; text-align: right; vertical-align: baseline;
20+ border: none; pointer-events: all; display: inline-block;
21+ -webkit-touch-callout: none; -webkit-user-select: none;
22+ -khtml-user-select: none; -moz-user-select: none;
23+ -ms-user-select: none; user-select: none;
24+ padding: 0 4px; width: 4em;
25+ color: #aaaaaa;
26+ }
27+pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
28+div.sourceCode
29+ { }
30+@media screen {
31+a.sourceLine::before { text-decoration: underline; }
32+}
33+code span.al { color: #ff0000; font-weight: bold; } /* Alert */
34+code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
35+code span.at { color: #7d9029; } /* Attribute */
36+code span.bn { color: #40a070; } /* BaseN */
37+code span.bu { } /* BuiltIn */
38+code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
39+code span.ch { color: #4070a0; } /* Char */
40+code span.cn { color: #880000; } /* Constant */
41+code span.co { color: #60a0b0; font-style: italic; } /* Comment */
42+code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
43+code span.do { color: #ba2121; font-style: italic; } /* Documentation */
7−.sourceCode span.dt { color: #902000; }
44+code span.dt { color: #902000; } /* DataType */
8−.sourceCode span.dv { color: #40a070; }
45+code span.dv { color: #40a070; } /* DecVal */
9−.sourceCode span.bn { color: #40a070; }
46+code span.er { color: #ff0000; font-weight: bold; } /* Error */
47+code span.ex { } /* Extension */
10−.sourceCode span.fl { color: #40a070; }
48+code span.fl { color: #40a070; } /* Float */
11−.sourceCode span.ch { color: #4070a0; }
12−.sourceCode span.st { color: #4070a0; }
13−.sourceCode span.co { color: #60a0b0; font-style: italic; }
14−.sourceCode span.ot { color: #007020; }
15−.sourceCode span.al { color: red; font-weight: bold; }
16−.sourceCode span.fu { color: #06287e; }
49+code span.fu { color: #06287e; } /* Function */
17−.sourceCode span.re { }
50+code span.im { } /* Import */
18−.sourceCode span.er { color: red; font-weight: bold; }
51+code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
52+code span.kw { color: #007020; font-weight: bold; } /* Keyword */
53+code span.op { color: #666666; } /* Operator */
54+code span.ot { color: #007020; } /* Other */
55+code span.pp { color: #bc7a00; } /* Preprocessor */
56+code span.sc { color: #4070a0; } /* SpecialChar */
57+code span.ss { color: #bb6688; } /* SpecialString */
58+code span.st { color: #4070a0; } /* String */
59+code span.va { color: #19177c; } /* Variable */
60+code span.vs { color: #4070a0; } /* VerbatimString */
61+code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
web/examples.markdown+48−28
@@ -20,6 +20,8 @@ directly with the default Hakyll site.
20 [source](https://github.com/jaspervdj/jaspervdj)
20 [source](https://github.com/jaspervdj/jaspervdj)
21- <http://www.imagination-land.org/>,
21- <http://www.imagination-land.org/>,
22 [source](https://github.com/Keruspe/blog/)
22 [source](https://github.com/Keruspe/blog/)
23+- <https://bananasandlenses.net/>,
24+ [source](https://github.com/denisshevchenko/bananasandlenses)
23- <http://blog.clement.delafargue.name/>,
25- <http://blog.clement.delafargue.name/>,
24 [source](https://github.com/divarvel/blog)
26 [source](https://github.com/divarvel/blog)
25- <http://bneijt.nl>,
27- <http://bneijt.nl>,
@@ -34,8 +36,6 @@ directly with the default Hakyll site.
34 [source](https://github.com/dannysu/hakyll-blog)
36 [source](https://github.com/dannysu/hakyll-blog)
35- <http://meta.plasm.us/>,
37- <http://meta.plasm.us/>,
36 [source](https://github.com/travisbrown/metaplasm)
38 [source](https://github.com/travisbrown/metaplasm)
37−- <http://www.web2day-nantes.org/>,
38− [source](https://github.com/CompanyCampus/web2day2013)
39- <http://workforpizza.com/>,
39- <http://workforpizza.com/>,
40 [source](https://github.com/irneh/workforpizza)
40 [source](https://github.com/irneh/workforpizza)
41- <http://poleiro.info/>,
41- <http://poleiro.info/>,
@@ -45,9 +45,7 @@ directly with the default Hakyll site.
45- <http://www.regisfoucault.com/>,
45- <http://www.regisfoucault.com/>,
46 [source](https://github.com/regisfoucault/blog)
46 [source](https://github.com/regisfoucault/blog)
47- <http://www.chaoxuprime.com/>,
47- <http://www.chaoxuprime.com/>,
48− [source](https://github.com/Mgccl/blog)
48+ [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
49−- <https://benjeffrey.com/>,
50− [source](https://github.com/jeffbr13/benjeffrey.com)
51- <http://www.skybluetrades.net/>,
49- <http://www.skybluetrades.net/>,
52 [source](https://github.com/ian-ross/blog)
50 [source](https://github.com/ian-ross/blog)
53- <http://chromaticleaves.com/>,
51- <http://chromaticleaves.com/>,
@@ -58,8 +56,6 @@ directly with the default Hakyll site.
58 [source](https://github.com/blaenk/blaenk.github.io)
56 [source](https://github.com/blaenk/blaenk.github.io)
59- <https://xinitrc.de/>,
57- <https://xinitrc.de/>,
60 [source](https://github.com/xinitrc/xinitrc.de)
58 [source](https://github.com/xinitrc/xinitrc.de)
61−- <http://blog.darkfox.id.au/>,
62− [source](http://hub.darcs.net/DarkFox/DarkFox-blog)
63- <http://nickcharlton.net/>,
59- <http://nickcharlton.net/>,
64 [source](https://github.com/nickcharlton/nickcharlton.net)
60 [source](https://github.com/nickcharlton/nickcharlton.net)
65- <http://scr.stunts.hu/>,
61- <http://scr.stunts.hu/>,
@@ -72,16 +68,10 @@ directly with the default Hakyll site.
72 [source](https://github.com/walkie/WebPage)
68 [source](https://github.com/walkie/WebPage)
73- <http://techblog.rosedu.org/>,
69- <http://techblog.rosedu.org/>,
74 [source](https://github.com/rosedu/techblog)
70 [source](https://github.com/rosedu/techblog)
75−- <http://abizern.org>,
76− [source](https://github.com/Abizern/hblog)
77- <http://freizl.github.io>,
71- <http://freizl.github.io>,
78 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
72 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
79−- <http://covariant.me>,
80− [source](http://hub.darcs.net/co-dan/website)
81- <http://www.gwern.net/>,
73- <http://www.gwern.net/>,
82 [source](https://github.com/gwern/gwern.net)
74 [source](https://github.com/gwern/gwern.net)
83−- <http://www.rohanjain.in/>,
84− [source](https://github.com/crodjer/rohanjain.in)
85- <http://www.corentindupont.info/>,
75- <http://www.corentindupont.info/>,
86 [source](https://github.com/cdupont/CorentinDupont-WebPage)
76 [source](https://github.com/cdupont/CorentinDupont-WebPage)
87- <http://jelv.is>,
77- <http://jelv.is>,
@@ -90,8 +80,8 @@ directly with the default Hakyll site.
90 [source](https://github.com/plaimi/www)
80 [source](https://github.com/plaimi/www)
91- <http://duplode.github.io/>,
81- <http://duplode.github.io/>,
92 [source](https://github.com/duplode/duplode.github.io/tree/sources)
82 [source](https://github.com/duplode/duplode.github.io/tree/sources)
93−- <http://maseek.codes>,
83+- <http://tzemanovic.github.io/>,
94− [source](https://github.com/maseek/maseek-codes)
84+ [source](https://github.com/tzemanovic/tzemanovic)
95- <http://listx.github.io>,
85- <http://listx.github.io>,
96 [source](https://github.com/listx/listx_blog)
86 [source](https://github.com/listx/listx_blog)
97- <http://kyle.marek-spartz.org>,
87- <http://kyle.marek-spartz.org>,
@@ -102,26 +92,20 @@ directly with the default Hakyll site.
102 [source](https://github.com/rbros/reichertbrothers.com)
92 [source](https://github.com/rbros/reichertbrothers.com)
103- <http://alemedeiros.sdf.org>,
93- <http://alemedeiros.sdf.org>,
104 [source](https://github.com/alemedeiros/homepage)
94 [source](https://github.com/alemedeiros/homepage)
105−- <https://ruudvanasseldonk.com/>,
106− [source](https://github.com/ruud-v-a/ruudvanasseldonk.com)
107- <http://www.haskell.mn>,
95- <http://www.haskell.mn>,
108 [source](https://github.com/HaskellMN/www.haskell.mn)
96 [source](https://github.com/HaskellMN/www.haskell.mn)
109- <https://axiomatic.neophilus.net/>,
97- <https://axiomatic.neophilus.net/>,
110 [source](https://github.com/Libbum/AxiomaticSemantics)
98 [source](https://github.com/Libbum/AxiomaticSemantics)
111−- <https://tesser.org/>,
99+- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
112 [source](https://github.com/fractalcat/tesser.org)
100 [source](https://github.com/fractalcat/tesser.org)
113−- <https://wei2912.github.io/>,
101+- <https://weien.io/>,
114− [source](https://github.com/wei2912/blog_src)
102+  [source](https://github.com/wei2912/blog-src)
115- <http://www.metasepi.org/>,
103- <http://www.metasepi.org/>,
116 [source](https://github.com/metasepi/metasepi-website)
104 [source](https://github.com/metasepi/metasepi-website)
117- <http://gisli.hamstur.is/>,
105- <http://gisli.hamstur.is/>,
118 [source](https://github.com/gislik/gisli.hamstur.is)
106 [source](https://github.com/gislik/gisli.hamstur.is)
119- <http://blog.ssanj.net/>,
107- <http://blog.ssanj.net/>,
120 [source](https://github.com/ssanj/babyloncandle)
108 [source](https://github.com/ssanj/babyloncandle)
121−- <https://rockymadden.com/>,
122− [source](https://github.com/rockymadden/rockymadden-web)
123−- <http://blog.dshevchenko.biz/>,
124− [source](https://github.com/denisshevchenko/blog)
125- <http://ruhaskell.org/>,
109- <http://ruhaskell.org/>,
126 [source](https://github.com/ruHaskell/ruhaskell)
110 [source](https://github.com/ruHaskell/ruhaskell)
127- <http://www.haskell.od.ua/>,
111- <http://www.haskell.od.ua/>,
@@ -142,8 +126,6 @@ directly with the default Hakyll site.
142 [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
126 [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
143- <http://lettier.github.io/>,
127- <http://lettier.github.io/>,
144 [source](https://github.com/lettier/lettier.github.io)
128 [source](https://github.com/lettier/lettier.github.io)
145−- <http://jozefg.bitbucket.org/>,
146− [source](https://github.com/jozefg/blog)
147- <http://raindev.io>,
129- <http://raindev.io>,
148 [source](https://github.com/raindev/raindev.github.io)
130 [source](https://github.com/raindev/raindev.github.io)
149- <http://hal2016.haskell.org/>,
131- <http://hal2016.haskell.org/>,
@@ -162,6 +144,46 @@ directly with the default Hakyll site.
162 [source](https://github.com/jjduhamel/blog)
144 [source](https://github.com/jjduhamel/blog)
163- <http://alexanderlobov.ru/>,
145- <http://alexanderlobov.ru/>,
164 [source](https://github.com/alexanderlobov/homepage)
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)
165
187
166## Hakyll 3.X
188## Hakyll 3.X
167
189
@@ -189,7 +211,5 @@ directly with the default Hakyll site.
189 [source](http://gitorious.org/shakthimaan-blog)
211 [source](http://gitorious.org/shakthimaan-blog)
190- <http://www.alfredodinapoli.com/>,
212- <http://www.alfredodinapoli.com/>,
191 [source](https://github.com/CharlesStain/alfredodinapoli.com)
213 [source](https://github.com/CharlesStain/alfredodinapoli.com)
192−- <http://datahackermd.com>,
193− [source](http://github.com/akshayjshah/datahackermd)
194- <http://michaelxavier.net>,
214- <http://michaelxavier.net>,
195 [source](https://github.com/michaelxavier/michaelxavier.net)
215 [source](https://github.com/michaelxavier/michaelxavier.net)
web/libraries.markdown+1
@@ -18,3 +18,4 @@ Here is a list of the ones I know of (I would be happy to hear about others).
18 a conversion tool from other blog engines to Hakyll.
18 a conversion tool from other blog engines to Hakyll.
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
20 Hakyll wrapper for the Elm compiler.
20 Hakyll wrapper for the Elm compiler.
21+- [hakyll-sass](http://hackage.haskell.org/package/hakyll-sass), a compiler for SASS templates.
web/site.hs+25−39
@@ -1,19 +1,23 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3−import Control.Arrow (second)
3+import Control.Arrow (second)
4−import Control.Monad (forM_)
4+import Control.Monad (filterM, forM_)
5−import Data.Char (isDigit)
6−import Data.List (isPrefixOf, sortBy)
5+import Data.List (isPrefixOf, sortBy)
7−import Data.Monoid ((<>))
6+import Data.Monoid ((<>))
8−import Data.Ord (comparing)
7+import Data.Ord (comparing)
9import Hakyll
8import 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
17 match "css/*" $ do
21 match "css/*" $ do
18 route idRoute
22 route idRoute
19 compile compressCssCompiler
23 compile compressCssCompiler
@@ -52,17 +56,11 @@ main = hakyllWith config $ do
52 create ["tutorials.html"] $ do
56 create ["tutorials.html"] $ do
53 route idRoute
57 route idRoute
54 compile $ do
58 compile $ do
55− tuts <-
59+ ctx <- tutorialsCtx <$>
56 sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
60 sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
57−
58− let tutorialsCtx =
59− constField "title" "Tutorials" `mappend`
60− listField "tutorials" tutorialCtx (return tuts) `mappend`
61− defaultContext
62−
63 makeItem ""
61 makeItem ""
64− >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
62+ >>= loadAndApplyTemplate "templates/tutorials.html" ctx
65− >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
63+ >>= loadAndApplyTemplate "templates/default.html" ctx
66 >>= relativizeUrls
64 >>= relativizeUrls
67
65
68 -- Templates
66 -- Templates
@@ -70,8 +68,7 @@ main = hakyllWith config $ do
70 where
68 where
71 withToc = defaultHakyllWriterOptions
69 withToc = defaultHakyllWriterOptions
72 { writerTableOfContents = True
70 { writerTableOfContents = True
73− , writerTemplate = "$toc$\n$body$"
71+ , writerTemplate = Just "$toc$\n$body$"
74− , writerStandalone = True
75 }
72 }
76
73
77
74
@@ -104,28 +101,17 @@ hackage url
104 reverse $ splitPath url
101 reverse $ splitPath url
105
102
106
103
107−--------------------------------------------------------------------------------
108−data TutorialType = SeriesTutorial | ArticlesTutorial | ExternalTutorial
109− deriving (Eq)
110−
111−
112--------------------------------------------------------------------------------
104--------------------------------------------------------------------------------
113-- | Partition tutorials into tutorial series, other articles, external articles
105-- | Partition tutorials into tutorial series, other articles, external articles
114−tutorialCtx :: Context String
106+tutorialsCtx :: [Item String] -> Context String
115−tutorialCtx =
107+tutorialsCtx tuts =
116− field "isSeries" (isTutorialType SeriesTutorial) <>
108+ constField "title" "Tutorials" <>
117− field "isArticle" (isTutorialType ArticlesTutorial) <>
109+ listField "main" defaultContext (ofType "main") <>
118− field "isExternal" (isTutorialType ExternalTutorial) <>
110+ listField "articles" defaultContext (ofType "article") <>
111+ listField "externals" defaultContext (ofType "external") <>
112+ listField "robertwpearce" defaultContext (ofType "robertwpearce") <>
119 defaultContext
113 defaultContext
120 where
114 where
121− getTutorialType item = do
115+ ofType ty = filterM (\item -> do
122− mbExternal <- getMetadataField (itemIdentifier item) "external"
116+ mbType <- getMetadataField (itemIdentifier item) "type"
123− return $ case mbExternal of
117+ return $ Just ty == mbType) tuts
124− Just _ -> ExternalTutorial
125− _ -> case splitPath (toFilePath $ itemIdentifier item) of
126− [_, (x : _)] -> if isDigit x then SeriesTutorial else ArticlesTutorial
127− _ -> ArticlesTutorial
128−
129− isTutorialType ty0 item = do
130− ty1 <- getTutorialType item
131− if ty0 == ty1 then return "yes" else fail "no"
web/templates/default.html+1−6
@@ -35,12 +35,7 @@
35 <a href="/tutorials/faq.html">faq</a>
35 <a href="/tutorials/faq.html">faq</a>
36 <a href="/examples.html">examples</a>
36 <a href="/examples.html">examples</a>
37 <a href="/releases.html">releases</a>
37 <a href="/releases.html">releases</a>
38−
39− <!-- Flattr button -->
40− <h1>Donate</h1>
41− <a href="https://flattr.com/thing/291889/Hakyll" target="_blank">
38+ <a href="https://jaspervdj.be/contact.html#donate" target="_blank">donate</a>
42− <img src="https://api.flattr.com/button/flattr-badge-large.png"
43− alt="Flattr this" title="Flattr this" border="0" /></a>
44 </div>
39 </div>
45
40
46 <div id="content">
41 <div id="content">
web/templates/tutorials.html+24−18
@@ -1,23 +1,19 @@
1<h1>Tutorials about Hakyll</h1>
1<h1>Tutorials about Hakyll</h1>
2<h2 id="series">Tutorial series</h2>
2<h2 id="series">Tutorial series</h2>
3<ul>
3<ul>
4− $for(tutorials)$
4+ $for(main)$
5− $if(isSeries)$
6− <li>
5+ <li>
7− <a href="$url$">$title$</a>
6+ <a href="$url$">$title$</a>
8− </li>
7+ </li>
9− $endif$
10 $endfor$
8 $endfor$
11</ul>
9</ul>
12<h2 id="articles">Other articles</h2>
10<h2 id="articles">Other articles</h2>
13<p>In no particular order:</p>
11<p>In no particular order:</p>
14<ul>
12<ul>
15− $for(tutorials)$
13+ $for(articles)$
16− $if(isArticle)$
17− <li>
14+ <li>
18− <a href="$url$">$title$</a> by <em>$author$</em>
15+ <a href="$url$">$title$</a> by <em>$author$</em>
19− </li>
16+ </li>
20− $endif$
21 $endfor$
17 $endfor$
22</ul>
18</ul>
23<h2 id="external-articles">External articles</h2>
19<h2 id="external-articles">External articles</h2>
@@ -28,12 +24,22 @@
28</p>
24</p>
29<p>In no particular order:</p>
25<p>In no particular order:</p>
30<ul>
26<ul>
31− $for(tutorials)$
27+ $for(externals)$
32− $if(isExternal)$
33− <li>
28+ <li>
34− <a href="$url$">$title$</a> by <em>$author$</em>
29+ <a href="$url$">$title$</a> by <em>$author$</em>
35− </li>
30+ </li>
36− $endif$
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>
37 $endfor$
43 $endfor$
38</ul>
44</ul>
39<p>
45<p>
web/tutorials/01-installation.markdown+4−1
@@ -1,6 +1,7 @@
1---
1---
2title: Installation
2title: Installation
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Installation
7Installation
@@ -17,6 +18,8 @@ different distributions. For installation from source (i.e. via Hackage),
17Linux distro packages:
18Linux distro packages:
18
19
19- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
20- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
21+- [Fedora](https://apps.fedoraproject.org/packages/ghc-hakyll)
22+- [Nix](https://nixos.org/nixos/packages.html#hakyll): `$ nix-env -i hakyll`
20
23
21Building the example site
24Building the example site
22-------------------------
25-------------------------
@@ -25,7 +28,7 @@ Apart from the main Hakyll library, the cabal package also provides you with an
25executable `hakyll-init` to create an example site. This is an easy way to get
28executable `hakyll-init` to create an example site. This is an easy way to get
26started:
29started:
27
30
28− $ hakyll-init my-site
31+ $ stack exec hakyll-init my-site
29
32
30This creates a folder `my-site` in the current directory, with some example
33This creates a folder `my-site` in the current directory, with some example
31content and a generic configuration.
34content and a generic configuration.
web/tutorials/02-basics.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: The basics
2title: The basics
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Building and cleaning
7Building and cleaning
web/tutorials/03-rules-routes-compilers.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Rules, routes and compilers
2title: Rules, routes and compilers
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic rules
7Basic rules
web/tutorials/04-compilers.markdown+18−3
@@ -1,6 +1,7 @@
1---
1---
2title: 'More on compilers: load, and templates'
2title: 'More on compilers: load, and templates'
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Loading items
7Loading items
@@ -92,7 +93,7 @@ And `$title$` like this:
92titleContext :: Context a
93titleContext :: Context a
93titleContext = field "title" $ \item -> do
94titleContext = field "title" $ \item -> do
94 metadata <- getMetadata (itemIdentifier item)
95 metadata <- getMetadata (itemIdentifier item)
95− return $ fromMaybe "No title" $ M.lookup "title" metadata
96+ return $ fromMaybe "No title" $ lookupString "title" metadata
96```
97```
97
98
98And compose them using the `Monoid` instance:
99And compose them using the `Monoid` instance:
@@ -130,7 +131,7 @@ postCtx =
130### Loading and applying templates
131### Loading and applying templates
131
132
132Now we know about templates, context and how to load arbitrary items. This gives
133Now we know about templates, context and how to load arbitrary items. This gives
133−us enough background information in order to understand you can apply a
134+us enough background information in order to understand how you can apply a
134template:
135template:
135
136
136```haskell
137```haskell
@@ -151,7 +152,7 @@ Control flow in templates
151-------------------------
152-------------------------
152
153
153Sometimes string interpolation does not suffice, and you want a little more
154Sometimes string interpolation does not suffice, and you want a little more
154−control over how your templates are layed out. Hakyll provides a few control
155+control over how your templates are laid out. Hakyll provides a few control
155structures for this. The syntax for these structures was based on the syntax
156structures for this. The syntax for these structures was based on the syntax
156used in pandoc templates, since Hakyll already has tight integration with
157used in pandoc templates, since Hakyll already has tight integration with
157pandoc.
158pandoc.
@@ -236,6 +237,20 @@ This uses the `$for(foo)$` construct. This construct allows you loop over a
236list, in this case, `$posts$`. Inside the body of this for loop, all fields
237list, in this case, `$posts$`. Inside the body of this for loop, all fields
237refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
238refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
238
239
240+You can also add a simple separator with the special `$sep$` field.
241+Everything between `$sep$` and `$endfor$` will be regarded as a separator
242+that will only be shown if there is more than one item in the list.
243+
244+```html
245+<ul>
246+ $for(posts)$
247+ $x$
248+ $sep$,
249+ $endfor$
250+</ul>
251+```
252+
253+
239Of course, $posts$ does not magically appear. We have to specify this in
254Of course, $posts$ does not magically appear. We have to specify this in
240`site.hs`. Let's look at how `archive.html` is generated:
255`site.hs`. Let's look at how `archive.html` is generated:
241
256
web/tutorials/05-snapshots-feeds.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Snapshots, and how to produce an RSS/Atom feed
2title: Snapshots, and how to produce an RSS/Atom feed
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic feed configuration
7Basic feed configuration
web/tutorials/06-versions.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Producing multiple versions of a single file
2title: Producing multiple versions of a single file
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basics
7Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: A Guide to the Hakyll Module Zoo
2title: A Guide to the Hakyll Module Zoo
3author: Brent Yorgey
3author: Brent Yorgey
4+type: article
4---
5---
5
6
6The hakyll package [contains a bewildering array](/reference/) of modules, and
7The hakyll package [contains a bewildering array](/reference/) of modules, and
web/tutorials/external-add-tags-to-your-hakyll-blog.md+1−1
@@ -2,5 +2,5 @@
2title: Add tags to your Hakyll blog
2title: Add tags to your Hakyll blog
3author: Javran Cheng
3author: Javran Cheng
4url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
4url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-blogger-port.mdnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: Port an existing Blogger blog to Hakyll
3+author: Daniel Campoverde
4+url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
5+type: external
6+---
web/tutorials/external-cassius-template.mdnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: Using Cassius templating with Hakyll
3+author: Daniel Campoverde
4+url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
5+type: external
6+---
web/tutorials/external-clean-urls-with-hakyll.md+1−1
@@ -2,5 +2,5 @@
2title: Clean URLs with Hakyll
2title: Clean URLs with Hakyll
3author: Rohan Jain
3author: Rohan Jain
4url: http://www.rohanjain.in/hakyll-clean-urls/
4url: http://www.rohanjain.in/hakyll-clean-urls/
5−external: true
5+type: external
6---
6---
web/tutorials/external-deploying-with-widely.md+1−1
@@ -2,5 +2,5 @@
2title: Deploying with Widely
2title: Deploying with Widely
3author: Kyle Marek-Spartz
3author: Kyle Marek-Spartz
4url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
4url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-external-code-inclusion.mdnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: 'External code inclusion with Hakyll'
3+author: 'Tobias Pleyer'
4+url: 'http://blog.tpleyer.de/posts/2019-04-21-external-code-inclusion-with-hakyll.html'
5+type: external
6+---
web/tutorials/external-functionfield.md+1−1
@@ -2,5 +2,5 @@
2title: Hakyll's functionField
2title: Hakyll's functionField
3author: Beerend Lauwers
3author: Beerend Lauwers
4url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
4url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-github-pages-circle-ci.md+1−1
@@ -2,5 +2,5 @@
2title: Create a GitHub page with Hakyll and CircleCI
2title: Create a GitHub page with Hakyll and CircleCI
3author: Juan Pedro Villa
3author: Juan Pedro Villa
4url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
4url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
5−external: true
5+type: external
6---
6---
web/tutorials/external-inlining-and-compressing-css.md+1−1
@@ -2,5 +2,5 @@
2title: Inlining and compressing CSS
2title: Inlining and compressing CSS
3author: Kyle Marek-Spartz
3author: Kyle Marek-Spartz
4url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
4url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-live-reload.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Live Reloading with Hakyll
3−author: Ben Kolera
4−url: 'http://benkolera.com/posts/2015-09-14-hakyll_livereload.html'
5−external: true
6−---
web/tutorials/faq.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: FAQ
2title: FAQ
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
7## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
web/tutorials/github-pages-tutorial.md+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using Hakyll with GitHub Pages
2title: Using Hakyll with GitHub Pages
3author: Erik Stevenson
3author: Erik Stevenson
4+type: article
4---
5---
5
6
6## Introduction
7## Introduction
web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Hakyll 3 to Hakyll 4 migration guide
2title: Hakyll 3 to Hakyll 4 migration guide
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6Introduction
7Introduction
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 1: Setup & Initial Customization'
3+url: 'https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 2: Generating a Sitemap XML File'
3+url: 'https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 3: Generating RSS and Atom XML Feeds'
3+url: 'https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-04-copying-static-files-for-your-build.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 4: Copying Static Files For Your Build'
3+url: 'https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 5 – Generating Custom Post Filenames From a Title Slug'
3+url: 'https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html'
4+type: robertwpearce
5+---
web/tutorials/using-clay-with-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using Clay with Hakyll
2title: Using Clay with Hakyll
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
7[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
web/tutorials/using-teasers-in-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using teasers in Hakyll
2title: Using teasers in Hakyll
3author: Ivan Veselov
3author: Ivan Veselov
4+type: article
4---
5---
5
6
6## Introduction
7## Introduction