on master help sign in
Merge branch 'master' into dependency-analyzer
80596b1Jasper Van der Jeugt
examples/brochure/hakyll.hs+10−8
@@ -6,13 +6,15 @@ import Hakyll
6
6
7main :: IO ()
7main :: IO ()
8main = hakyll $ do
8main = hakyll $ do
9+ match "css/*" $ do
9− route "css/*" idRoute
10+ route idRoute
10− compile "css/*" compressCssCompiler
11+ compile compressCssCompiler
11
12
12− compile "templates/*" templateCompiler
13+ match "templates/*" $ compile templateCompiler
13
14
14− forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page -> do
15+ forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page ->
16+ match page $ do
15− route page $ setExtension "html"
17+ route $ setExtension "html"
16− compile page $ pageCompiler
18+ compile $ pageCompiler
17− >>> applyTemplateCompiler "templates/default.html"
19+ >>> applyTemplateCompiler "templates/default.html"
18− >>> relativizeUrlsCompiler
20+ >>> relativizeUrlsCompiler
examples/feedblog/hakyll.hs+22−26
@@ -11,47 +11,43 @@ import Hakyll
11main :: IO ()
11main :: IO ()
12main = hakyll $ do
12main = hakyll $ do
13 -- Compress CSS
13 -- Compress CSS
14+ match "css/*" $ do
14− route "css/*" idRoute
15+ route idRoute
15− compile "css/*" compressCssCompiler
16+ compile compressCssCompiler
16
17
17 -- Render posts
18 -- Render posts
19+ match "posts/*" $ do
18− route "posts/*" $ setExtension ".html"
20+ route $ setExtension ".html"
19− compile "posts/*" $
20− pageCompiler
21+ compile $ pageCompiler
21 >>> applyTemplateCompiler "templates/post.html"
22 >>> applyTemplateCompiler "templates/post.html"
22 >>> applyTemplateCompiler "templates/default.html"
23 >>> applyTemplateCompiler "templates/default.html"
23 >>> relativizeUrlsCompiler
24 >>> relativizeUrlsCompiler
24
25
25 -- Render posts list
26 -- Render posts list
26− route "posts.html" $ idRoute
27+ match "posts.html" $ route idRoute
27− create "posts.html" $
28+ create "posts.html" $ constA mempty
28− constA mempty
29− >>> arr (setField "title" "All posts")
29+ >>> arr (setField "title" "All posts")
30− >>> requireAllA "posts/*" addPostList
30+ >>> requireAllA "posts/*" addPostList
31− >>> applyTemplateCompiler "templates/posts.html"
31+ >>> applyTemplateCompiler "templates/posts.html"
32− >>> applyTemplateCompiler "templates/default.html"
32+ >>> applyTemplateCompiler "templates/default.html"
33− >>> relativizeUrlsCompiler
33+ >>> relativizeUrlsCompiler
34
34
35 -- Index
35 -- Index
36− route "index.html" $ idRoute
36+ match "index.html" $ route idRoute
37− create "index.html" $
37+ create "index.html" $ constA mempty
38− constA mempty
39− >>> arr (setField "title" "Home")
38+ >>> arr (setField "title" "Home")
40− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
39+ >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
41− >>> applyTemplateCompiler "templates/index.html"
40+ >>> applyTemplateCompiler "templates/index.html"
42− >>> applyTemplateCompiler "templates/default.html"
41+ >>> applyTemplateCompiler "templates/default.html"
43− >>> relativizeUrlsCompiler
42+ >>> relativizeUrlsCompiler
44
43
45 -- Render RSS feed
44 -- Render RSS feed
46− route "rss.xml" $ idRoute
45+ match "rss.xml" $ route idRoute
47 create "rss.xml" $
46 create "rss.xml" $
48 requireAll_ "posts/*" >>> renderRss feedConfiguration
47 requireAll_ "posts/*" >>> renderRss feedConfiguration
49
48
50 -- Read templates
49 -- Read templates
51− compile "templates/*" templateCompiler
50+ match "templates/*" $ compile templateCompiler
52−
53− -- End
54− return ()
55
51
56-- | Auxiliary compiler: generate a post list from a list of given posts, and
52-- | Auxiliary compiler: generate a post list from a list of given posts, and
57-- add it to the current page under @$posts@
53-- add it to the current page under @$posts@
examples/hakyll/about.markdown+1
@@ -33,3 +33,4 @@ who still maintains the package. Contributors:
33- [sargon](http://github.com/sargon)
33- [sargon](http://github.com/sargon)
34- [Paolo Veronelli](http://github.com/paolino)
34- [Paolo Veronelli](http://github.com/paolino)
35- [Benedict Eastaugh](http://extralogical.net/)
35- [Benedict Eastaugh](http://extralogical.net/)
36+- [Nicolas Wu](http://zenzike.com/)
examples/hakyll/changelog.markdown+5
@@ -2,6 +2,11 @@
2title: Changelog
2title: Changelog
3---
3---
4
4
5+## Hakyll 3.1
6+
7+- New `match` function in rules DSL
8+- More expressive `Pattern`s
9+
5## Hakyll 3
10## Hakyll 3
6
11
7- Complete rewrite
12- Complete rewrite
examples/hakyll/hakyll.hs+18−16
@@ -6,35 +6,37 @@ import Text.Pandoc
6
6
7main :: IO ()
7main :: IO ()
8main = hakyll $ do
8main = hakyll $ do
9+ match "css/*" $ do
9− route "css/*" idRoute
10+ route idRoute
10− compile "css/*" compressCssCompiler
11+ compile compressCssCompiler
11
12
12 -- Static directories
13 -- Static directories
13− forM_ ["images/*", "examples/*", "reference/*"] $ \f -> do
14+ forM_ ["images/*", "examples/*", "reference/*"] $ \f -> match f $ do
14− route f idRoute
15+ route idRoute
15− compile f copyFileCompiler
16+ compile copyFileCompiler
16
17
17 -- Pages
18 -- Pages
18− forM_ pages $ \p -> do
19+ forM_ pages $ \p -> match p $ do
19− route p $ setExtension "html"
20+ route $ setExtension "html"
20− compile p $ pageCompiler
21+ compile $ pageCompiler
21 >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
22 >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
22 >>> applyTemplateCompiler "templates/default.html"
23 >>> applyTemplateCompiler "templates/default.html"
23 >>> relativizeUrlsCompiler
24 >>> relativizeUrlsCompiler
24
25
25 -- Tutorial
26 -- Tutorial
27+ match "tutorial.markdown" $ do
26− route "tutorial.markdown" $ setExtension "html"
28+ route $ setExtension "html"
27− compile "tutorial.markdown" $ readPageCompiler
29+ compile $ readPageCompiler
28− >>> pageRenderPandocWith defaultHakyllParserState withToc
30+ >>> pageRenderPandocWith defaultHakyllParserState withToc
29− >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
31+ >>> requireA "sidebar.markdown" (setFieldA "sidebar" $ arr pageBody)
30− >>> applyTemplateCompiler "templates/default.html"
32+ >>> applyTemplateCompiler "templates/default.html"
31− >>> relativizeUrlsCompiler
33+ >>> relativizeUrlsCompiler
32
34
33 -- Sidebar
35 -- Sidebar
34− compile "sidebar.markdown" pageCompiler
36+ match "sidebar.markdown" $ compile pageCompiler
35
37
36 -- Templates
38 -- Templates
37− compile "templates/*" templateCompiler
39+ match "templates/*" $ compile templateCompiler
38 where
40 where
39 withToc = defaultHakyllWriterOptions
41 withToc = defaultHakyllWriterOptions
40 { writerTableOfContents = True
42 { writerTableOfContents = True
examples/hakyll/tutorial.markdown+33−22
@@ -65,16 +65,18 @@ import Hakyll
65
65
66main :: IO ()
66main :: IO ()
67main = hakyll $ do
67main = hakyll $ do
68+ match "css/*" $ do
68− route "css/*" idRoute
69+ route idRoute
69− compile "css/*" compressCssCompiler
70+ compile compressCssCompiler
70−
71+
71− compile "templates/*" templateCompiler
72+ match "templates/*" $ compile templateCompiler
72−
73+
73− forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page -> do
74+ forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page ->
75+ match page $ do
74− route page $ setExtension "html"
76+ route $ setExtension "html"
75− compile page $ pageCompiler
77+ compile $ pageCompiler
76− >>> applyTemplateCompiler "templates/default.html"
78+ >>> applyTemplateCompiler "templates/default.html"
77− >>> relativizeUrlsCompiler
79+ >>> relativizeUrlsCompiler
78~~~~~
80~~~~~
79
81
80This is enough code to create a small brochure site! You can find all code
82This is enough code to create a small brochure site! You can find all code
@@ -111,12 +113,11 @@ main :: IO ()
111main = hakyll $ do
113main = hakyll $ do
112~~~~~
114~~~~~
113
115
114−The `RulesM` monad is composed of a few functions. A first important one is
116+The `RulesM` monad is composed of a few functions. Seldomly, you want to apply a
115−`route`: this creates a new rule for routing items. This rule is applied to all
117+compiler to *all* resources. You want to apply a compiler to certain files
116−items it matches -- and matching is done using the `"css/*"` [pattern].
118+instead. That's why the `match` function exists. First, let's handle the CSS of
117−`idRoute` simply means that an item will be routed to it's own filename. For
119+our file. We use a `"css/*"` [Pattern] to match all files in the `css/`
118−example, `css/screen.css` will be routed to `css/screen.css` -- not very
120+directory.
119−exciting.
120
121
121Note that a [Pattern] matches an [Identifier], it doesn't match filenames.
122Note that a [Pattern] matches an [Identifier], it doesn't match filenames.
122
123
@@ -124,7 +125,16 @@ Note that a [Pattern] matches an [Identifier], it doesn't match filenames.
124[Identifier]: /reference/Hakyll-Core-Identifier.html
125[Identifier]: /reference/Hakyll-Core-Identifier.html
125
126
126~~~~~{.haskell}
127~~~~~{.haskell}
128+match "css/*" $ do
129+~~~~~
130+
131+`route` creates a new rule for routing items. This rule is applied to all items
132+that are currently matched -- in this case, `"css/*"`. `idRoute` simply means
133+that an item will be routed to it's own filename. For example, `css/screen.css`
134+will be routed to `css/screen.css` -- not very exciting.
135+
136+~~~~~{.haskell}
127−route "css/*" idRoute
137+route idRoute
128~~~~~
138~~~~~
129
139
130Apart from specifying where the items should go (using `route`), we also have to
140Apart from specifying where the items should go (using `route`), we also have to
@@ -135,12 +145,12 @@ good default compilers. The `compressCssCompiler` compiler will simply compress
135the CSS found in the files.
145the CSS found in the files.
136
146
137~~~~~{.haskell}
147~~~~~{.haskell}
138−compile "css/*" compressCssCompiler
148+compile compressCssCompiler
139~~~~~
149~~~~~
140
150
141Next, we're going to render some pages. We're going to style the results a
151Next, we're going to render some pages. We're going to style the results a
142little, so we're going to need a [Template]. We simply compile a template using
152little, so we're going to need a [Template]. We simply compile a template using
143−the `defaultTemplateRead` compiler, it's good enough in most cases.
153+the `templateCompiler` compiler, it's good enough in most cases.
144
154
145[Template]: /reference/Hakyll-Web-Template.html
155[Template]: /reference/Hakyll-Web-Template.html
146
156
@@ -148,7 +158,7 @@ We don't use a route for these templates, after all, we don't want to route them
148anywhere, we just want to use them to style our pages a little.
158anywhere, we just want to use them to style our pages a little.
149
159
150~~~~~{.haskell}
160~~~~~{.haskell}
151−compile "templates/*" templateCompiler
161+match "templates/*" $ compile templateCompiler
152~~~~~
162~~~~~
153
163
154We can conclude that some rules do not *directly* add an output page on our
164We can conclude that some rules do not *directly* add an output page on our
@@ -164,13 +174,14 @@ manually).
164
174
165~~~~~{.haskell}
175~~~~~{.haskell}
166forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page -> do
176forM_ ["about.rst", "index.markdown", "code.lhs"] $ \page -> do
177+ match page $ do
167~~~~~
178~~~~~
168
179
169The pages all have different extensions. In our website, we only want to see
180The pages all have different extensions. In our website, we only want to see
170`.html` files. Hakyll provides a route to do just that:
181`.html` files. Hakyll provides a route to do just that:
171
182
172~~~~~{.haskell}
183~~~~~{.haskell}
173−route page $ setExtension "html"
184+route setExtension "html"
174~~~~~
185~~~~~
175
186
176The [Rules] reference page has a complete listing of the API used.
187The [Rules] reference page has a complete listing of the API used.
@@ -189,7 +200,7 @@ reference page has some more readable information on this subject.
189[Compiler]: /reference/Hakyll-Core-Compiler.html
200[Compiler]: /reference/Hakyll-Core-Compiler.html
190
201
191~~~~~{.haskell}
202~~~~~{.haskell}
192−compile page $ pageCompiler
203+compile pageCompiler
193 >>> applyTemplateCompiler "templates/default.html"
204 >>> applyTemplateCompiler "templates/default.html"
194 >>> relativizeUrlsCompiler
205 >>> relativizeUrlsCompiler
195~~~~~
206~~~~~
examples/morepages/hakyll.hs+12−11
@@ -9,20 +9,21 @@ import Hakyll
9main :: IO ()
9main :: IO ()
10main = hakyll $ do
10main = hakyll $ do
11 -- Compress CSS
11 -- Compress CSS
12+ match "css/*" $ do
12− route "css/*" idRoute
13+ route idRoute
13− compile "css/*" compressCssCompiler
14+ compile compressCssCompiler
14
15
15 -- Render static pages
16 -- Render static pages
16− forM_ ["about.markdown", "index.markdown", "products.markdown"] $ \p -> do
17+ forM_ ["about.markdown", "index.markdown", "products.markdown"] $ \p ->
18+ match p $ do
17− route p $ setExtension ".html"
19+ route $ setExtension ".html"
18− compile p $
19− pageCompiler
20+ compile $ pageCompiler
20− >>> requireA "footer.markdown" (setFieldA "footer" $ arr pageBody)
21+ >>> requireA "footer.markdown" (setFieldA "footer" $ arr pageBody)
21− >>> applyTemplateCompiler "templates/default.html"
22+ >>> applyTemplateCompiler "templates/default.html"
22− >>> relativizeUrlsCompiler
23+ >>> relativizeUrlsCompiler
23
24
24 -- Compile footer
25 -- Compile footer
25− compile "footer.markdown" pageCompiler
26+ match "footer.markdown" $ compile pageCompiler
26
27
27 -- Read templates
28 -- Read templates
28− compile "templates/*" templateCompiler
29+ match "templates/*" $ compile templateCompiler
examples/simpleblog/hakyll.hs+21−25
@@ -11,42 +11,38 @@ import Hakyll
11main :: IO ()
11main :: IO ()
12main = hakyll $ do
12main = hakyll $ do
13 -- Compress CSS
13 -- Compress CSS
14+ match "css/*" $ do
14− route "css/*" idRoute
15+ route idRoute
15− compile "css/*" compressCssCompiler
16+ compile compressCssCompiler
16
17
17 -- Render posts
18 -- Render posts
19+ match "posts/*" $ do
18− route "posts/*" $ setExtension ".html"
20+ route $ setExtension ".html"
19− compile "posts/*" $
20− pageCompiler
21+ compile $ pageCompiler
21 >>> applyTemplateCompiler "templates/post.html"
22 >>> applyTemplateCompiler "templates/post.html"
22 >>> applyTemplateCompiler "templates/default.html"
23 >>> applyTemplateCompiler "templates/default.html"
23 >>> relativizeUrlsCompiler
24 >>> relativizeUrlsCompiler
24
25
25 -- Render posts list
26 -- Render posts list
26− route "posts.html" $ idRoute
27+ match "posts.html" $ route idRoute
27− create "posts.html" $
28+ create "posts.html" $ constA mempty
28− constA mempty
29− >>> arr (setField "title" "All posts")
29+ >>> arr (setField "title" "All posts")
30− >>> requireAllA "posts/*" addPostList
30+ >>> requireAllA "posts/*" addPostList
31− >>> applyTemplateCompiler "templates/posts.html"
31+ >>> applyTemplateCompiler "templates/posts.html"
32− >>> applyTemplateCompiler "templates/default.html"
32+ >>> applyTemplateCompiler "templates/default.html"
33− >>> relativizeUrlsCompiler
33+ >>> relativizeUrlsCompiler
34
34
35 -- Index
35 -- Index
36− route "index.html" idRoute
36+ match "index.html" $ route idRoute
37− create "index.html" $
37+ create "index.html" $ constA mempty
38− constA mempty
39− >>> arr (setField "title" "Home")
38+ >>> arr (setField "title" "Home")
40− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
39+ >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
41− >>> applyTemplateCompiler "templates/index.html"
40+ >>> applyTemplateCompiler "templates/index.html"
42− >>> applyTemplateCompiler "templates/default.html"
41+ >>> applyTemplateCompiler "templates/default.html"
43− >>> relativizeUrlsCompiler
42+ >>> relativizeUrlsCompiler
44
43
45 -- Read templates
44 -- Read templates
46− compile "templates/*" templateCompiler
45+ match "templates/*" $ compile templateCompiler
47−
48− -- End
49− return ()
50
46
51-- | Auxiliary compiler: generate a post list from a list of given posts, and
47-- | Auxiliary compiler: generate a post list from a list of given posts, and
52-- add it to the current page under @$posts@
48-- add it to the current page under @$posts@
examples/tagblog/hakyll.hs+24−28
@@ -12,13 +12,14 @@ import Hakyll
12main :: IO ()
12main :: IO ()
13main = hakyll $ do
13main = hakyll $ do
14 -- Compress CSS
14 -- Compress CSS
15+ match "css/*" $ do
15− route "css/*" idRoute
16+ route idRoute
16− compile "css/*" compressCssCompiler
17+ compile compressCssCompiler
17
18
18 -- Render posts
19 -- Render posts
20+ match "posts/*" $ do
19− route "posts/*" $ setExtension ".html"
21+ route $ setExtension ".html"
20− compile "posts/*" $
21− pageCompiler
22+ compile $ pageCompiler
22 >>> arr (renderDateField "date" "%B %e, %Y" "Date unknown")
23 >>> arr (renderDateField "date" "%B %e, %Y" "Date unknown")
23 >>> renderTagsField "prettytags" (fromCaptureString "tags/*")
24 >>> renderTagsField "prettytags" (fromCaptureString "tags/*")
24 >>> applyTemplateCompiler "templates/post.html"
25 >>> applyTemplateCompiler "templates/post.html"
@@ -26,48 +27,43 @@ main = hakyll $ do
26 >>> relativizeUrlsCompiler
27 >>> relativizeUrlsCompiler
27
28
28 -- Render posts list
29 -- Render posts list
29− route "posts.html" $ idRoute
30+ match "posts.html" $ route idRoute
30− create "posts.html" $
31+ create "posts.html" $ constA mempty
31− constA mempty
32− >>> arr (setField "title" "All posts")
32+ >>> arr (setField "title" "All posts")
33− >>> requireAllA "posts/*" addPostList
33+ >>> requireAllA "posts/*" addPostList
34− >>> applyTemplateCompiler "templates/posts.html"
34+ >>> applyTemplateCompiler "templates/posts.html"
35− >>> applyTemplateCompiler "templates/default.html"
35+ >>> applyTemplateCompiler "templates/default.html"
36− >>> relativizeUrlsCompiler
36+ >>> relativizeUrlsCompiler
37
37
38 -- Index
38 -- Index
39− route "index.html" $ idRoute
39+ match "index.html" $ route idRoute
40− create "index.html" $
40+ create "index.html" $ constA mempty
41− constA mempty
42− >>> arr (setField "title" "Home")
41+ >>> arr (setField "title" "Home")
43− >>> requireA "tags" (setFieldA "tagcloud" (renderTagCloud'))
42+ >>> requireA "tags" (setFieldA "tagcloud" (renderTagCloud'))
44− >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
43+ >>> requireAllA "posts/*" (id *** arr (take 3 . reverse . sortByBaseName) >>> addPostList)
45− >>> applyTemplateCompiler "templates/index.html"
44+ >>> applyTemplateCompiler "templates/index.html"
46− >>> applyTemplateCompiler "templates/default.html"
45+ >>> applyTemplateCompiler "templates/default.html"
47− >>> relativizeUrlsCompiler
46+ >>> relativizeUrlsCompiler
48
47
49 -- Tags
48 -- Tags
50 create "tags" $
49 create "tags" $
51 requireAll "posts/*" (\_ ps -> readTags ps :: Tags String)
50 requireAll "posts/*" (\_ ps -> readTags ps :: Tags String)
52
51
53 -- Add a tag list compiler for every tag
52 -- Add a tag list compiler for every tag
54− route "tags/*" $ setExtension ".html"
53+ match "tags/*" $ route $ setExtension ".html"
55 metaCompile $ require_ "tags"
54 metaCompile $ require_ "tags"
56 >>> arr (M.toList . tagsMap)
55 >>> arr (M.toList . tagsMap)
57 >>> arr (map (\(t, p) -> (tagIdentifier t, makeTagList t p)))
56 >>> arr (map (\(t, p) -> (tagIdentifier t, makeTagList t p)))
58
57
59 -- Render RSS feed
58 -- Render RSS feed
60− route "rss.xml" $ idRoute
59+ match "rss.xml" $ route idRoute
61 create "rss.xml" $
60 create "rss.xml" $
62 requireAll_ "posts/*"
61 requireAll_ "posts/*"
63 >>> mapCompiler (arr $ copyBodyToField "description")
62 >>> mapCompiler (arr $ copyBodyToField "description")
64 >>> renderRss feedConfiguration
63 >>> renderRss feedConfiguration
65
64
66 -- Read templates
65 -- Read templates
67− compile "templates/*" templateCompiler
66+ match "templates/*" $ compile templateCompiler
68−
69− -- End
70− return ()
71 where
67 where
72 renderTagCloud' :: Compiler (Tags String) String
68 renderTagCloud' :: Compiler (Tags String) String
73 renderTagCloud' = renderTagCloud tagIdentifier 100 120
69 renderTagCloud' = renderTagCloud tagIdentifier 100 120
hakyll.cabal+4−3
@@ -1,5 +1,5 @@
1Name: hakyll
1Name: hakyll
2−Version: 3.0.2.0
2+Version: 3.1.0.0
3
3
4Synopsis: A simple static site generator library.
4Synopsis: A simple static site generator library.
5Description: A simple static site generator library, mainly aimed at
5Description: A simple static site generator library, mainly aimed at
@@ -81,7 +81,6 @@ library
81 Hakyll.Web.RelativizeUrls
81 Hakyll.Web.RelativizeUrls
82 Hakyll.Web.Page.Read
82 Hakyll.Web.Page.Read
83 Hakyll.Web.Page.Metadata
83 Hakyll.Web.Page.Metadata
84− Hakyll.Core.ResourceProvider.FileResourceProvider
85 Hakyll.Core.Configuration
84 Hakyll.Core.Configuration
86 Hakyll.Core.DependencyAnalyzer
85 Hakyll.Core.DependencyAnalyzer
87 Hakyll.Core.Identifier.Pattern
86 Hakyll.Core.Identifier.Pattern
@@ -89,7 +88,9 @@ library
89 Hakyll.Core.Util.Arrow
88 Hakyll.Core.Util.Arrow
90 Hakyll.Core.Util.File
89 Hakyll.Core.Util.File
91 Hakyll.Core.Util.String
90 Hakyll.Core.Util.String
92− Hakyll.Core.ResourceProvider
91+ Hakyll.Core.Resource
92+ Hakyll.Core.Resource.Provider
93+ Hakyll.Core.Resource.Provider.File
93 Hakyll.Core.CompiledItem
94 Hakyll.Core.CompiledItem
94 Hakyll.Core.Compiler
95 Hakyll.Core.Compiler
95 Hakyll.Core.Run
96 Hakyll.Core.Run
src-inotify/Hakyll/Web/Preview/Poll.hs+1−1
@@ -13,7 +13,7 @@ import Data.List (isPrefixOf)
13import System.INotify
13import System.INotify
14
14
15import Hakyll.Core.Configuration
15import Hakyll.Core.Configuration
16−import Hakyll.Core.ResourceProvider
16+import Hakyll.Core.Resource
17import Hakyll.Core.Identifier
17import Hakyll.Core.Identifier
18
18
19-- | Calls the given callback when the directory tree changes
19-- | Calls the given callback when the directory tree changes
src-interval/Hakyll/Web/Preview/Poll.hs+1−1
@@ -15,7 +15,7 @@ import System.Directory (getModificationTime)
15
15
16import Hakyll.Core.Configuration
16import Hakyll.Core.Configuration
17import Hakyll.Core.Identifier
17import Hakyll.Core.Identifier
18−import Hakyll.Core.ResourceProvider
18+import Hakyll.Core.Resource
19
19
20-- | A preview thread that periodically recompiles the site.
20-- | A preview thread that periodically recompiles the site.
21--
21--
src/Hakyll.hs+4−2
@@ -5,7 +5,8 @@ module Hakyll
5 , module Hakyll.Core.Configuration
5 , module Hakyll.Core.Configuration
6 , module Hakyll.Core.Identifier
6 , module Hakyll.Core.Identifier
7 , module Hakyll.Core.Identifier.Pattern
7 , module Hakyll.Core.Identifier.Pattern
8− , module Hakyll.Core.ResourceProvider
8+ , module Hakyll.Core.Resource
9+ , module Hakyll.Core.Resource.Provider
9 , module Hakyll.Core.Routes
10 , module Hakyll.Core.Routes
10 , module Hakyll.Core.Rules
11 , module Hakyll.Core.Rules
11 , module Hakyll.Core.UnixFilter
12 , module Hakyll.Core.UnixFilter
@@ -34,7 +35,8 @@ import Hakyll.Core.Compiler
34import Hakyll.Core.Configuration
35import Hakyll.Core.Configuration
35import Hakyll.Core.Identifier
36import Hakyll.Core.Identifier
36import Hakyll.Core.Identifier.Pattern
37import Hakyll.Core.Identifier.Pattern
37−import Hakyll.Core.ResourceProvider
38+import Hakyll.Core.Resource
39+import Hakyll.Core.Resource.Provider
38import Hakyll.Core.Routes
40import Hakyll.Core.Routes
39import Hakyll.Core.Rules
41import Hakyll.Core.Rules
40import Hakyll.Core.UnixFilter
42import Hakyll.Core.UnixFilter
src/Hakyll/Core/Compiler.hs+3−2
@@ -124,7 +124,8 @@ import Hakyll.Core.Identifier
124import Hakyll.Core.Identifier.Pattern
124import Hakyll.Core.Identifier.Pattern
125import Hakyll.Core.CompiledItem
125import Hakyll.Core.CompiledItem
126import Hakyll.Core.Writable
126import Hakyll.Core.Writable
127−import Hakyll.Core.ResourceProvider
127+import Hakyll.Core.Resource
128+import Hakyll.Core.Resource.Provider
128import Hakyll.Core.Compiler.Internal
129import Hakyll.Core.Compiler.Internal
129import Hakyll.Core.Store
130import Hakyll.Core.Store
130import Hakyll.Core.Rules.Internal
131import Hakyll.Core.Rules.Internal
@@ -237,7 +238,7 @@ requireAll_ :: (Binary a, Typeable a, Writable a)
237 -> Compiler b [a]
238 -> Compiler b [a]
238requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
239requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
239 where
240 where
240− getDeps = matches pattern . map unResource . resourceList
241+ getDeps = filterMatches pattern . map unResource . resourceList
241 requireAll_' = const $ CompilerM $ do
242 requireAll_' = const $ CompilerM $ do
242 deps <- getDeps . compilerResourceProvider <$> ask
243 deps <- getDeps . compilerResourceProvider <$> ask
243 mapM (unCompilerM . getDependency) deps
244 mapM (unCompilerM . getDependency) deps
src/Hakyll/Core/Compiler/Internal.hs+1−1
@@ -26,7 +26,7 @@ import Control.Category (Category, (.), id)
26import Control.Arrow (Arrow, ArrowChoice, arr, first, left)
26import Control.Arrow (Arrow, ArrowChoice, arr, first, left)
27
27
28import Hakyll.Core.Identifier
28import Hakyll.Core.Identifier
29−import Hakyll.Core.ResourceProvider
29+import Hakyll.Core.Resource.Provider
30import Hakyll.Core.Store
30import Hakyll.Core.Store
31import Hakyll.Core.Routes
31import Hakyll.Core.Routes
32import Hakyll.Core.Logger
32import Hakyll.Core.Logger
src/Hakyll/Core/Identifier/Pattern.hs+86−42
@@ -1,4 +1,12 @@
1-- | Module providing pattern matching and capturing on 'Identifier's.
1-- | Module providing pattern matching and capturing on 'Identifier's.
2+-- 'Pattern's come in two kinds:
3+--
4+-- * Simple glob patterns, like @foo\/*@;
5+--
6+-- * Custom, arbitrary predicates of the type @Identifier -> Bool@.
7+--
8+-- They both have advantages and disadvantages. By default, globs are used,
9+-- unless you construct your 'Pattern' using the 'predicate' function.
2--
10--
3-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
11-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
4-- only match the exact @foo\/bar@ identifier.
12-- only match the exact @foo\/bar@ identifier.
@@ -20,15 +28,17 @@
20--
28--
21-- * @foo\/*.html@ will match all HTML files in the @foo\/@ directory.
29-- * @foo\/*.html@ will match all HTML files in the @foo\/@ directory.
22--
30--
23−-- The 'match' function allows the user to get access to the elements captured
31+-- The 'capture' function allows the user to get access to the elements captured
24-- by the capture elements in the pattern.
32-- by the capture elements in the pattern.
25--
33--
26module Hakyll.Core.Identifier.Pattern
34module Hakyll.Core.Identifier.Pattern
27 ( Pattern
35 ( Pattern
28− , parsePattern
36+ , parseGlob
29− , match
37+ , predicate
30− , doesMatch
38+ , regex
31 , matches
39 , matches
40+ , filterMatches
41+ , capture
32 , fromCapture
42 , fromCapture
33 , fromCaptureString
43 , fromCaptureString
34 , fromCaptures
44 , fromCaptures
@@ -37,32 +47,39 @@ module Hakyll.Core.Identifier.Pattern
37import Data.List (isPrefixOf, inits, tails)
47import Data.List (isPrefixOf, inits, tails)
38import Control.Arrow ((&&&), (>>>))
48import Control.Arrow ((&&&), (>>>))
39import Control.Monad (msum)
49import Control.Monad (msum)
40−import Data.Maybe (isJust)
50+import Data.Maybe (isJust, fromMaybe)
41−import Data.Monoid (mempty, mappend)
51+import Data.Monoid (Monoid, mempty, mappend)
42
52
43import GHC.Exts (IsString, fromString)
53import GHC.Exts (IsString, fromString)
54+import Text.Regex.PCRE ((=~~))
44
55
45import Hakyll.Core.Identifier
56import Hakyll.Core.Identifier
46
57
47-- | One base element of a pattern
58-- | One base element of a pattern
48--
59--
49−data PatternComponent = Capture
60+data GlobComponent = Capture
50− | CaptureMany
61+ | CaptureMany
51− | Literal String
62+ | Literal String
52− deriving (Eq, Show)
63+ deriving (Eq, Show)
53
64
54-- | Type that allows matching on identifiers
65-- | Type that allows matching on identifiers
55--
66--
56−newtype Pattern = Pattern {unPattern :: [PatternComponent]}
67+data Pattern = Glob [GlobComponent]
57− deriving (Eq, Show)
68+ | Predicate (Identifier -> Bool)
58
69
59instance IsString Pattern where
70instance IsString Pattern where
60− fromString = parsePattern
71+ fromString = parseGlob
72+
73+instance Monoid Pattern where
74+ mempty = Predicate (const True)
75+ g@(Glob _) `mappend` x = Predicate (matches g) `mappend` x
76+ x `mappend` g@(Glob _) = x `mappend` Predicate (matches g)
77+ Predicate f `mappend` Predicate g = Predicate $ \i -> f i && g i
61
78
62-- | Parse a pattern from a string
79-- | Parse a pattern from a string
63--
80--
64−parsePattern :: String -> Pattern
81+parseGlob :: String -> Pattern
65−parsePattern = Pattern . parse' -- undefined -- Pattern . map toPattern . unIdentifier . parseIdentifier
82+parseGlob = Glob . parse'
66 where
83 where
67 parse' str =
84 parse' str =
68 let (chunk, rest) = break (`elem` "\\*") str
85 let (chunk, rest) = break (`elem` "\\*") str
@@ -72,20 +89,34 @@ parsePattern = Pattern . parse' -- undefined -- Pattern . map toPattern . unIden
72 ('*' : xs) -> Literal chunk : Capture : parse' xs
89 ('*' : xs) -> Literal chunk : Capture : parse' xs
73 xs -> Literal chunk : Literal xs : []
90 xs -> Literal chunk : Literal xs : []
74
91
75−-- | Match an identifier against a pattern, generating a list of captures
92+-- | Create a 'Pattern' from an arbitrary predicate
93+--
94+-- Example:
95+--
96+-- > predicate (\i -> matches "foo/*" i && not (matches "foo/bar" i))
97+--
98+predicate :: (Identifier -> Bool) -> Pattern
99+predicate = Predicate
100+
101+-- | Create a 'Pattern' from a regex
102+--
103+-- Example:
104+--
105+-- > regex "^foo/[^x]*$
76--
106--
77−match :: Pattern -> Identifier -> Maybe [Identifier]
107+regex :: String -> Pattern
78−match p (Identifier i) = fmap (map Identifier) $ match' (unPattern p) i
108+regex str = predicate $ fromMaybe False . (=~~ str) . toFilePath
79
109
80-- | Check if an identifier matches a pattern
110-- | Check if an identifier matches a pattern
81--
111--
82−doesMatch :: Pattern -> Identifier -> Bool
112+matches :: Pattern -> Identifier -> Bool
83−doesMatch p = isJust . match p
113+matches (Glob p) = isJust . capture (Glob p)
114+matches (Predicate p) = (p $)
84
115
85-- | Given a list of identifiers, retain only those who match the given pattern
116-- | Given a list of identifiers, retain only those who match the given pattern
86--
117--
87−matches :: Pattern -> [Identifier] -> [Identifier]
118+filterMatches :: Pattern -> [Identifier] -> [Identifier]
88−matches p = filter (doesMatch p)
119+filterMatches = filter . matches
89
120
90-- | Split a list at every possible point, generate a list of (init, tail)
121-- | Split a list at every possible point, generate a list of (init, tail)
91-- cases. The result is sorted with inits decreasing in length.
122-- cases. The result is sorted with inits decreasing in length.
@@ -93,30 +124,35 @@ matches p = filter (doesMatch p)
93splits :: [a] -> [([a], [a])]
124splits :: [a] -> [([a], [a])]
94splits = inits &&& tails >>> uncurry zip >>> reverse
125splits = inits &&& tails >>> uncurry zip >>> reverse
95
126
96−-- | Internal verion of 'match'
127+-- | Match a glob against a pattern, generating a list of captures
97--
128--
129+capture :: Pattern -> Identifier -> Maybe [Identifier]
130+capture (Glob p) (Identifier i) = fmap (map Identifier) $ capture' p i
131+capture (Predicate _) _ = Nothing
132+
133+-- | Internal verion of 'capture'
134+--
98−match' :: [PatternComponent] -> String -> Maybe [String]
135+capture' :: [GlobComponent] -> String -> Maybe [String]
99−match' [] [] = Just [] -- An empty match
136+capture' [] [] = Just [] -- An empty match
100−match' [] _ = Nothing -- No match
137+capture' [] _ = Nothing -- No match
101−-- match' _ [] = Nothing -- No match
102−match' (Literal l : ms) str
138+capture' (Literal l : ms) str
103 -- Match the literal against the string
139 -- Match the literal against the string
104− | l `isPrefixOf` str = match' ms $ drop (length l) str
140+ | l `isPrefixOf` str = capture' ms $ drop (length l) str
105 | otherwise = Nothing
141 | otherwise = Nothing
106−match' (Capture : ms) str =
142+capture' (Capture : ms) str =
107 -- Match until the next /
143 -- Match until the next /
108 let (chunk, rest) = break (== '/') str
144 let (chunk, rest) = break (== '/') str
109− in msum $ [ fmap (i :) (match' ms (t ++ rest)) | (i, t) <- splits chunk ]
145+ in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
110−match' (CaptureMany : ms) str =
146+capture' (CaptureMany : ms) str =
111 -- Match everything
147 -- Match everything
112− msum $ [ fmap (i :) (match' ms t) | (i, t) <- splits str ]
148+ msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
113
149
114-- | Create an identifier from a pattern by filling in the captures with a given
150-- | Create an identifier from a pattern by filling in the captures with a given
115-- string
151-- string
116--
152--
117-- Example:
153-- Example:
118--
154--
119−-- > fromCapture (parsePattern "tags/*") (parseIdentifier "foo")
155+-- > fromCapture (parseGlob "tags/*") (parseIdentifier "foo")
120--
156--
121-- Result:
157-- Result:
122--
158--
@@ -128,7 +164,7 @@ fromCapture pattern = fromCaptures pattern . repeat
128-- | Simplified version of 'fromCapture' which takes a 'String' instead of an
164-- | Simplified version of 'fromCapture' which takes a 'String' instead of an
129-- 'Identifier'
165-- 'Identifier'
130--
166--
131−-- > fromCaptureString (parsePattern "tags/*") "foo"
167+-- > fromCaptureString (parseGlob "tags/*") "foo"
132--
168--
133-- Result:
169-- Result:
134--
170--
@@ -141,11 +177,19 @@ fromCaptureString pattern = fromCapture pattern . parseIdentifier
141-- given list of strings
177-- given list of strings
142--
178--
143fromCaptures :: Pattern -> [Identifier] -> Identifier
179fromCaptures :: Pattern -> [Identifier] -> Identifier
144−fromCaptures (Pattern []) _ = mempty
180+fromCaptures (Glob p) = fromCaptures' p
181+fromCaptures (Predicate _) = error $
182+ "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures called on a " ++
183+ "predicate instead of a glob"
184+
185+-- | Internally used version of 'fromCaptures'
186+--
187+fromCaptures' :: [GlobComponent] -> [Identifier] -> Identifier
188+fromCaptures' [] _ = mempty
145−fromCaptures (Pattern (m : ms)) [] = case m of
189+fromCaptures' (m : ms) [] = case m of
146− Literal l -> Identifier l `mappend` fromCaptures (Pattern ms) []
190+ Literal l -> Identifier l `mappend` fromCaptures' ms []
147− _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures: "
191+ _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
148 ++ "identifier list exhausted"
192 ++ "identifier list exhausted"
149−fromCaptures (Pattern (m : ms)) ids@(i : is) = case m of
193+fromCaptures' (m : ms) ids@(i : is) = case m of
150− Literal l -> Identifier l `mappend` fromCaptures (Pattern ms) ids
194+ Literal l -> Identifier l `mappend` fromCaptures' ms ids
151− _ -> i `mappend` fromCaptures (Pattern ms) is
195+ _ -> i `mappend` fromCaptures' ms is
src/Hakyll/Core/Resource.hsnew file+14
@@ -0,0 +1,14 @@
1+-- | Module exporting the simple 'Resource' type
2+--
3+module Hakyll.Core.Resource
4+ ( Resource (..)
5+ ) where
6+
7+import Hakyll.Core.Identifier
8+
9+-- | A resource
10+--
11+-- Invariant: the resource specified by the given identifier must exist
12+--
13+newtype Resource = Resource {unResource :: Identifier}
14+ deriving (Eq, Show, Ord)
src/Hakyll/Core/ResourceProvider.hs → src/Hakyll/Core/Resource/Provider.hsrenamed+27−11
@@ -10,16 +10,18 @@
10-- Therefore, it is not recommended to read files directly -- you should use the
10-- Therefore, it is not recommended to read files directly -- you should use the
11-- provided 'Resource' methods.
11-- provided 'Resource' methods.
12--
12--
13−module Hakyll.Core.ResourceProvider
13+module Hakyll.Core.Resource.Provider
14− ( Resource (..)
15− , ResourceProvider (..)
14+ ( ResourceProvider (..)
16 , resourceExists
15 , resourceExists
17 , resourceDigest
16 , resourceDigest
18 , resourceModified
17 , resourceModified
19 ) where
18 ) where
20
19
20+import Control.Concurrent (MVar, readMVar, modifyMVar_)
21import Control.Monad ((<=<))
21import Control.Monad ((<=<))
22import Data.Word (Word8)
22import Data.Word (Word8)
23+import Data.Map (Map)
24+import qualified Data.Map as M
23
25
24import qualified Data.ByteString.Lazy as LB
26import qualified Data.ByteString.Lazy as LB
25import OpenSSL.Digest.ByteString.Lazy (digest)
27import OpenSSL.Digest.ByteString.Lazy (digest)
@@ -27,13 +29,7 @@ import OpenSSL.Digest (MessageDigest (MD5))
27
29
28import Hakyll.Core.Identifier
30import Hakyll.Core.Identifier
29import Hakyll.Core.Store
31import Hakyll.Core.Store
30−
32+import Hakyll.Core.Resource
31−-- | A resource
32−--
33−-- Invariant: the resource specified by the given identifier must exist
34−--
35−newtype Resource = Resource {unResource :: Identifier}
36− deriving (Eq, Show, Ord)
37
33
38-- | A value responsible for retrieving and listing resources
34-- | A value responsible for retrieving and listing resources
39--
35--
@@ -44,6 +40,8 @@ data ResourceProvider = ResourceProvider
44 resourceString :: Resource -> IO String
40 resourceString :: Resource -> IO String
45 , -- | Retrieve a certain resource as lazy bytestring
41 , -- | Retrieve a certain resource as lazy bytestring
46 resourceLazyByteString :: Resource -> IO LB.ByteString
42 resourceLazyByteString :: Resource -> IO LB.ByteString
43+ , -- | Cache keeping track of modified items
44+ resourceModifiedCache :: MVar (Map Resource Bool)
47 }
45 }
48
46
49-- | Check if a given identifier has a resource
47-- | Check if a given identifier has a resource
@@ -60,6 +58,24 @@ resourceDigest provider = digest MD5 <=< resourceLazyByteString provider
60--
58--
61resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool
59resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool
62resourceModified provider resource store = do
60resourceModified provider resource store = do
61+ cache <- readMVar mvar
62+ case M.lookup resource cache of
63+ -- Already in the cache
64+ Just m -> return m
65+ -- Not yet in the cache, check digests (if it exists)
66+ Nothing -> do
67+ m <- if resourceExists provider (unResource resource)
68+ then digestModified provider resource store
69+ else return False
70+ modifyMVar_ mvar (return . M.insert resource m)
71+ return m
72+ where
73+ mvar = resourceModifiedCache provider
74+
75+-- | Check if a resource digest was modified
76+--
77+digestModified :: ResourceProvider -> Resource -> Store -> IO Bool
78+digestModified provider resource store = do
63 -- Get the latest seen digest from the store
79 -- Get the latest seen digest from the store
64 lastDigest <- storeGet store itemName $ unResource resource
80 lastDigest <- storeGet store itemName $ unResource resource
65 -- Calculate the digest for the resource
81 -- Calculate the digest for the resource
@@ -72,4 +88,4 @@ resourceModified provider resource store = do
72 else do storeSet store itemName (unResource resource) newDigest
88 else do storeSet store itemName (unResource resource) newDigest
73 return True
89 return True
74 where
90 where
75− itemName = "Hakyll.Core.ResourceProvider.resourceModified"
91+ itemName = "Hakyll.Core.ResourceProvider.digestModified"
src/Hakyll/Core/ResourceProvider/FileResourceProvider.hs → src/Hakyll/Core/Resource/Provider/File.hsrenamed+9−2
@@ -1,14 +1,17 @@
1-- | A concrete 'ResourceProvider' that gets it's resources from the filesystem
1-- | A concrete 'ResourceProvider' that gets it's resources from the filesystem
2--
2--
3−module Hakyll.Core.ResourceProvider.FileResourceProvider
3+module Hakyll.Core.Resource.Provider.File
4 ( fileResourceProvider
4 ( fileResourceProvider
5 ) where
5 ) where
6
6
7import Control.Applicative ((<$>))
7import Control.Applicative ((<$>))
8+import Control.Concurrent (newMVar)
9+import qualified Data.Map as M
8
10
9import qualified Data.ByteString.Lazy as LB
11import qualified Data.ByteString.Lazy as LB
10
12
11−import Hakyll.Core.ResourceProvider
13+import Hakyll.Core.Resource
14+import Hakyll.Core.Resource.Provider
12import Hakyll.Core.Identifier
15import Hakyll.Core.Identifier
13import Hakyll.Core.Util.File
16import Hakyll.Core.Util.File
14import Hakyll.Core.Configuration
17import Hakyll.Core.Configuration
@@ -21,9 +24,13 @@ fileResourceProvider configuration = do
21 list <- map parseIdentifier . filter (not . ignoreFile configuration) <$>
24 list <- map parseIdentifier . filter (not . ignoreFile configuration) <$>
22 getRecursiveContents False "."
25 getRecursiveContents False "."
23
26
27+ -- MVar for the cache
28+ mvar <- newMVar M.empty
29+
24 -- Construct a resource provider
30 -- Construct a resource provider
25 return ResourceProvider
31 return ResourceProvider
26 { resourceList = map Resource list
32 { resourceList = map Resource list
27 , resourceString = readFile . toFilePath . unResource
33 , resourceString = readFile . toFilePath . unResource
28 , resourceLazyByteString = LB.readFile . toFilePath . unResource
34 , resourceLazyByteString = LB.readFile . toFilePath . unResource
35+ , resourceModifiedCache = mvar
29 }
36 }
src/Hakyll/Core/Routes.hs+8−8
@@ -30,7 +30,7 @@ module Hakyll.Core.Routes
30 , runRoutes
30 , runRoutes
31 , idRoute
31 , idRoute
32 , setExtension
32 , setExtension
33− , ifMatch
33+ , matchRoute
34 , customRoute
34 , customRoute
35 , gsubRoute
35 , gsubRoute
36 , composeRoutes
36 , composeRoutes
@@ -85,15 +85,15 @@ setExtension :: String -> Routes
85setExtension extension = Routes $ fmap (`replaceExtension` extension)
85setExtension extension = Routes $ fmap (`replaceExtension` extension)
86 . unRoutes idRoute
86 . unRoutes idRoute
87
87
88−-- | Modify a route: apply the route if the identifier matches the given
88+-- | Apply the route if the identifier matches the given pattern, fail
89−-- pattern, fail otherwise.
89+-- otherwise
90--
90--
91−ifMatch :: Pattern -> Routes -> Routes
91+matchRoute :: Pattern -> Routes -> Routes
92−ifMatch pattern (Routes route) = Routes $ \id' ->
92+matchRoute pattern (Routes route) = Routes $ \id' ->
93− if doesMatch pattern id' then route id'
93+ if matches pattern id' then route id' else Nothing
94− else Nothing
95
94
96−-- | Create a custom route. This should almost always be used with 'ifMatch'.
95+-- | Create a custom route. This should almost always be used with
96+-- 'matchRoute'
97--
97--
98customRoute :: (Identifier -> FilePath) -> Routes
98customRoute :: (Identifier -> FilePath) -> Routes
99customRoute f = Routes $ Just . f
99customRoute f = Routes $ Just . f
src/Hakyll/Core/Rules.hs+36−17
@@ -7,13 +7,18 @@
7-- A typical usage example would be:
7-- A typical usage example would be:
8--
8--
9-- > main = hakyll $ do
9-- > main = hakyll $ do
10+-- > match "posts/*" $ do
10−-- > route "posts/*" (setExtension "html")
11+-- > route (setExtension "html")
11−-- > compile "posts/*" someCompiler
12+-- > compile someCompiler
13+-- > match "css/*" $ do
14+-- > route idRoute
15+-- > compile compressCssCompiler
12--
16--
13{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
17{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
14module Hakyll.Core.Rules
18module Hakyll.Core.Rules
15 ( RulesM
19 ( RulesM
16 , Rules
20 , Rules
21+ , match
17 , compile
22 , compile
18 , create
23 , create
19 , route
24 , route
@@ -23,16 +28,17 @@ module Hakyll.Core.Rules
23
28
24import Control.Applicative ((<$>))
29import Control.Applicative ((<$>))
25import Control.Monad.Writer (tell)
30import Control.Monad.Writer (tell)
26−import Control.Monad.Reader (ask)
31+import Control.Monad.Reader (ask, local)
27import Control.Arrow (second, (>>>), arr, (>>^))
32import Control.Arrow (second, (>>>), arr, (>>^))
28import Control.Monad.State (get, put)
33import Control.Monad.State (get, put)
29−import Data.Monoid (mempty)
34+import Data.Monoid (mempty, mappend)
30import qualified Data.Set as S
35import qualified Data.Set as S
31
36
32import Data.Typeable (Typeable)
37import Data.Typeable (Typeable)
33import Data.Binary (Binary)
38import Data.Binary (Binary)
34
39
35−import Hakyll.Core.ResourceProvider
40+import Hakyll.Core.Resource
41+import Hakyll.Core.Resource.Provider
36import Hakyll.Core.Identifier
42import Hakyll.Core.Identifier
37import Hakyll.Core.Identifier.Pattern
43import Hakyll.Core.Identifier.Pattern
38import Hakyll.Core.Compiler.Internal
44import Hakyll.Core.Compiler.Internal
@@ -63,21 +69,32 @@ tellResources :: [Resource]
63 -> Rules
69 -> Rules
64tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources
70tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources
65
71
72+-- | Only compile/route items satisfying the given predicate
73+--
74+match :: Pattern -> Rules -> Rules
75+match pattern = RulesM . local addPredicate . unRulesM
76+ where
77+ addPredicate env = env
78+ { rulesPattern = rulesPattern env `mappend` pattern
79+ }
80+
66-- | Add a compilation rule to the rules.
81-- | Add a compilation rule to the rules.
67--
82--
68−-- This instructs all resources matching the given pattern to be compiled using
83+-- This instructs all resources to be compiled using the given compiler. When
69−-- the given compiler. When no resources match the given pattern, nothing will
84+-- no resources match the current selection, nothing will happen. In this case,
70−-- happen. In this case, you might want to have a look at 'create'.
85+-- you might want to have a look at 'create'.
71--
86--
72compile :: (Binary a, Typeable a, Writable a)
87compile :: (Binary a, Typeable a, Writable a)
73− => Pattern -> Compiler Resource a -> Rules
88+ => Compiler Resource a -> Rules
74−compile pattern compiler = RulesM $ do
89+compile compiler = RulesM $ do
90+ pattern <- rulesPattern <$> ask
91+ provider <- rulesResourceProvider <$> ask
75− identifiers <- matches pattern . map unResource . resourceList <$> ask
92+ let ids = filterMatches pattern $ map unResource $ resourceList provider
76 unRulesM $ do
93 unRulesM $ do
77− tellCompilers $ flip map identifiers $ \identifier ->
94+ tellCompilers $ flip map ids $ \identifier ->
78 (identifier, constA (Resource identifier) >>> compiler)
95 (identifier, constA (Resource identifier) >>> compiler)
79− tellResources $ map Resource identifiers
96+ tellResources $ map Resource ids
80−
97+
81-- | Add a compilation rule
98-- | Add a compilation rule
82--
99--
83-- This sets a compiler for the given identifier. No resource is needed, since
100-- This sets a compiler for the given identifier. No resource is needed, since
@@ -91,10 +108,12 @@ create identifier compiler = tellCompilers [(identifier, compiler)]
91
108
92-- | Add a route.
109-- | Add a route.
93--
110--
94−-- This adds a route for all items matching the given pattern.
111+-- This adds a route for all items matching the current pattern.
95--
112--
96−route :: Pattern -> Routes -> Rules
113+route :: Routes -> Rules
114+route route' = RulesM $ do
115+ pattern <- rulesPattern <$> ask
97−route pattern route' = tellRoute $ ifMatch pattern route'
116+ unRulesM $ tellRoute $ matchRoute pattern route'
98
117
99-- | Apart from regular compilers, one is also able to specify metacompilers.
118-- | Apart from regular compilers, one is also able to specify metacompilers.
100-- Metacompilers are a special class of compilers: they are compilers which
119-- Metacompilers are a special class of compilers: they are compilers which
src/Hakyll/Core/Rules/Internal.hs+16−3
@@ -5,6 +5,7 @@ module Hakyll.Core.Rules.Internal
5 ( CompileRule (..)
5 ( CompileRule (..)
6 , RuleSet (..)
6 , RuleSet (..)
7 , RuleState (..)
7 , RuleState (..)
8+ , RuleEnvironment (..)
8 , RulesM (..)
9 , RulesM (..)
9 , Rules
10 , Rules
10 , runRules
11 , runRules
@@ -17,8 +18,10 @@ import Control.Monad.State (State, evalState)
17import Data.Monoid (Monoid, mempty, mappend)
18import Data.Monoid (Monoid, mempty, mappend)
18import Data.Set (Set)
19import Data.Set (Set)
19
20
20−import Hakyll.Core.ResourceProvider
21+import Hakyll.Core.Resource
22+import Hakyll.Core.Resource.Provider
21import Hakyll.Core.Identifier
23import Hakyll.Core.Identifier
24+import Hakyll.Core.Identifier.Pattern
22import Hakyll.Core.Compiler.Internal
25import Hakyll.Core.Compiler.Internal
23import Hakyll.Core.Routes
26import Hakyll.Core.Routes
24import Hakyll.Core.CompiledItem
27import Hakyll.Core.CompiledItem
@@ -55,10 +58,17 @@ data RuleState = RuleState
55 { rulesMetaCompilerIndex :: Int
58 { rulesMetaCompilerIndex :: Int
56 } deriving (Show)
59 } deriving (Show)
57
60
61+-- | Rule environment
62+--
63+data RuleEnvironment = RuleEnvironment
64+ { rulesResourceProvider :: ResourceProvider
65+ , rulesPattern :: Pattern
66+ }
67+
58-- | The monad used to compose rules
68-- | The monad used to compose rules
59--
69--
60newtype RulesM a = RulesM
70newtype RulesM a = RulesM
61− { unRulesM :: ReaderT ResourceProvider (WriterT RuleSet (State RuleState)) a
71+ { unRulesM :: ReaderT RuleEnvironment (WriterT RuleSet (State RuleState)) a
62 } deriving (Monad, Functor, Applicative)
72 } deriving (Monad, Functor, Applicative)
63
73
64-- | Simplification of the RulesM type; usually, it will not return any
74-- | Simplification of the RulesM type; usually, it will not return any
@@ -70,6 +80,9 @@ type Rules = RulesM ()
70--
80--
71runRules :: Rules -> ResourceProvider -> RuleSet
81runRules :: Rules -> ResourceProvider -> RuleSet
72runRules rules provider =
82runRules rules provider =
73− evalState (execWriterT $ runReaderT (unRulesM rules) provider) state
83+ evalState (execWriterT $ runReaderT (unRulesM rules) env) state
74 where
84 where
75 state = RuleState {rulesMetaCompilerIndex = 0}
85 state = RuleState {rulesMetaCompilerIndex = 0}
86+ env = RuleEnvironment { rulesResourceProvider = provider
87+ , rulesPattern = mempty
88+ }
src/Hakyll/Core/Run.hs+4−5
@@ -25,8 +25,9 @@ import Hakyll.Core.Identifier
25import Hakyll.Core.Util.File
25import Hakyll.Core.Util.File
26import Hakyll.Core.Compiler
26import Hakyll.Core.Compiler
27import Hakyll.Core.Compiler.Internal
27import Hakyll.Core.Compiler.Internal
28−import Hakyll.Core.ResourceProvider
28+import Hakyll.Core.Resource
29−import Hakyll.Core.ResourceProvider.FileResourceProvider
29+import Hakyll.Core.Resource.Provider
30+import Hakyll.Core.Resource.Provider.File
30import Hakyll.Core.Rules.Internal
31import Hakyll.Core.Rules.Internal
31import Hakyll.Core.DirectedGraph
32import Hakyll.Core.DirectedGraph
32import Hakyll.Core.DirectedGraph.Dot
33import Hakyll.Core.DirectedGraph.Dot
@@ -108,9 +109,7 @@ modified :: ResourceProvider -- ^ Resource provider
108 -> [Identifier] -- ^ Identifiers to check
109 -> [Identifier] -- ^ Identifiers to check
109 -> IO (Set Identifier) -- ^ Modified resources
110 -> IO (Set Identifier) -- ^ Modified resources
110modified provider store ids = fmap S.fromList $ flip filterM ids $ \id' ->
111modified provider store ids = fmap S.fromList $ flip filterM ids $ \id' ->
111− if resourceExists provider id'
112− then resourceModified provider (Resource id') store
112+ resourceModified provider (Resource id') store
113− else return False
114
113
115-- | Add a number of compilers and continue using these compilers
114-- | Add a number of compilers and continue using these compilers
116--
115--
src/Hakyll/Core/Util/File.hs+1−1
@@ -47,7 +47,7 @@ getRecursiveContents includeDirs topdir = do
47 return $ if includeDirs then topdir : concat paths
47 return $ if includeDirs then topdir : concat paths
48 else concat paths
48 else concat paths
49 where
49 where
50− isProper = not . (== ".") . take 1
50+ isProper = (`notElem` [".", ".."])
51
51
52-- | Check if a timestamp is obsolete compared to the timestamps of a number of
52-- | Check if a timestamp is obsolete compared to the timestamps of a number of
53-- files. When they are no files, it is never obsolete.
53-- files. When they are no files, it is never obsolete.
src/Hakyll/Core/Writable/CopyFile.hs+1−1
@@ -12,7 +12,7 @@ import System.Directory (copyFile)
12import Data.Typeable (Typeable)
12import Data.Typeable (Typeable)
13import Data.Binary (Binary)
13import Data.Binary (Binary)
14
14
15−import Hakyll.Core.ResourceProvider
15+import Hakyll.Core.Resource
16import Hakyll.Core.Writable
16import Hakyll.Core.Writable
17import Hakyll.Core.Compiler
17import Hakyll.Core.Compiler
18import Hakyll.Core.Identifier
18import Hakyll.Core.Identifier
src/Hakyll/Web/CompressCss.hs+1−1
@@ -11,7 +11,7 @@ import Data.List (isPrefixOf)
11import Control.Arrow ((>>^))
11import Control.Arrow ((>>^))
12
12
13import Hakyll.Core.Compiler
13import Hakyll.Core.Compiler
14−import Hakyll.Core.ResourceProvider
14+import Hakyll.Core.Resource
15import Hakyll.Core.Util.String
15import Hakyll.Core.Util.String
16
16
17-- | Compiler form of 'compressCss'
17-- | Compiler form of 'compressCss'
src/Hakyll/Web/Page.hs+1−1
@@ -67,7 +67,7 @@ import Data.Ord (comparing)
67
67
68import Hakyll.Core.Identifier
68import Hakyll.Core.Identifier
69import Hakyll.Core.Compiler
69import Hakyll.Core.Compiler
70−import Hakyll.Core.ResourceProvider
70+import Hakyll.Core.Resource
71import Hakyll.Web.Page.Internal
71import Hakyll.Web.Page.Internal
72import Hakyll.Web.Page.Read
72import Hakyll.Web.Page.Read
73import Hakyll.Web.Page.Metadata
73import Hakyll.Web.Page.Metadata
src/Hakyll/Web/Tags.hs+56−32
@@ -32,6 +32,7 @@ module Hakyll.Web.Tags
32 , readTags
32 , readTags
33 , readCategory
33 , readCategory
34 , renderTagCloud
34 , renderTagCloud
35+ , renderTagList
35 , renderTagsField
36 , renderTagsField
36 , renderCategoryField
37 , renderCategoryField
37 ) where
38 ) where
@@ -39,9 +40,8 @@ module Hakyll.Web.Tags
39import Prelude hiding (id)
40import Prelude hiding (id)
40import Control.Category (id)
41import Control.Category (id)
41import Control.Applicative ((<$>))
42import Control.Applicative ((<$>))
42−import Data.Map (Map)
43import qualified Data.Map as M
43import qualified Data.Map as M
44−import Data.List (intersperse)
44+import Data.List (intersperse, intercalate)
45import Control.Arrow (arr, (&&&), (>>>), (***), (<<^), returnA)
45import Control.Arrow (arr, (&&&), (>>>), (***), (<<^), returnA)
46import Data.Maybe (catMaybes, fromMaybe)
46import Data.Maybe (catMaybes, fromMaybe)
47import Data.Monoid (mconcat)
47import Data.Monoid (mconcat)
@@ -64,7 +64,7 @@ import Hakyll.Core.Util.String
64-- | Data about tags
64-- | Data about tags
65--
65--
66data Tags a = Tags
66data Tags a = Tags
67− { tagsMap :: Map String [Page a]
67+ { tagsMap :: [(String, [Page a])]
68 } deriving (Show, Typeable)
68 } deriving (Show, Typeable)
69
69
70instance Binary a => Binary (Tags a) where
70instance Binary a => Binary (Tags a) where
@@ -90,7 +90,8 @@ readTagsWith :: (Page a -> [String]) -- ^ Function extracting tags from a page
90 -> [Page a] -- ^ Pages
90 -> [Page a] -- ^ Pages
91 -> Tags a -- ^ Resulting tags
91 -> Tags a -- ^ Resulting tags
92readTagsWith f pages = Tags
92readTagsWith f pages = Tags
93+ { tagsMap = M.toList $
93− { tagsMap = foldl (M.unionWith (++)) M.empty (map readTagsWith' pages)
94+ foldl (M.unionWith (++)) M.empty (map readTagsWith' pages)
94 }
95 }
95 where
96 where
96 -- Create a tag map for one page
97 -- Create a tag map for one page
@@ -108,41 +109,64 @@ readTags = readTagsWith getTags
108readCategory :: [Page a] -> Tags a
109readCategory :: [Page a] -> Tags a
109readCategory = readTagsWith getCategory
110readCategory = readTagsWith getCategory
110
111
111−-- | Render a tag cloud in HTML
112+-- | Render tags in HTML
112--
113--
113−renderTagCloud :: (String -> Identifier) -- ^ Produce a link for a tag
114+renderTags :: (String -> Identifier)
114− -> Double -- ^ Smallest font size, in percent
115+ -- ^ Produce a link
115− -> Double -- ^ Biggest font size, in percent
116+ -> (String -> String -> Int -> Int -> Int -> String)
117+ -- ^ Produce a tag item: tag, url, count, min count, max count
118+ -> ([String] -> String)
119+ -- ^ Join items
116− -> Compiler (Tags a) String -- ^ Tag cloud renderer
120+ -> Compiler (Tags a) String
121+ -- ^ Tag cloud renderer
117−renderTagCloud makeUrl minSize maxSize = proc (Tags tags) -> do
122+renderTags makeUrl makeItem concatItems = proc (Tags tags) -> do
118 -- In tags' we create a list: [((tag, route), count)]
123 -- In tags' we create a list: [((tag, route), count)]
119 tags' <- mapCompiler ((id &&& (getRouteFor <<^ makeUrl)) *** arr length)
124 tags' <- mapCompiler ((id &&& (getRouteFor <<^ makeUrl)) *** arr length)
120− -< M.toList tags
125+ -< tags
121
126
122 let -- Absolute frequencies of the pages
127 let -- Absolute frequencies of the pages
123 freqs = map snd tags'
128 freqs = map snd tags'
124
129
125− -- Find out the relative count of a tag: on a scale from 0 to 1
126− relative count = (fromIntegral count - min') / (1 + max' - min')
127−
128− -- Show the relative size of one 'count' in percent
129− size count =
130− let size' = floor $ minSize + relative count * (maxSize - minSize)
131− in show (size' :: Int) ++ "%"
132−
133− -- The minimum and maximum count found, as doubles
130+ -- The minimum and maximum count found
134 (min', max')
131 (min', max')
135 | null freqs = (0, 1)
132 | null freqs = (0, 1)
136− | otherwise = (minimum &&& maximum) $ map fromIntegral freqs
133+ | otherwise = (minimum &&& maximum) freqs
137
134
138 -- Create a link for one item
135 -- Create a link for one item
139− makeLink ((tag, url), count) =
136+ makeItem' ((tag, url), count) =
140− H.a ! A.style (toValue $ "font-size: " ++ size count)
141− ! A.href (toValue $ toUrl $ fromMaybe "/" url)
137+ makeItem tag (toUrl $ fromMaybe "/" url) count min' max'
142− $ toHtml tag
143
138
144 -- Render and return the HTML
139 -- Render and return the HTML
145− returnA -< renderHtml $ mconcat $ intersperse " " $ map makeLink tags'
140+ returnA -< concatItems $ map makeItem' tags'
141+
142+-- | Render a tag cloud in HTML
143+--
144+renderTagCloud :: (String -> Identifier) -- ^ Produce a link for a tag
145+ -> Double -- ^ Smallest font size, in percent
146+ -> Double -- ^ Biggest font size, in percent
147+ -> Compiler (Tags a) String -- ^ Tag cloud renderer
148+renderTagCloud makeUrl minSize maxSize =
149+ renderTags makeUrl makeLink (intercalate " ")
150+ where
151+ makeLink tag url count min' max' = renderHtml $
152+ H.a ! A.style (toValue $ "font-size: " ++ size count min' max')
153+ ! A.href (toValue url)
154+ $ toHtml tag
155+
156+ -- Show the relative size of one 'count' in percent
157+ size count min' max' =
158+ let diff = 1 + fromIntegral max' - fromIntegral min'
159+ relative = (fromIntegral count - fromIntegral min') / diff
160+ size' = floor $ minSize + relative * (maxSize - minSize)
161+ in show (size' :: Int) ++ "%"
162+
163+-- | Render a simple tag list in HTML, with the tag count next to the item
164+--
165+renderTagList :: (String -> Identifier) -> Compiler (Tags a) (String)
166+renderTagList makeUrl = renderTags makeUrl makeLink (intercalate ", ")
167+ where
168+ makeLink tag url count _ _ = renderHtml $
169+ H.a ! A.href (toValue url) $ toHtml (tag ++ "(" ++ show count ++ ")")
146
170
147-- | Render tags with links
171-- | Render tags with links
148--
172--
@@ -151,14 +175,14 @@ renderTagsFieldWith :: (Page a -> [String]) -- ^ Function to get the tags
151 -> (String -> Identifier) -- ^ Create a link for a tag
175 -> (String -> Identifier) -- ^ Create a link for a tag
152 -> Compiler (Page a) (Page a) -- ^ Resulting compiler
176 -> Compiler (Page a) (Page a) -- ^ Resulting compiler
153renderTagsFieldWith tags destination makeUrl =
177renderTagsFieldWith tags destination makeUrl =
154− id &&& arr tags >>> setFieldA destination renderTags
178+ id &&& arr tags >>> setFieldA destination renderTags'
155 where
179 where
156 -- Compiler creating a comma-separated HTML string for a list of tags
180 -- Compiler creating a comma-separated HTML string for a list of tags
157− renderTags :: Compiler [String] String
181+ renderTags' :: Compiler [String] String
158− renderTags = arr (map $ id &&& makeUrl)
182+ renderTags' = arr (map $ id &&& makeUrl)
159− >>> mapCompiler (id *** getRouteFor)
183+ >>> mapCompiler (id *** getRouteFor)
160− >>> arr (map $ uncurry renderLink)
184+ >>> arr (map $ uncurry renderLink)
161− >>> arr (renderHtml . mconcat . intersperse ", " . catMaybes)
185+ >>> arr (renderHtml . mconcat . intersperse ", " . catMaybes)
162
186
163 -- Render one tag link
187 -- Render one tag link
164 renderLink _ Nothing = Nothing
188 renderLink _ Nothing = Nothing
src/Hakyll/Web/Template.hs+1−1
@@ -59,7 +59,7 @@ import Text.Hamlet (HamletSettings, defaultHamletSettings)
59
59
60import Hakyll.Core.Compiler
60import Hakyll.Core.Compiler
61import Hakyll.Core.Identifier
61import Hakyll.Core.Identifier
62−import Hakyll.Core.ResourceProvider
62+import Hakyll.Core.Resource
63import Hakyll.Web.Template.Internal
63import Hakyll.Web.Template.Internal
64import Hakyll.Web.Template.Read
64import Hakyll.Web.Template.Read
65import Hakyll.Web.Page.Internal
65import Hakyll.Web.Page.Internal
tests/Hakyll/Core/Identifier/Tests.hs+26−14
@@ -10,18 +10,30 @@ import Hakyll.Core.Identifier.Pattern
10import TestSuite.Util
10import TestSuite.Util
11
11
12tests :: [Test]
12tests :: [Test]
13+tests = concat
14+ [ captureTests
15+ , regexTests
16+ ]
17+
18+captureTests :: [Test]
13−tests = fromAssertions "match"
19+captureTests = fromAssertions "capture"
14− [ Just ["bar"] @=? match "foo/**" "foo/bar"
20+ [ Just ["bar"] @=? capture "foo/**" "foo/bar"
15− , Just ["foo/bar"] @=? match "**" "foo/bar"
21+ , Just ["foo/bar"] @=? capture "**" "foo/bar"
16− , Nothing @=? match "*" "foo/bar"
22+ , Nothing @=? capture "*" "foo/bar"
17− , Just [] @=? match "foo" "foo"
23+ , Just [] @=? capture "foo" "foo"
18− , Just ["foo"] @=? match "*/bar" "foo/bar"
24+ , Just ["foo"] @=? capture "*/bar" "foo/bar"
19− , Just ["foo/bar"] @=? match "**/qux" "foo/bar/qux"
25+ , Just ["foo/bar"] @=? capture "**/qux" "foo/bar/qux"
20− , Just ["foo/bar", "qux"] @=? match "**/*" "foo/bar/qux"
26+ , Just ["foo/bar", "qux"] @=? capture "**/*" "foo/bar/qux"
21− , Just ["foo", "bar/qux"] @=? match "*/**" "foo/bar/qux"
27+ , Just ["foo", "bar/qux"] @=? capture "*/**" "foo/bar/qux"
22− , Just ["foo"] @=? match "*.html" "foo.html"
28+ , Just ["foo"] @=? capture "*.html" "foo.html"
23− , Nothing @=? match "*.html" "foo/bar.html"
29+ , Nothing @=? capture "*.html" "foo/bar.html"
24− , Just ["foo/bar"] @=? match "**.html" "foo/bar.html"
30+ , Just ["foo/bar"] @=? capture "**.html" "foo/bar.html"
25− , Just ["foo/bar", "wut"] @=? match "**/qux/*" "foo/bar/qux/wut"
31+ , Just ["foo/bar", "wut"] @=? capture "**/qux/*" "foo/bar/qux/wut"
26− , Just ["lol", "fun/large"] @=? match "*cat/**.jpg" "lolcat/fun/large.jpg"
32+ , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
33+ ]
34+
35+regexTests :: [Test]
36+regexTests = fromAssertions "regex"
37+ [ True @=? matches (regex "^foo/[^x]*$") "foo/bar"
38+ , False @=? matches (regex "^foo/[^x]*$") "foo/barx"
27 ]
39 ]