packages feed

hakyll 0.1 → 0.2

raw patch · 6 files changed

+178/−69 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Text.Hakyll.Page: addContext :: String -> String -> Page -> Page
- Text.Hakyll.Page: concatPages :: [Page] -> PageValue
- Text.Hakyll.Page: concatPagesWith :: String -> [Page] -> PageValue
- Text.Hakyll.Page: getURL :: Page -> String
- Text.Hakyll.Page: pageFromList :: [(String, String)] -> Page
- Text.Hakyll.Page: type Page = Map String PageValue
- Text.Hakyll.Page: type PageValue = ByteString
- Text.Hakyll.Render: renderAndWrite :: FilePath -> Page -> IO ()
- Text.Hakyll.Render: renderPage :: FilePath -> Page -> IO Page
- Text.Hakyll.Util: isCacheFileValid :: FilePath -> FilePath -> IO Bool
+ Text.Hakyll.Page: data Page
+ Text.Hakyll.Page: fromContext :: (Map ByteString ByteString) -> Page
+ Text.Hakyll.Page: instance Renderable Page
+ Text.Hakyll.Page: writePage :: Page -> IO ()
+ Text.Hakyll.Render: depends :: FilePath -> [FilePath] -> IO () -> IO ()
+ Text.Hakyll.Render: render :: (Renderable a) => FilePath -> a -> IO Page
+ Text.Hakyll.Render: renderAndConcat :: (Renderable a) => FilePath -> [a] -> IO ByteString
+ Text.Hakyll.Render: renderChain :: (Renderable a) => [FilePath] -> a -> IO ()
+ Text.Hakyll.Renderable: class Renderable a
+ Text.Hakyll.Renderable: getDependencies :: (Renderable a) => a -> [FilePath]
+ Text.Hakyll.Renderable: getURL :: (Renderable a) => a -> FilePath
+ Text.Hakyll.Renderable: toContext :: (Renderable a) => a -> IO Context
+ Text.Hakyll.Renderables: createCustomPage :: String -> [FilePath] -> [(String, Either String (IO ByteString))] -> CustomPage
+ Text.Hakyll.Renderables: createPagePath :: FilePath -> PagePath
+ Text.Hakyll.Renderables: data CustomPage
+ Text.Hakyll.Renderables: data PagePath
+ Text.Hakyll.Renderables: instance Renderable CustomPage
+ Text.Hakyll.Renderables: instance Renderable PagePath
+ Text.Hakyll.Util: isCacheValid :: FilePath -> [FilePath] -> IO Bool
+ Text.Hakyll.Util: toURL :: FilePath -> FilePath
- Text.Hakyll.Page: getBody :: Page -> PageValue
+ Text.Hakyll.Page: getBody :: Page -> ByteString

Files

hakyll.cabal view
@@ -1,5 +1,5 @@ Name:           hakyll-Version:        0.1+Version:        0.2  Synopsis:       A simple static site generator library. Description:@@ -21,5 +21,7 @@   build-depends:   base >= 4 && < 5, template, filepath, directory, containers, bytestring,                    pandoc >= 1   exposed-modules: Text.Hakyll.Render+                   Text.Hakyll.Renderable+                   Text.Hakyll.Renderables                    Text.Hakyll.Page                    Text.Hakyll.Util
src/Text/Hakyll/Page.hs view
@@ -1,13 +1,9 @@ module Text.Hakyll.Page      ( Page,-      PageValue,-      addContext,-      getURL,+      fromContext,       getBody,       readPage,-      pageFromList,-      concatPages,-      concatPagesWith+      writePage     ) where  import qualified Data.Map as M@@ -20,43 +16,49 @@ import System.IO  import Text.Hakyll.Util+import Text.Hakyll.Renderable import Text.Pandoc  -- | A Page is basically key-value mapping. Certain keys have special --   meanings, like for example url, body and title.-type Page = M.Map String PageValue+data Page = Page (M.Map B.ByteString B.ByteString) --- | We use a ByteString for obvious reasons.-type PageValue = B.ByteString+-- | Create a Page from a key-value mapping.+fromContext :: (M.Map B.ByteString B.ByteString) -> Page+fromContext = Page --- | Add a key-value mapping to the Page.-addContext :: String -> String -> Page -> Page-addContext key value = M.insert key (B.pack value)+-- | Auxiliary function to pack a pair.+packPair :: (String, String) -> (B.ByteString, B.ByteString)+packPair (a, b) = (B.pack a, B.pack b)  -- | Get the URL for a certain page. This should always be defined. If --   not, it will return trash.html.-getURL :: Page -> String-getURL context = let result = M.lookup "url" context-                 in case result of (Just url) -> B.unpack url-                                   Nothing    -> error "URL is not defined."+getPageURL :: Page -> String+getPageURL (Page page) =+    let result = M.lookup (B.pack "url") page+    in case result of (Just url) -> B.unpack url+                      Nothing    -> error "URL is not defined."  -- | Get the body for a certain page. When not defined, the body will be --   empty.-getBody :: Page -> PageValue-getBody context = fromMaybe B.empty $ M.lookup "body" context+getBody :: Page -> B.ByteString+getBody (Page page) = fromMaybe B.empty $ M.lookup (B.pack "body") page +-- | The default writer options for pandoc rendering. writerOptions :: WriterOptions writerOptions = defaultWriterOptions +-- | Get a render function for a given extension. renderFunction :: String -> (String -> String) renderFunction ".html" = id renderFunction ext = writeHtmlString writerOptions .-                     renderFunction' ext defaultParserState-    where renderFunction' ".markdown" = readMarkdown-          renderFunction' ".md"       = readMarkdown-          renderFunction' ".tex"      = readLaTeX-          renderFunction' _           = readMarkdown+                     readFunction ext defaultParserState+    where readFunction ".markdown" = readMarkdown+          readFunction ".md"       = readMarkdown+          readFunction ".tex"      = readLaTeX+          readFunction _           = readMarkdown +-- | Read metadata header from a file handle. readMetaData :: Handle -> IO [(String, String)] readMetaData handle = do     line <- hGetLine handle@@ -65,22 +67,25 @@                                 return $ (trimPair . break (== ':')) line : others         where trimPair (key, value) = (trim key, trim $ tail value) +-- | Check if the given string is a metadata delimiter. isDelimiter :: String -> Bool isDelimiter = L.isPrefixOf "---"  -- | Used for caching of files. cachePage :: Page -> IO ()-cachePage page = do+cachePage page@(Page mapping) = do     let destination = toCache $ getURL page     makeDirectories destination     handle <- openFile destination WriteMode     hPutStrLn handle "---"-    mapM_ (writePair handle) $ M.toList page+    mapM_ (writePair handle) $ M.toList $ M.delete (B.pack "body") mapping     hPutStrLn handle "---"     B.hPut handle $ getBody page     hClose handle-    where writePair _ ("body", _) = return ()-          writePair h (k, v) = hPutStr h (k ++ ": ") >> B.hPut h v >> hPutStrLn h ""+    where writePair h (k, v) = B.hPut h k >>+                               B.hPut h (B.pack ": ") >>+                               B.hPut h v >>+                               hPutStrLn h ""  -- | Read a page from a file. Metadata is supported, and if the filename --   has a .markdown extension, it will be rendered using pandoc. Note that@@ -88,7 +93,7 @@ readPage :: FilePath -> IO Page readPage pagePath = do     -- Check cache.-    getFromCache <- isCacheFileValid cacheFile pagePath+    getFromCache <- isCacheValid cacheFile [pagePath]     let path = if getFromCache then cacheFile else pagePath      -- Read file.@@ -103,26 +108,24 @@     -- Render file     let rendered = B.pack $ (renderFunction $ takeExtension path) body     seq rendered $ hClose handle-    let page = M.insert "body" rendered $ addContext "url" url $ pageFromList context+    let page = fromContext $ M.fromList $+            [(B.pack "body", rendered), packPair ("url", url)] ++ map packPair context      -- Cache if needed     if getFromCache then return () else cachePage page     return page-    where url = addExtension (dropExtension pagePath) ".html"+    where url = toURL pagePath           cacheFile = toCache url --- | Create a key-value mapping page from an association list.-pageFromList :: [(String, String)] -> Page-pageFromList = M.fromList . map packPair-    where packPair (k, v) = let pv = B.pack v-                            in seq pv (k, pv)---- | Concat the bodies of pages, and return the result.-concatPages :: [Page] -> PageValue-concatPages = concatPagesWith "body"+-- | Write a page to the site destination.+writePage :: Page -> IO ()+writePage page = do+    let destination = toDestination $ getURL page+    makeDirectories destination+    B.writeFile destination (getBody page) --- | Concat certain values of pages, and return the result.-concatPagesWith :: String -- ^ Key of which to concat the values.-                -> [Page] -- ^ Pages to get the values from.-                -> PageValue -- ^ The concatenation.-concatPagesWith key = B.concat . map (fromMaybe B.empty . M.lookup key)+-- Make pages renderable.+instance Renderable Page where+    getDependencies = (:[]) . flip addExtension ".html" . dropExtension . getPageURL+    getURL = getPageURL+    toContext (Page mapping) = return mapping
src/Text/Hakyll/Render.hs view
@@ -1,11 +1,13 @@ module Text.Hakyll.Render -    ( renderPage,-      renderAndWrite,+    ( depends,+      render,+      renderAndConcat,+      renderChain,       static,       staticDirectory     ) where -import Text.Template+import Text.Template hiding (render) import qualified Data.ByteString.Lazy.Char8 as B import qualified Data.Map as M import Control.Monad@@ -14,33 +16,59 @@ import System.IO  import Text.Hakyll.Page+import Text.Hakyll.Renderable import Text.Hakyll.Util -createContext :: Page -> Context-createContext = M.fromList . map packPair . M.toList-    where packPair (a, b) = (B.pack a, b)+-- | Execute an IO action only when the cache is invalid.+depends :: FilePath -- ^ File to be rendered or created.+        -> [FilePath] -- ^ Files the render depends on.+        -> IO () -- ^ IO action to execute when the file is out of date.+        -> IO ()+depends file dependencies action = do+    valid <- isCacheValid (toDestination file) dependencies+    unless valid action -renderPage :: FilePath -> Page -> IO Page-renderPage templatePath page = do+-- | Render to a Page.+render :: Renderable a+       => FilePath -- ^ Template to use for rendering.+       -> a -- ^ Renderable object to render with given template.+       -> IO Page -- ^ The body of the result will contain the render.+render templatePath renderable = do     handle <- openFile templatePath ReadMode     templateString <- liftM B.pack $ hGetContents handle     seq templateString $ hClose handle-    let body = substitute templateString (createContext page)-    return $ addContext "body" (B.unpack body) page+    context <- toContext renderable+    let body = substitute templateString context+    return $ fromContext (M.insert (B.pack "body") body context) -renderAndWrite :: FilePath -> Page -> IO ()-renderAndWrite templatePath page = do-    rendered <- renderPage templatePath page-    let destination = toDestination $ getURL rendered-    makeDirectories destination-    B.writeFile destination (getBody rendered)+-- | Render each renderable with the given template, then concatenate the+--   result.+renderAndConcat :: Renderable a => FilePath -> [a] -> IO B.ByteString+renderAndConcat templatePath renderables = foldM concatRender' B.empty renderables+    where concatRender' :: Renderable a => B.ByteString -> a -> IO B.ByteString+          concatRender' chunk renderable = do+              rendered <- render templatePath renderable+              let body = getBody rendered+              return $ B.append chunk $ body +-- | Chain a render action for a page with a number of templates. This will+--   also write the result to the site destination. This is the preferred way+--   to do general rendering.+renderChain :: Renderable a => [FilePath] -> a -> IO ()+renderChain templates renderable =+    depends (getURL renderable) (getDependencies renderable ++ templates) $+        do initialPage <- toContext renderable+           result <- foldM (flip render) (fromContext initialPage) templates+           writePage result++-- | Mark a certain file as static, so it will just be copied when the site is+--   generated. static :: FilePath -> IO ()-static source = do-    makeDirectories destination-    copyFile source destination+static source = depends destination [source]+        (makeDirectories destination >> copyFile source destination)     where destination = toDestination source +-- | Mark a whole directory as static. staticDirectory :: FilePath -> IO () staticDirectory dir =      getRecursiveContents dir >>= mapM_ static
+ src/Text/Hakyll/Renderable.hs view
@@ -0,0 +1,18 @@+module Text.Hakyll.Renderable+    ( Renderable(toContext, getDependencies, getURL)+    ) where++import System.FilePath+import Text.Template++-- | A class for datatypes that can be rendered to pages.+class Renderable a where+    -- | Get a context to do substitutions with.+    toContext :: a -> IO Context++    -- | Get the dependencies for the renderable. This is used for cache+    --   invalidation.+    getDependencies :: a -> [FilePath]++    -- | Get the destination for the renderable.+    getURL :: a -> FilePath
+ src/Text/Hakyll/Renderables.hs view
@@ -0,0 +1,50 @@+module Text.Hakyll.Renderables+    ( CustomPage,+      createCustomPage,+      PagePath,+      createPagePath+    ) where++import System.FilePath+import qualified Data.ByteString.Lazy.Char8 as B+import qualified Data.Map as M+import Control.Monad+import Text.Hakyll.Util+import Text.Hakyll.Page+import Text.Hakyll.Renderable++-- | A custom page.+data CustomPage = CustomPage +    { url :: String,+      dependencies :: [FilePath],+      mapping :: [(String, Either String (IO B.ByteString))]+    }++-- | Create a custom page.+createCustomPage :: String -- ^ Destination of the page, relative to _site.+                 -> [FilePath] -- ^ Dependencies of the page.+                 -> [(String, Either String (IO B.ByteString))] -- ^ Key - value mapping for rendering.+                 -> CustomPage+createCustomPage = CustomPage++instance Renderable CustomPage where+    getDependencies = dependencies+    getURL = url+    toContext page = do+        values <- mapM (either (return . B.pack) (>>= return) . snd) (mapping page)+        let keys = map (B.pack . fst) (mapping page)+        return $ M.fromList $ (B.pack "url", B.pack $ url page) : zip keys values ++-- | PagePath is a class that wraps a FilePath. This is used to render Pages+--   without reading them first through use of caching.+data PagePath = PagePath FilePath++-- | Create a PagePath from a FilePath.+createPagePath :: FilePath -> PagePath+createPagePath = PagePath++-- We can render filepaths+instance Renderable PagePath where+    getDependencies (PagePath path) = return path+    getURL (PagePath path) = toURL path+    toContext (PagePath path) = readPage path >>= toContext
src/Text/Hakyll/Util.hs view
@@ -1,11 +1,12 @@ module Text.Hakyll.Util      ( toDestination,       toCache,+      toURL,       makeDirectories,       getRecursiveContents,       trim,       split,-      isCacheFileValid+      isCacheValid     ) where  import System.Directory@@ -14,12 +15,18 @@ import Data.Char import Data.List +-- | Convert a relative filepath to a filepath in the destination (_site). toDestination :: FilePath -> FilePath toDestination path = "_site" </> path +-- | Convert a relative filepath to a filepath in the cache (_cache). toCache :: FilePath -> FilePath toCache path = "_cache" </> path +-- | Get the url for a given page.+toURL :: FilePath -> FilePath+toURL = flip addExtension ".html" . dropExtension+ -- | Given a path to a file, try to make the path writable by making --   all directories on the path. makeDirectories :: FilePath -> IO ()@@ -56,8 +63,9 @@                                                     else Just (x, tail xs)  -- | Check is a cache file is still valid.-isCacheFileValid :: FilePath -> FilePath -> IO Bool-isCacheFileValid cache file = doesFileExist cache >>= \exists ->+isCacheValid :: FilePath -> [FilePath] -> IO Bool+isCacheValid cache depends = doesFileExist cache >>= \exists ->     if not exists then return False-                  else liftM2 (<=) (getModificationTime file)-                                   (getModificationTime cache)+                  else do dependsModified <- (mapM getModificationTime depends) >>= return . maximum+                          cacheModified <- getModificationTime cache+                          return (cacheModified >= dependsModified)