packages feed

hakyll (empty) → 0.1

raw patch · 6 files changed

+295/−0 lines, 6 filesdep +basedep +bytestringdep +containerssetup-changed

Dependencies added: base, bytestring, containers, directory, filepath, pandoc, template

Files

+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2009, Jasper Van der Jeugt+All rights reserved.+Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:+    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.+    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.+    * Neither the name of Jasper Van der Jeugt nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hakyll.cabal view
@@ -0,0 +1,25 @@+Name:           hakyll+Version:        0.1++Synopsis:       A simple static site generator library.+Description:+  A simple static site generator library , mainly aimed at+  creating blogs.+Author:         Jasper Van der Jeugt+Maintainer:     jaspervdj@gmail.com+Homepage:       http://github.com/jaspervdj/Hakyll+License:        BSD3+License-File:   LICENSE+Category:       Text+Cabal-Version:  >= 1.2++build-type:     Simple++library+  ghc-options: -Wall+  hs-source-dirs: src/+  build-depends:   base >= 4 && < 5, template, filepath, directory, containers, bytestring,+                   pandoc >= 1+  exposed-modules: Text.Hakyll.Render+                   Text.Hakyll.Page+                   Text.Hakyll.Util
+ src/Text/Hakyll/Page.hs view
@@ -0,0 +1,128 @@+module Text.Hakyll.Page +    ( Page,+      PageValue,+      addContext,+      getURL,+      getBody,+      readPage,+      pageFromList,+      concatPages,+      concatPagesWith+    ) where++import qualified Data.Map as M+import qualified Data.List as L+import qualified Data.ByteString.Lazy.Char8 as B+import Data.Maybe+import Control.Monad++import System.FilePath+import System.IO++import Text.Hakyll.Util+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++-- | We use a ByteString for obvious reasons.+type PageValue = B.ByteString++-- | Add a key-value mapping to the Page.+addContext :: String -> String -> Page -> Page+addContext key value = M.insert key (B.pack value)++-- | 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."++-- | 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++writerOptions :: WriterOptions+writerOptions = defaultWriterOptions++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++readMetaData :: Handle -> IO [(String, String)]+readMetaData handle = do+    line <- hGetLine handle+    if isDelimiter line then return []+                        else do others <- readMetaData handle+                                return $ (trimPair . break (== ':')) line : others+        where trimPair (key, value) = (trim key, trim $ tail value)++isDelimiter :: String -> Bool+isDelimiter = L.isPrefixOf "---"++-- | Used for caching of files.+cachePage :: Page -> IO ()+cachePage page = do+    let destination = toCache $ getURL page+    makeDirectories destination+    handle <- openFile destination WriteMode+    hPutStrLn handle "---"+    mapM_ (writePair handle) $ M.toList page+    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 ""++-- | 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+--   pages are not templates, so they should not contain $identifiers.+readPage :: FilePath -> IO Page+readPage pagePath = do+    -- Check cache.+    getFromCache <- isCacheFileValid cacheFile pagePath+    let path = if getFromCache then cacheFile else pagePath++    -- Read file.+    handle <- openFile path ReadMode+    line <- hGetLine handle+    (context, body) <- if isDelimiter line+                            then do md <- readMetaData handle+                                    c <- hGetContents handle+                                    return (md, c)+                            else hGetContents handle >>= \b -> return ([], line ++ b)++    -- 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++    -- Cache if needed+    if getFromCache then return () else cachePage page+    return page+    where url = addExtension (dropExtension pagePath) ".html"+          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"++-- | 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)
+ src/Text/Hakyll/Render.hs view
@@ -0,0 +1,46 @@+module Text.Hakyll.Render +    ( renderPage,+      renderAndWrite,+      static,+      staticDirectory+    ) where++import Text.Template+import qualified Data.ByteString.Lazy.Char8 as B+import qualified Data.Map as M+import Control.Monad++import System.Directory+import System.IO++import Text.Hakyll.Page+import Text.Hakyll.Util++createContext :: Page -> Context+createContext = M.fromList . map packPair . M.toList+    where packPair (a, b) = (B.pack a, b)++renderPage :: FilePath -> Page -> IO Page+renderPage templatePath page = 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++renderAndWrite :: FilePath -> Page -> IO ()+renderAndWrite templatePath page = do+    rendered <- renderPage templatePath page+    let destination = toDestination $ getURL rendered+    makeDirectories destination+    B.writeFile destination (getBody rendered)++static :: FilePath -> IO ()+static source = do+    makeDirectories destination+    copyFile source destination+    where destination = toDestination source++staticDirectory :: FilePath -> IO ()+staticDirectory dir = +    getRecursiveContents dir >>= mapM_ static
+ src/Text/Hakyll/Util.hs view
@@ -0,0 +1,63 @@+module Text.Hakyll.Util +    ( toDestination,+      toCache,+      makeDirectories,+      getRecursiveContents,+      trim,+      split,+      isCacheFileValid+    ) where++import System.Directory+import System.FilePath+import Control.Monad+import Data.Char+import Data.List++toDestination :: FilePath -> FilePath+toDestination path = "_site" </> path++toCache :: FilePath -> FilePath+toCache path = "_cache" </> path++-- | Given a path to a file, try to make the path writable by making+--   all directories on the path.+makeDirectories :: FilePath -> IO ()+makeDirectories path = createDirectoryIfMissing True dir+    where dir = takeDirectory path++-- | Get all contents of a directory. Note that files starting with a dot (.)+--   will be ignored.+getRecursiveContents :: FilePath -> IO [FilePath]+getRecursiveContents topdir = do+    names <- getDirectoryContents topdir+    let properNames = filter isProper names+    paths <- forM properNames $ \name -> do+        let path = topdir </> name+        isDirectory <- doesDirectoryExist path+        if isDirectory+            then getRecursiveContents path+            else return [path]+    return (concat paths)+    where isProper = not . (== '.') . head++-- | Trim a string (drop spaces and tabs at both sides).+trim :: String -> String+trim = reverse . trim' . reverse . trim'+    where trim' = dropWhile isSpace++-- | Split a list at a certain element.+split :: (Eq a) => a -> [a] -> [[a]]+split element = unfoldr splitOnce+    where splitOnce l = let r = break (== element) l+                        in case r of ([], []) -> Nothing+                                     (x, xs) -> if null xs+                                                    then Just (x, [])+                                                    else Just (x, tail xs)++-- | Check is a cache file is still valid.+isCacheFileValid :: FilePath -> FilePath -> IO Bool+isCacheFileValid cache file = doesFileExist cache >>= \exists ->+    if not exists then return False+                  else liftM2 (<=) (getModificationTime file)+                                   (getModificationTime cache)