on master help sign in
Fix feeds
6b5c299Jasper Van der Jeugt
hakyll.cabal+1−1
@@ -110,7 +110,7 @@ Library
110 Hakyll.Core.Writable.CopyFile
110 Hakyll.Core.Writable.CopyFile
111 Hakyll.Main
111 Hakyll.Main
112 Hakyll.Web.CompressCss
112 Hakyll.Web.CompressCss
113− -- Hakyll.Web.Feed
113+ Hakyll.Web.Feed
114 Hakyll.Web.Page
114 Hakyll.Web.Page
115 -- Hakyll.Web.Page.List
115 -- Hakyll.Web.Page.List
116 Hakyll.Web.Page.Metadata
116 Hakyll.Web.Page.Metadata
src/Hakyll.hs+2−2
@@ -20,7 +20,7 @@ module Hakyll
20 , module Hakyll.Core.Writable.CopyFile
20 , module Hakyll.Core.Writable.CopyFile
21 , module Hakyll.Main
21 , module Hakyll.Main
22 , module Hakyll.Web.CompressCss
22 , module Hakyll.Web.CompressCss
23− -- , module Hakyll.Web.Feed
23+ , module Hakyll.Web.Feed
24 , module Hakyll.Web.Page
24 , module Hakyll.Web.Page
25 -- , module Hakyll.Web.Page.List
25 -- , module Hakyll.Web.Page.List
26 , module Hakyll.Web.Pandoc
26 , module Hakyll.Web.Pandoc
@@ -53,7 +53,7 @@ import Hakyll.Core.Writable
53import Hakyll.Core.Writable.CopyFile
53import Hakyll.Core.Writable.CopyFile
54import Hakyll.Main
54import Hakyll.Main
55import Hakyll.Web.CompressCss
55import Hakyll.Web.CompressCss
56−-- import Hakyll.Web.Feed
56+import Hakyll.Web.Feed
57import Hakyll.Web.Page
57import Hakyll.Web.Page
58-- import Hakyll.Web.Page.List
58-- import Hakyll.Web.Page.List
59import Hakyll.Web.Pandoc
59import Hakyll.Web.Pandoc
src/Hakyll/Core/Compiler.hs+1
@@ -12,6 +12,7 @@ module Hakyll.Core.Compiler
12 , getResourceLBS
12 , getResourceLBS
13 , getResourceWith
13 , getResourceWith
14 , require
14 , require
15+ , requireBody
15 , requireAll
16 , requireAll
16 , cached
17 , cached
17 , unsafeCompiler
18 , unsafeCompiler
src/Hakyll/Core/Compiler/Require.hs+10−3
@@ -2,6 +2,7 @@
2module Hakyll.Core.Compiler.Require
2module Hakyll.Core.Compiler.Require
3 ( save
3 ( save
4 , require
4 , require
5+ , requireBody
5 , requireAll
6 , requireAll
6 ) where
7 ) where
7
8
@@ -17,6 +18,7 @@ import Hakyll.Core.Compiler.Internal
17import Hakyll.Core.Dependencies
18import Hakyll.Core.Dependencies
18import Hakyll.Core.Identifier
19import Hakyll.Core.Identifier
19import Hakyll.Core.Identifier.Pattern
20import Hakyll.Core.Identifier.Pattern
21+import Hakyll.Core.Item
20import Hakyll.Core.Store (Store)
22import Hakyll.Core.Store (Store)
21import qualified Hakyll.Core.Store as Store
23import qualified Hakyll.Core.Store as Store
22
24
@@ -27,7 +29,7 @@ save store identifier x = Store.set store (key identifier) x
27
29
28
30
29--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
30−require :: (Binary a, Typeable a) => Identifier -> Compiler a
32+require :: (Binary a, Typeable a) => Identifier -> Compiler (Item a)
31require id' = do
33require id' = do
32 store <- compilerStore <$> compilerAsk
34 store <- compilerStore <$> compilerAsk
33
35
@@ -37,7 +39,7 @@ require id' = do
37 case result of
39 case result of
38 Store.NotFound -> compilerThrow notFound
40 Store.NotFound -> compilerThrow notFound
39 Store.WrongType e r -> compilerThrow $ wrongType e r
41 Store.WrongType e r -> compilerThrow $ wrongType e r
40− Store.Found x -> return x
42+ Store.Found x -> return $ Item id' x
41 where
43 where
42 notFound =
44 notFound =
43 "Hakyll.Core.Compiler.Require.require: " ++ show id' ++ " was " ++
45 "Hakyll.Core.Compiler.Require.require: " ++ show id' ++ " was " ++
@@ -50,7 +52,12 @@ require id' = do
50
52
51
53
52--------------------------------------------------------------------------------
54--------------------------------------------------------------------------------
55+requireBody :: (Binary a, Typeable a) => Identifier -> Compiler a
56+requireBody = fmap itemBody . require
57+
58+
59+--------------------------------------------------------------------------------
53−requireAll :: (Binary a, Typeable a) => Pattern -> Compiler [a]
60+requireAll :: (Binary a, Typeable a) => Pattern -> Compiler [Item a]
54requireAll pattern = do
61requireAll pattern = do
55 universe <- compilerUniverse <$> compilerAsk
62 universe <- compilerUniverse <$> compilerAsk
56 let matching = filterMatches pattern universe
63 let matching = filterMatches pattern universe
src/Hakyll/Web/Feed.hs+79−84
@@ -1,11 +1,11 @@
1−-- TODO: port
1+--------------------------------------------------------------------------------
2-- | A Module that allows easy rendering of RSS feeds.
2-- | A Module that allows easy rendering of RSS feeds.
3--
3--
4-- The main rendering functions (@renderRss@, @renderAtom@) all assume that
4-- The main rendering functions (@renderRss@, @renderAtom@) all assume that
5-- you pass the list of items so that the most recent entry in the feed is the
5-- you pass the list of items so that the most recent entry in the feed is the
6-- first item in the list.
6-- first item in the list.
7--
7--
8−-- Also note that the pages should have (at least) the following fields to
8+-- Also note that the context should have (at least) the following fields to
9-- produce a correct feed:
9-- produce a correct feed:
10--
10--
11-- - @$title$@: Title of the item
11-- - @$title$@: Title of the item
@@ -15,29 +15,33 @@
15-- - @$url$@: URL to the item - this is usually set automatically.
15-- - @$url$@: URL to the item - this is usually set automatically.
16--
16--
17-- In addition, the posts should be named according to the rules for
17-- In addition, the posts should be named according to the rules for
18−-- 'Hakyll.Page.Metadata.renderDateField'.
18+-- 'Hakyll.Web.Template.Context.dateField'
19−--
20module Hakyll.Web.Feed
19module Hakyll.Web.Feed
21 ( FeedConfiguration (..)
20 ( FeedConfiguration (..)
22 , renderRss
21 , renderRss
23 , renderAtom
22 , renderAtom
24 ) where
23 ) where
25
24
26−import Prelude hiding (id)
27−import Control.Category (id)
28−import Control.Arrow ((>>>), arr, (&&&))
29−import Control.Monad ((<=<))
30−import Data.Maybe (fromMaybe, listToMaybe)
31
25
26+--------------------------------------------------------------------------------
27+import Control.Monad (forM, (<=<))
28+import Data.Monoid (mconcat)
29+
30+
31+--------------------------------------------------------------------------------
32−import Hakyll.Core.Compiler
32+import Hakyll.Core.Compiler
33−import Hakyll.Web.Page
33+import Hakyll.Core.Compiler.Internal
34−import Hakyll.Web.Page.Metadata
34+import Hakyll.Core.Item
35+import Hakyll.Web.Template
35−import Hakyll.Web.Template
36+import Hakyll.Web.Template.Context
36−import Hakyll.Web.Template.Read.Hakyll (readTemplate)
37+import Hakyll.Web.Template.Read.Hakyll (readTemplate)
37−import Hakyll.Web.Urls
38
38
39−import Paths_hakyll
40
39
40+--------------------------------------------------------------------------------
41+import Paths_hakyll
42+
43+
44+--------------------------------------------------------------------------------
41-- | This is a data structure to keep the configuration of a feed.
45-- | This is a data structure to keep the configuration of a feed.
42data FeedConfiguration = FeedConfiguration
46data FeedConfiguration = FeedConfiguration
43 { -- | Title of the feed.
47 { -- | Title of the feed.
@@ -52,83 +56,74 @@ data FeedConfiguration = FeedConfiguration
52 feedRoot :: String
56 feedRoot :: String
53 } deriving (Show, Eq)
57 } deriving (Show, Eq)
54
58
55−-- | This is an auxiliary function to create a listing that is, in fact, a feed.
56−-- The items should be sorted on date. The @$updated@ field should be set for
57−-- each item.
58−--
59−createFeed :: Template -- ^ Feed template
60− -> Template -- ^ Item template
61− -> String -- ^ URL of the feed
62− -> FeedConfiguration -- ^ Feed configuration
63− -> [Page String] -- ^ Items to include
64− -> String -- ^ Resulting feed
65−createFeed feedTemplate itemTemplate url configuration items =
66− pageBody $ applyTemplateToPage feedTemplate
67− $ trySetField "updated" updated
68− $ trySetField "title" (feedTitle configuration)
69− $ trySetField "description" (feedDescription configuration)
70− $ trySetField "authorName" (feedAuthorName configuration)
71− $ trySetField "authorEmail" (feedAuthorEmail configuration)
72− $ trySetField "root" (feedRoot configuration)
73− $ trySetField "url" url
74− $ fromBody body
75− where
76− -- Preprocess items
77− items' = flip map items $ applyTemplateToPage itemTemplate
78− . trySetField "root" (feedRoot configuration)
79−
80− -- Body: concatenated items
81− body = concat $ map pageBody items'
82−
83− -- Take the first updated, which should be the most recent
84− updated = fromMaybe "Unknown" $ do
85− p <- listToMaybe items
86− return $ getField "updated" p
87−
88
59
60+--------------------------------------------------------------------------------
89-- | Abstract function to render any feed.
61-- | Abstract function to render any feed.
90−--
91−renderFeed :: FilePath -- ^ Feed template
62+renderFeed :: FilePath -- ^ Feed template
92− -> FilePath -- ^ Item template
63+ -> FilePath -- ^ Item template
93− -> FeedConfiguration -- ^ Feed configuration
64+ -> FeedConfiguration -- ^ Feed configuration
94− -> Compiler [Page String] String -- ^ Feed compiler
65+ -> Context String -- ^ Context for the items
95−renderFeed feedTemplate itemTemplate configuration =
66+ -> [Item String] -- ^ Input items
96− id &&& getRoute >>> renderFeed'
67+ -> Compiler (Item String) -- ^ Resulting item
97− where
68+renderFeed feedPath itemPath config itemContext items = do
98− -- Arrow rendering the feed from the items and the URL
69+ feedTpl <- compilerUnsafeIO $ loadTemplate feedPath
99− renderFeed' = unsafeCompiler $ \(items, url) -> do
70+ itemTpl <- compilerUnsafeIO $ loadTemplate itemPath
100− feedTemplate' <- loadTemplate feedTemplate
101− itemTemplate' <- loadTemplate itemTemplate
102− let url' = toUrl $ fromMaybe noUrl url
103− return $ createFeed feedTemplate' itemTemplate' url' configuration items
104
71
72+ items' <- forM items $ applyTemplate itemTpl itemContext'
73+ body <- makeItem $ concat $ map itemBody items'
74+ applyTemplate feedTpl feedContext body
75+ where
105 -- Auxiliary: load a template from a datafile
76 -- Auxiliary: load a template from a datafile
106 loadTemplate = fmap readTemplate . readFile <=< getDataFileName
77 loadTemplate = fmap readTemplate . readFile <=< getDataFileName
107
78
108− -- URL is required to have a valid field
79+ itemContext' = mconcat
109− noUrl = error "Hakyll.Web.Feed.renderFeed: no route specified"
80+ [ constField "root" (feedRoot config)
81+ , itemContext
82+ ]
83+
84+ feedContext = mconcat
85+ [ bodyField "body"
86+ , constField "title" (feedTitle config)
87+ , constField "description" (feedDescription config)
88+ , constField "authorName" (feedAuthorName config)
89+ , constField "authorEmail" (feedAuthorEmail config)
90+ , constField "root" (feedRoot config)
91+ , urlField "url"
92+ , updatedField
93+ , missingField
94+ ]
95+
96+ -- Take the first "updated" field from all items -- this should be the most
97+ -- recent.
98+ updatedField = field "updated" $ \_ -> case items of
99+ [] -> return "Unknown"
100+ (x : _) -> unContext itemContext' "updated" x
110
101
102+
103+--------------------------------------------------------------------------------
111-- | Render an RSS feed with a number of items.
104-- | Render an RSS feed with a number of items.
112−--
113−renderRss :: FeedConfiguration -- ^ Feed configuration
105+renderRss :: FeedConfiguration -- ^ Feed configuration
114− -> Compiler [Page String] String -- ^ Feed compiler
106+ -> Context String -- ^ Item context
115−renderRss configuration = arr (map (addUpdated . renderDate))
107+ -> [Item String] -- ^ Feed items
108+ -> Compiler (Item String) -- ^ Resulting feed
109+renderRss config context = renderFeed
116− >>> renderFeed "templates/rss.xml" "templates/rss-item.xml" configuration
110+ "templates/rss.xml" "templates/rss-item.xml" config
117− where
111+ (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
118− renderDate = renderDateField "published" "%a, %d %b %Y %H:%M:%S UT"
119− "No date found."
120
112
113+
114+--------------------------------------------------------------------------------
121-- | Render an Atom feed with a number of items.
115-- | Render an Atom feed with a number of items.
122−--
123−renderAtom :: FeedConfiguration -- ^ Feed configuration
116+renderAtom :: FeedConfiguration -- ^ Feed configuration
124− -> Compiler [Page String] String -- ^ Feed compiler
117+ -> Context String -- ^ Item context
125−renderAtom configuration = arr (map (addUpdated . renderDate))
118+ -> [Item String] -- ^ Feed items
119+ -> Compiler (Item String) -- ^ Resulting feed
120+renderAtom config context = renderFeed
126− >>> renderFeed "templates/atom.xml" "templates/atom-item.xml" configuration
121+ "templates/atom.xml" "templates/atom-item.xml" config
127− where
122+ (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
128− renderDate = renderDateField "published" "%Y-%m-%dT%H:%M:%SZ"
129− "No date found."
130
123
124+
125+--------------------------------------------------------------------------------
131-- | Copies @$updated$@ from @$published$@ if it is not already set.
126-- | Copies @$updated$@ from @$published$@ if it is not already set.
132−--
127+makeItemContext :: String -> Context a -> Context a
133−addUpdated :: Page a -> Page a
128+makeItemContext fmt context = mconcat
134−addUpdated page = trySetField "updated" (getField "published" page) page
129+ [dateField "published" fmt, context, dateField "updated" fmt]
src/Hakyll/Web/Template/Context.hs+10−1
@@ -3,6 +3,7 @@ module Hakyll.Web.Template.Context
3 ( Context (..)
3 ( Context (..)
4 , mapContext
4 , mapContext
5 , field
5 , field
6+ , constField
6
7
7 , defaultContext
8 , defaultContext
8 , bodyField
9 , bodyField
@@ -15,6 +16,7 @@ module Hakyll.Web.Template.Context
15 , dateFieldWith
16 , dateFieldWith
16 , modificationTimeField
17 , modificationTimeField
17 , modificationTimeFieldWith
18 , modificationTimeFieldWith
19+ , missingField
18 ) where
20 ) where
19
21
20
22
@@ -63,6 +65,11 @@ field :: String -> (Item a -> Compiler String) -> Context a
63field key value = Context $ \k i -> if k == key then value i else empty
65field key value = Context $ \k i -> if k == key then value i else empty
64
66
65
67
68+--------------------------------------------------------------------------------
69+constField :: String -> String -> Context a
70+constField key = field key . const . return
71+
72+
66--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
67defaultContext :: Context String
74defaultContext :: Context String
68defaultContext =
75defaultContext =
@@ -194,4 +201,6 @@ modificationTimeFieldWith locale key fmt = field key $ \i -> do
194
201
195--------------------------------------------------------------------------------
202--------------------------------------------------------------------------------
196missingField :: Context a
203missingField :: Context a
197−missingField = Context $ \k _ -> return $ "$" ++ k ++ "$"
204+missingField = Context $ \k i -> compilerThrow $
205+ "Missing field $" ++ k ++ "$ in context for item " ++
206+ show (itemIdentifier i)