on master help sign in
Merge branch 'nested-rules'
433f36eJasper 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/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
src/Hakyll/Core/Compiler.hs+1−1
@@ -245,7 +245,7 @@ requireAll_ :: (Binary a, Typeable a, Writable a)
245 -> Compiler b [a]
245 -> Compiler b [a]
246requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
246requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
247 where
247 where
248− getDeps = matches pattern . map unResource . resourceList
248+ getDeps = filterMatches pattern . map unResource . resourceList
249 requireAll_' = const $ CompilerM $ do
249 requireAll_' = const $ CompilerM $ do
250 deps <- getDeps . compilerResourceProvider <$> ask
250 deps <- getDeps . compilerResourceProvider <$> ask
251 mapM (unCompilerM . getDependency) deps
251 mapM (unCompilerM . getDependency) deps
src/Hakyll/Core/Identifier/Pattern.hs+74−41
@@ -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,16 @@
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
31 , matches
38 , matches
39+ , filterMatches
40+ , capture
32 , fromCapture
41 , fromCapture
33 , fromCaptureString
42 , fromCaptureString
34 , fromCaptures
43 , fromCaptures
@@ -38,7 +47,7 @@ import Data.List (isPrefixOf, inits, tails)
38import Control.Arrow ((&&&), (>>>))
47import Control.Arrow ((&&&), (>>>))
39import Control.Monad (msum)
48import Control.Monad (msum)
40import Data.Maybe (isJust)
49import Data.Maybe (isJust)
41−import Data.Monoid (mempty, mappend)
50+import Data.Monoid (Monoid, mempty, mappend)
42
51
43import GHC.Exts (IsString, fromString)
52import GHC.Exts (IsString, fromString)
44
53
@@ -46,23 +55,29 @@ import Hakyll.Core.Identifier
46
55
47-- | One base element of a pattern
56-- | One base element of a pattern
48--
57--
49−data PatternComponent = Capture
58+data GlobComponent = Capture
50− | CaptureMany
59+ | CaptureMany
51− | Literal String
60+ | Literal String
52− deriving (Eq, Show)
61+ deriving (Eq, Show)
53
62
54-- | Type that allows matching on identifiers
63-- | Type that allows matching on identifiers
55--
64--
56−newtype Pattern = Pattern {unPattern :: [PatternComponent]}
65+data Pattern = Glob [GlobComponent]
57− deriving (Eq, Show)
66+ | Predicate (Identifier -> Bool)
58
67
59instance IsString Pattern where
68instance IsString Pattern where
60− fromString = parsePattern
69+ fromString = parseGlob
70+
71+instance Monoid Pattern where
72+ mempty = Predicate (const True)
73+ g@(Glob _) `mappend` x = Predicate (matches g) `mappend` x
74+ x `mappend` g@(Glob _) = x `mappend` Predicate (matches g)
75+ Predicate f `mappend` Predicate g = Predicate $ \i -> f i && g i
61
76
62-- | Parse a pattern from a string
77-- | Parse a pattern from a string
63--
78--
64−parsePattern :: String -> Pattern
79+parseGlob :: String -> Pattern
65−parsePattern = Pattern . parse' -- undefined -- Pattern . map toPattern . unIdentifier . parseIdentifier
80+parseGlob = Glob . parse'
66 where
81 where
67 parse' str =
82 parse' str =
68 let (chunk, rest) = break (`elem` "\\*") str
83 let (chunk, rest) = break (`elem` "\\*") str
@@ -72,20 +87,25 @@ parsePattern = Pattern . parse' -- undefined -- Pattern . map toPattern . unIden
72 ('*' : xs) -> Literal chunk : Capture : parse' xs
87 ('*' : xs) -> Literal chunk : Capture : parse' xs
73 xs -> Literal chunk : Literal xs : []
88 xs -> Literal chunk : Literal xs : []
74
89
75−-- | Match an identifier against a pattern, generating a list of captures
90+-- | Create a 'Pattern' from an arbitrary predicate
91+--
92+-- Example:
76--
93--
77−match :: Pattern -> Identifier -> Maybe [Identifier]
94+-- > predicate (\i -> matches "foo/*" i && not (matches "foo/bar" i))
78−match p (Identifier i) = fmap (map Identifier) $ match' (unPattern p) i
95+--
96+predicate :: (Identifier -> Bool) -> Pattern
97+predicate = Predicate
79
98
80-- | Check if an identifier matches a pattern
99-- | Check if an identifier matches a pattern
81--
100--
82−doesMatch :: Pattern -> Identifier -> Bool
101+matches :: Pattern -> Identifier -> Bool
83−doesMatch p = isJust . match p
102+matches (Glob p) = isJust . capture (Glob p)
103+matches (Predicate p) = (p $)
84
104
85-- | Given a list of identifiers, retain only those who match the given pattern
105-- | Given a list of identifiers, retain only those who match the given pattern
86--
106--
87−matches :: Pattern -> [Identifier] -> [Identifier]
107+filterMatches :: Pattern -> [Identifier] -> [Identifier]
88−matches p = filter (doesMatch p)
108+filterMatches = filter . matches
89
109
90-- | Split a list at every possible point, generate a list of (init, tail)
110-- | Split a list at every possible point, generate a list of (init, tail)
91-- cases. The result is sorted with inits decreasing in length.
111-- cases. The result is sorted with inits decreasing in length.
@@ -93,30 +113,35 @@ matches p = filter (doesMatch p)
93splits :: [a] -> [([a], [a])]
113splits :: [a] -> [([a], [a])]
94splits = inits &&& tails >>> uncurry zip >>> reverse
114splits = inits &&& tails >>> uncurry zip >>> reverse
95
115
116+-- | Match a glob against a pattern, generating a list of captures
117+--
118+capture :: Pattern -> Identifier -> Maybe [Identifier]
119+capture (Glob p) (Identifier i) = fmap (map Identifier) $ capture' p i
120+capture (Predicate _) _ = Nothing
121+
96−-- | Internal verion of 'match'
122+-- | Internal verion of 'capture'
97--
123--
98−match' :: [PatternComponent] -> String -> Maybe [String]
124+capture' :: [GlobComponent] -> String -> Maybe [String]
99−match' [] [] = Just [] -- An empty match
125+capture' [] [] = Just [] -- An empty match
100−match' [] _ = Nothing -- No match
126+capture' [] _ = Nothing -- No match
101−-- match' _ [] = Nothing -- No match
102−match' (Literal l : ms) str
127+capture' (Literal l : ms) str
103 -- Match the literal against the string
128 -- Match the literal against the string
104− | l `isPrefixOf` str = match' ms $ drop (length l) str
129+ | l `isPrefixOf` str = capture' ms $ drop (length l) str
105 | otherwise = Nothing
130 | otherwise = Nothing
106−match' (Capture : ms) str =
131+capture' (Capture : ms) str =
107 -- Match until the next /
132 -- Match until the next /
108 let (chunk, rest) = break (== '/') str
133 let (chunk, rest) = break (== '/') str
109− in msum $ [ fmap (i :) (match' ms (t ++ rest)) | (i, t) <- splits chunk ]
134+ in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
110−match' (CaptureMany : ms) str =
135+capture' (CaptureMany : ms) str =
111 -- Match everything
136 -- Match everything
112− msum $ [ fmap (i :) (match' ms t) | (i, t) <- splits str ]
137+ msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
113
138
114-- | Create an identifier from a pattern by filling in the captures with a given
139-- | Create an identifier from a pattern by filling in the captures with a given
115-- string
140-- string
116--
141--
117-- Example:
142-- Example:
118--
143--
119−-- > fromCapture (parsePattern "tags/*") (parseIdentifier "foo")
144+-- > fromCapture (parseGlob "tags/*") (parseIdentifier "foo")
120--
145--
121-- Result:
146-- Result:
122--
147--
@@ -128,7 +153,7 @@ fromCapture pattern = fromCaptures pattern . repeat
128-- | Simplified version of 'fromCapture' which takes a 'String' instead of an
153-- | Simplified version of 'fromCapture' which takes a 'String' instead of an
129-- 'Identifier'
154-- 'Identifier'
130--
155--
131−-- > fromCaptureString (parsePattern "tags/*") "foo"
156+-- > fromCaptureString (parseGlob "tags/*") "foo"
132--
157--
133-- Result:
158-- Result:
134--
159--
@@ -141,11 +166,19 @@ fromCaptureString pattern = fromCapture pattern . parseIdentifier
141-- given list of strings
166-- given list of strings
142--
167--
143fromCaptures :: Pattern -> [Identifier] -> Identifier
168fromCaptures :: Pattern -> [Identifier] -> Identifier
144−fromCaptures (Pattern []) _ = mempty
169+fromCaptures (Glob p) = fromCaptures' p
170+fromCaptures (Predicate _) = error $
171+ "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures called on a " ++
172+ "predicate instead of a glob"
173+
174+-- | Internally used version of 'fromCaptures'
175+--
176+fromCaptures' :: [GlobComponent] -> [Identifier] -> Identifier
177+fromCaptures' [] _ = mempty
145−fromCaptures (Pattern (m : ms)) [] = case m of
178+fromCaptures' (m : ms) [] = case m of
146− Literal l -> Identifier l `mappend` fromCaptures (Pattern ms) []
179+ Literal l -> Identifier l `mappend` fromCaptures' ms []
147− _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures: "
180+ _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
148 ++ "identifier list exhausted"
181 ++ "identifier list exhausted"
149−fromCaptures (Pattern (m : ms)) ids@(i : is) = case m of
182+fromCaptures' (m : ms) ids@(i : is) = case m of
150− Literal l -> Identifier l `mappend` fromCaptures (Pattern ms) ids
183+ Literal l -> Identifier l `mappend` fromCaptures' ms ids
151− _ -> i `mappend` fromCaptures (Pattern ms) is
184+ _ -> i `mappend` fromCaptures' ms is
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+34−16
@@ -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,10 +28,10 @@ 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)
@@ -63,21 +68,32 @@ tellResources :: [Resource]
63 -> Rules
68 -> Rules
64tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources
69tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources
65
70
71+-- | Only compile/route items satisfying the given predicate
72+--
73+match :: Pattern -> Rules -> Rules
74+match pattern = RulesM . local addPredicate . unRulesM
75+ where
76+ addPredicate env = env
77+ { rulesPattern = rulesPattern env `mappend` pattern
78+ }
79+
66-- | Add a compilation rule to the rules.
80-- | Add a compilation rule to the rules.
67--
81--
68−-- This instructs all resources matching the given pattern to be compiled using
82+-- 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
83+-- 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'.
84+-- you might want to have a look at 'create'.
71--
85--
72compile :: (Binary a, Typeable a, Writable a)
86compile :: (Binary a, Typeable a, Writable a)
73− => Pattern -> Compiler Resource a -> Rules
87+ => Compiler Resource a -> Rules
74−compile pattern compiler = RulesM $ do
88+compile compiler = RulesM $ do
89+ pattern <- rulesPattern <$> ask
90+ provider <- rulesResourceProvider <$> ask
75− identifiers <- matches pattern . map unResource . resourceList <$> ask
91+ let ids = filterMatches pattern $ map unResource $ resourceList provider
76 unRulesM $ do
92 unRulesM $ do
77− tellCompilers $ flip map identifiers $ \identifier ->
93+ tellCompilers $ flip map ids $ \identifier ->
78 (identifier, constA (Resource identifier) >>> compiler)
94 (identifier, constA (Resource identifier) >>> compiler)
79− tellResources $ map Resource identifiers
95+ tellResources $ map Resource ids
80−
96+
81-- | Add a compilation rule
97-- | Add a compilation rule
82--
98--
83-- This sets a compiler for the given identifier. No resource is needed, since
99-- This sets a compiler for the given identifier. No resource is needed, since
@@ -91,10 +107,12 @@ create identifier compiler = tellCompilers [(identifier, compiler)]
91
107
92-- | Add a route.
108-- | Add a route.
93--
109--
94−-- This adds a route for all items matching the given pattern.
110+-- This adds a route for all items matching the current pattern.
95--
111--
96−route :: Pattern -> Routes -> Rules
112+route :: Routes -> Rules
113+route route' = RulesM $ do
114+ pattern <- rulesPattern <$> ask
97−route pattern route' = tellRoute $ ifMatch pattern route'
115+ unRulesM $ tellRoute $ matchRoute pattern route'
98
116
99-- | Apart from regular compilers, one is also able to specify metacompilers.
117-- | Apart from regular compilers, one is also able to specify metacompilers.
100-- Metacompilers are a special class of compilers: they are compilers which
118-- Metacompilers are a special class of compilers: they are compilers which
src/Hakyll/Core/Rules/Internal.hs+14−2
@@ -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
@@ -19,6 +20,7 @@ import Data.Set (Set)
19
20
20import Hakyll.Core.ResourceProvider
21import Hakyll.Core.ResourceProvider
21import Hakyll.Core.Identifier
22import Hakyll.Core.Identifier
23+import Hakyll.Core.Identifier.Pattern
22import Hakyll.Core.Compiler.Internal
24import Hakyll.Core.Compiler.Internal
23import Hakyll.Core.Routes
25import Hakyll.Core.Routes
24import Hakyll.Core.CompiledItem
26import Hakyll.Core.CompiledItem
@@ -55,10 +57,17 @@ data RuleState = RuleState
55 { rulesMetaCompilerIndex :: Int
57 { rulesMetaCompilerIndex :: Int
56 } deriving (Show)
58 } deriving (Show)
57
59
60+-- | Rule environment
61+--
62+data RuleEnvironment = RuleEnvironment
63+ { rulesResourceProvider :: ResourceProvider
64+ , rulesPattern :: Pattern
65+ }
66+
58-- | The monad used to compose rules
67-- | The monad used to compose rules
59--
68--
60newtype RulesM a = RulesM
69newtype RulesM a = RulesM
61− { unRulesM :: ReaderT ResourceProvider (WriterT RuleSet (State RuleState)) a
70+ { unRulesM :: ReaderT RuleEnvironment (WriterT RuleSet (State RuleState)) a
62 } deriving (Monad, Functor, Applicative)
71 } deriving (Monad, Functor, Applicative)
63
72
64-- | Simplification of the RulesM type; usually, it will not return any
73-- | Simplification of the RulesM type; usually, it will not return any
@@ -70,6 +79,9 @@ type Rules = RulesM ()
70--
79--
71runRules :: Rules -> ResourceProvider -> RuleSet
80runRules :: Rules -> ResourceProvider -> RuleSet
72runRules rules provider =
81runRules rules provider =
73− evalState (execWriterT $ runReaderT (unRulesM rules) provider) state
82+ evalState (execWriterT $ runReaderT (unRulesM rules) env) state
74 where
83 where
75 state = RuleState {rulesMetaCompilerIndex = 0}
84 state = RuleState {rulesMetaCompilerIndex = 0}
85+ env = RuleEnvironment { rulesResourceProvider = provider
86+ , rulesPattern = mempty
87+ }
tests/Hakyll/Core/Identifier/Tests.hs+14−14
@@ -10,18 +10,18 @@ import Hakyll.Core.Identifier.Pattern
10import TestSuite.Util
10import TestSuite.Util
11
11
12tests :: [Test]
12tests :: [Test]
13−tests = fromAssertions "match"
13+tests = fromAssertions "capture"
14− [ Just ["bar"] @=? match "foo/**" "foo/bar"
14+ [ Just ["bar"] @=? capture "foo/**" "foo/bar"
15− , Just ["foo/bar"] @=? match "**" "foo/bar"
15+ , Just ["foo/bar"] @=? capture "**" "foo/bar"
16− , Nothing @=? match "*" "foo/bar"
16+ , Nothing @=? capture "*" "foo/bar"
17− , Just [] @=? match "foo" "foo"
17+ , Just [] @=? capture "foo" "foo"
18− , Just ["foo"] @=? match "*/bar" "foo/bar"
18+ , Just ["foo"] @=? capture "*/bar" "foo/bar"
19− , Just ["foo/bar"] @=? match "**/qux" "foo/bar/qux"
19+ , Just ["foo/bar"] @=? capture "**/qux" "foo/bar/qux"
20− , Just ["foo/bar", "qux"] @=? match "**/*" "foo/bar/qux"
20+ , Just ["foo/bar", "qux"] @=? capture "**/*" "foo/bar/qux"
21− , Just ["foo", "bar/qux"] @=? match "*/**" "foo/bar/qux"
21+ , Just ["foo", "bar/qux"] @=? capture "*/**" "foo/bar/qux"
22− , Just ["foo"] @=? match "*.html" "foo.html"
22+ , Just ["foo"] @=? capture "*.html" "foo.html"
23− , Nothing @=? match "*.html" "foo/bar.html"
23+ , Nothing @=? capture "*.html" "foo/bar.html"
24− , Just ["foo/bar"] @=? match "**.html" "foo/bar.html"
24+ , Just ["foo/bar"] @=? capture "**.html" "foo/bar.html"
25− , Just ["foo/bar", "wut"] @=? match "**/qux/*" "foo/bar/qux/wut"
25+ , Just ["foo/bar", "wut"] @=? capture "**/qux/*" "foo/bar/qux/wut"
26− , Just ["lol", "fun/large"] @=? match "*cat/**.jpg" "lolcat/fun/large.jpg"
26+ , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
27 ]
27 ]