packages feed

hakyll 4.12.5.2 → 4.13.0.0

raw patch · 21 files changed

+597/−232 lines, 21 filesdep +template-haskelldep ~QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: template-haskell

Dependency ranges changed: QuickCheck

API changes (from Hackage documentation)

- Hakyll.Core.Compiler.Internal: [CompilerDone] :: a -> CompilerWrite -> CompilerResult a
- Hakyll.Core.Compiler.Internal: [CompilerError] :: [String] -> CompilerResult a
- Hakyll.Core.Compiler.Internal: [CompilerRequire] :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
- Hakyll.Core.Compiler.Internal: [CompilerSnapshot] :: Snapshot -> Compiler a -> CompilerResult a
- Hakyll.Web.Template: readTemplateElems :: String -> [TemplateElement]
- Hakyll.Web.Template: template :: [TemplateElement] -> Template
- Hakyll.Web.Template.Internal: [unTemplate] :: Template -> [TemplateElement]
- Hakyll.Web.Template.Internal: applyTemplate' :: forall a. [TemplateElement] -> Context a -> Item a -> Compiler String
- Hakyll.Web.Template.Internal: newtype Template
- Hakyll.Web.Template.Internal.Element: readTemplateElems :: String -> [TemplateElement]
- Hakyll.Web.Template.Internal.Element: readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
+ Hakyll.Core.Compiler: noResult :: String -> Compiler a
+ Hakyll.Core.Compiler: withErrorMessage :: String -> Compiler a -> Compiler a
+ Hakyll.Core.Compiler.Internal: CompilationFailure :: NonEmpty a -> CompilerErrors a
+ Hakyll.Core.Compiler.Internal: CompilationNoResult :: [a] -> CompilerErrors a
+ Hakyll.Core.Compiler.Internal: CompilerDone :: a -> CompilerWrite -> CompilerResult a
+ Hakyll.Core.Compiler.Internal: CompilerError :: CompilerErrors String -> CompilerResult a
+ Hakyll.Core.Compiler.Internal: CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
+ Hakyll.Core.Compiler.Internal: CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
+ Hakyll.Core.Compiler.Internal: compilerDebugEntries :: String -> [String] -> Compiler ()
+ Hakyll.Core.Compiler.Internal: compilerErrorMessages :: CompilerErrors a -> [a]
+ Hakyll.Core.Compiler.Internal: compilerNoResult :: [String] -> Compiler a
+ Hakyll.Core.Compiler.Internal: compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
+ Hakyll.Core.Compiler.Internal: data CompilerErrors a
+ Hakyll.Core.Compiler.Internal: instance GHC.Base.Functor Hakyll.Core.Compiler.Internal.CompilerErrors
+ Hakyll.Web.Template: compileTemplateItem :: Item String -> Compiler Template
+ Hakyll.Web.Template: embedTemplate :: FilePath -> Q Exp
+ Hakyll.Web.Template.Context: EmptyField :: ContextField
+ Hakyll.Web.Template.Internal: [tplElements] :: Template -> [TemplateElement]
+ Hakyll.Web.Template.Internal: [tplOrigin] :: Template -> FilePath
+ Hakyll.Web.Template.Internal: compileTemplateItem :: Item String -> Compiler Template
+ Hakyll.Web.Template.Internal: data Template
+ Hakyll.Web.Template.Internal: instance GHC.Generics.Generic Hakyll.Web.Template.Internal.Template
+ Hakyll.Web.Template.Internal.Element: parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
- Hakyll.Core.Compiler.Internal: compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
+ Hakyll.Core.Compiler.Internal: compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
- Hakyll.Web.Feed: renderAtomWithTemplates :: String -> String -> FeedConfiguration -> Context String -> [Item String] -> Compiler (Item String)
+ Hakyll.Web.Feed: renderAtomWithTemplates :: Template -> Template -> FeedConfiguration -> Context String -> [Item String] -> Compiler (Item String)
- Hakyll.Web.Feed: renderRssWithTemplates :: String -> String -> FeedConfiguration -> Context String -> [Item String] -> Compiler (Item String)
+ Hakyll.Web.Feed: renderRssWithTemplates :: Template -> Template -> FeedConfiguration -> Context String -> [Item String] -> Compiler (Item String)
- Hakyll.Web.Template.Internal: Template :: [TemplateElement] -> Template
+ Hakyll.Web.Template.Internal: Template :: [TemplateElement] -> FilePath -> Template
- Hakyll.Web.Template.Internal: template :: [TemplateElement] -> Template
+ Hakyll.Web.Template.Internal: template :: FilePath -> [TemplateElement] -> Template

Files

CHANGELOG.md view
@@ -4,6 +4,19 @@  # Releases +## Hakyll 4.13.0.0 (2019-08-30)++- Improved documentation in many places (contribution by Bergi)+- Significantly improve error messages when reading and applying templates+  (contribution by Bergi)+    * `empty` and `fail` for `Compiler` now fail without or with a message,+      allowing for much better debug output+    * `renderFeed`, `renderRssWithTemplates` and `renderAtomWithTemplates` now+      take `Template` rather than `String` arguments+- Add option to specify date in directory structure,e.g.+  `posts/2019/05/10/tomorrow.md` (contribution by Taeer Bar-Ya)+- Bump QuickCheck to 2.13+ ## Hakyll 4.12.5.2 (2019-05-09)  - Bump pandoc to 2.7
hakyll.cabal view
@@ -1,5 +1,5 @@ Name:    hakyll-Version: 4.12.5.2+Version: 4.13.0.0  Synopsis: A static website compiler library Description:@@ -172,10 +172,12 @@     data-default         >= 0.4      && < 0.8,     deepseq              >= 1.3      && < 1.5,     directory            >= 1.0      && < 1.4,+    file-embed           >= 0.0.10.1 && < 0.0.12,     filepath             >= 1.0      && < 1.5,     lrucache             >= 1.1.1    && < 1.3,     mtl                  >= 1        && < 2.3,     network-uri          >= 2.6      && < 2.7,+    optparse-applicative >= 0.12     && < 0.15,     parsec               >= 3.0      && < 3.2,     process              >= 1.6      && < 1.7,     random               >= 1.0      && < 1.2,@@ -183,14 +185,13 @@     resourcet            >= 1.1      && < 1.3,     scientific           >= 0.3.4    && < 0.4,     tagsoup              >= 0.13.1   && < 0.15,+    template-haskell     >= 2.14     && < 2.15,     text                 >= 0.11     && < 1.3,     time                 >= 1.8      && < 1.10,     time-locale-compat   >= 0.1      && < 0.2,     unordered-containers >= 0.2      && < 0.3,     vector               >= 0.11     && < 0.13,-    yaml                 >= 0.8.11   && < 0.12,-    optparse-applicative >= 0.12     && < 0.15,-    file-embed           >= 0.0.10.1 && < 0.0.12+    yaml                 >= 0.8.11   && < 0.12    If flag(previewServer)     Build-depends:@@ -254,13 +255,14 @@     Hakyll.Web.CompressCss.Tests     Hakyll.Web.Html.RelativizeUrls.Tests     Hakyll.Web.Html.Tests+    Hakyll.Web.Tags.Tests     Hakyll.Web.Template.Context.Tests     Hakyll.Web.Template.Tests     TestSuite.Util    Build-Depends:     hakyll,-    QuickCheck                 >= 2.8  && < 2.13,+    QuickCheck                 >= 2.8  && < 2.14,     tasty                      >= 0.11 && < 1.3,     tasty-hunit                >= 0.9  && < 0.11,     tasty-quickcheck           >= 0.8  && < 0.11,
lib/Hakyll/Core/Compiler.hs view
@@ -24,13 +24,16 @@     , cached     , unsafeCompiler     , debugCompiler+    , noResult+    , withErrorMessage     ) where   ---------------------------------------------------------------------------------import           Control.Monad                 (when, unless)+import           Control.Monad                 (unless, when, (>=>)) import           Data.Binary                   (Binary) import           Data.ByteString.Lazy          (ByteString)+import qualified Data.List.NonEmpty            as NonEmpty import           Data.Typeable                 (Typeable) import           System.Environment            (getProgName) import           System.FilePath               (takeExtension)@@ -62,6 +65,7 @@   --------------------------------------------------------------------------------+-- | Create an item from the underlying identifier and a given value. makeItem :: a -> Compiler (Item a) makeItem x = do     identifier <- getUnderlying@@ -141,6 +145,10 @@   --------------------------------------------------------------------------------+-- | Turn on caching for a compilation value to avoid recomputing it+-- on subsequent Hakyll runs.+-- The storage key consists of the underlying identifier of the compiled+-- ressource and the given name. cached :: (Binary a, Typeable a)        => String        -> Compiler a@@ -177,12 +185,37 @@   --------------------------------------------------------------------------------+-- | Run an IO computation without dependencies in a Compiler unsafeCompiler :: IO a -> Compiler a unsafeCompiler = compilerUnsafeIO   ----------------------------------------------------------------------------------- | Compiler for debugging purposes+-- | Fail so that it is treated as non-defined in an @\$if()\$@ branching+-- "Hakyll.Web.Template" macro, and alternative+-- 'Hakyll.Web.Template.Context.Context's are tried+--+-- @since 4.13.0+noResult :: String -> Compiler a+noResult = compilerNoResult . return+++--------------------------------------------------------------------------------+-- | Prepend an error line to the error, if there is one.  This allows you to+-- add helpful context to error messages.+--+-- @since 4.13.0+withErrorMessage :: String -> Compiler a -> Compiler a+withErrorMessage x = do+    compilerTry >=> either (compilerResult . CompilerError . prepend) return+  where+    prepend (CompilationFailure  es) = CompilationFailure  (x `NonEmpty.cons` es)+    prepend (CompilationNoResult es) = CompilationNoResult (x : es)+++--------------------------------------------------------------------------------+-- | Compiler for debugging purposes.+-- Passes a message to the debug logger that is printed in verbose mode. debugCompiler :: String -> Compiler () debugCompiler msg = do     logger <- compilerLogger <$> compilerAsk
lib/Hakyll/Core/Compiler/Internal.hs view
@@ -1,6 +1,7 @@ -------------------------------------------------------------------------------- -- | Internally used compiler module {-# LANGUAGE CPP                        #-}+{-# LANGUAGE DeriveFunctor              #-} {-# LANGUAGE FlexibleInstances          #-} {-# LANGUAGE GADTs                      #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -10,19 +11,26 @@       Snapshot     , CompilerRead (..)     , CompilerWrite (..)+    , CompilerErrors (..)     , CompilerResult (..)     , Compiler (..)     , runCompiler        -- * Core operations+    , compilerResult     , compilerTell     , compilerAsk+    , compilerUnsafeIO++      -- * Error operations     , compilerThrow+    , compilerNoResult     , compilerCatch-    , compilerResult-    , compilerUnsafeIO+    , compilerTry+    , compilerErrorMessages        -- * Utilities+    , compilerDebugEntries     , compilerTellDependencies     , compilerTellCacheHits     ) where@@ -32,7 +40,9 @@ import           Control.Applicative            (Alternative (..)) import           Control.Exception              (SomeException, handle) import           Control.Monad                  (forM_)-import           Control.Monad.Except            (MonadError (..))+import           Control.Monad.Except           (MonadError (..))+import           Data.List.NonEmpty             (NonEmpty (..))+import qualified Data.List.NonEmpty             as NonEmpty #if MIN_VERSION_base(4,9,0) import           Data.Semigroup                 (Semigroup (..)) #endif@@ -45,7 +55,6 @@ import           Hakyll.Core.Dependencies import           Hakyll.Core.Identifier import           Hakyll.Core.Identifier.Pattern-import           Hakyll.Core.Logger             (Logger) import qualified Hakyll.Core.Logger             as Logger import           Hakyll.Core.Metadata import           Hakyll.Core.Provider@@ -75,7 +84,7 @@     , -- | Compiler store       compilerStore      :: Store     , -- | Logger-      compilerLogger     :: Logger+      compilerLogger     :: Logger.Logger     }  @@ -104,14 +113,32 @@   ---------------------------------------------------------------------------------data CompilerResult a where-    CompilerDone     :: a -> CompilerWrite -> CompilerResult a-    CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a-    CompilerError    :: [String] -> CompilerResult a-    CompilerRequire  :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a+-- | Distinguishes reasons in a 'CompilerError'+data CompilerErrors a+    -- | One or more exceptions occured during compilation+    = CompilationFailure (NonEmpty a)+    -- | Absence of any result, most notably in template contexts.  May still+    -- have error messages.+    | CompilationNoResult [a]+    deriving Functor  +-- | Unwrap a `CompilerErrors`+compilerErrorMessages :: CompilerErrors a -> [a]+compilerErrorMessages (CompilationFailure x)  = NonEmpty.toList x+compilerErrorMessages (CompilationNoResult x) = x++ --------------------------------------------------------------------------------+-- | An intermediate result of a compilation step+data CompilerResult a+    = CompilerDone a CompilerWrite+    | CompilerSnapshot Snapshot (Compiler a)+    | CompilerRequire (Identifier, Snapshot) (Compiler a)+    | CompilerError (CompilerErrors String)+++-------------------------------------------------------------------------------- -- | A monad which lets you compile items and takes care of dependency tracking -- for you. newtype Compiler a = Compiler@@ -126,14 +153,14 @@         return $ case res of             CompilerDone x w      -> CompilerDone (f x) w             CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')-            CompilerError e       -> CompilerError e             CompilerRequire i c'  -> CompilerRequire i (fmap f c')+            CompilerError e       -> CompilerError e     {-# INLINE fmap #-}   -------------------------------------------------------------------------------- instance Monad Compiler where-    return x = Compiler $ \_ -> return $ CompilerDone x mempty+    return x = compilerResult $ CompilerDone x mempty     {-# INLINE return #-}      Compiler c >>= f = Compiler $ \r -> do@@ -146,14 +173,14 @@                     CompilerSnapshot s c' -> CompilerSnapshot s $ do                         compilerTell w  -- Save dependencies!                         c'-                    CompilerError e       -> CompilerError e                     CompilerRequire i c'  -> CompilerRequire i $ do                         compilerTell w  -- Save dependencies!                         c'+                    CompilerError e       -> CompilerError e              CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)-            CompilerError e       -> return $ CompilerError e             CompilerRequire i c'  -> return $ CompilerRequire i (c' >>= f)+            CompilerError e       -> return $ CompilerError e     {-# INLINE (>>=) #-}      fail = compilerThrow . return@@ -170,87 +197,145 @@   --------------------------------------------------------------------------------+-- | Access provided metadata from anywhere instance MonadMetadata Compiler where     getMetadata = compilerGetMetadata     getMatches  = compilerGetMatches   --------------------------------------------------------------------------------+-- | Compilation may fail with multiple error messages.+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult' instance MonadError [String] Compiler where-  throwError = compilerThrow-  catchError = compilerCatch+    throwError = compilerThrow+    catchError c = compilerCatch c . (. compilerErrorMessages)   --------------------------------------------------------------------------------+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a) runCompiler compiler read' = handle handler $ unCompiler compiler read'   where     handler :: SomeException -> IO (CompilerResult a)-    handler e = return $ CompilerError [show e]+    handler e = return $ CompilerError $ CompilationFailure $ show e :| []   --------------------------------------------------------------------------------+-- | Trying alternative compilers if the first fails, regardless whether through+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.+-- Aggregates error messages if all fail. instance Alternative Compiler where-    empty   = compilerThrow []-    x <|> y = compilerCatch x $ \es -> do-        logger <- compilerLogger <$> compilerAsk-        forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $-            "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e-        y+    empty   = compilerNoResult []+    x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->+        case (rx, ry) of+          (CompilationFailure xs,  CompilationFailure ys)  ->+            compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys+          (CompilationFailure xs,  CompilationNoResult ys) ->+            debug ys >> compilerThrow (NonEmpty.toList xs)+          (CompilationNoResult xs, CompilationFailure ys)  ->+            debug xs >> compilerThrow (NonEmpty.toList ys)+          (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys+        ))+      where+        debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"     {-# INLINE (<|>) #-}   --------------------------------------------------------------------------------+-- | Put the result back in a compiler+compilerResult :: CompilerResult a -> Compiler a+compilerResult x = Compiler $ \_ -> return x+{-# INLINE compilerResult #-}+++--------------------------------------------------------------------------------+-- | Get the current environment compilerAsk :: Compiler CompilerRead compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty {-# INLINE compilerAsk #-}   --------------------------------------------------------------------------------+-- | Put a 'CompilerWrite' compilerTell :: CompilerWrite -> Compiler ()-compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps+compilerTell = compilerResult . CompilerDone () {-# INLINE compilerTell #-}   --------------------------------------------------------------------------------+-- | Run an IO computation without dependencies in a Compiler+compilerUnsafeIO :: IO a -> Compiler a+compilerUnsafeIO io = Compiler $ \_ -> do+    x <- io+    return $ CompilerDone x mempty+{-# INLINE compilerUnsafeIO #-}+++--------------------------------------------------------------------------------+-- | Throw errors in the 'Compiler'.+--+-- If no messages are given, this is considered a 'CompilationNoResult' error.+-- Otherwise, it is treated as a proper compilation failure. compilerThrow :: [String] -> Compiler a-compilerThrow es = Compiler $ \_ -> return $ CompilerError es-{-# INLINE compilerThrow #-}+compilerThrow = compilerResult . CompilerError .+    maybe (CompilationNoResult []) CompilationFailure .+    NonEmpty.nonEmpty +-- | Put a 'CompilerError' with  multiple messages as 'CompilationNoResult'+compilerNoResult :: [String] -> Compiler a+compilerNoResult = compilerResult . CompilerError . CompilationNoResult + ---------------------------------------------------------------------------------compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a+-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'+--+-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right+compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)+compilerTry (Compiler x) = Compiler $ \r -> do+    res <- x r+    case res of+        CompilerDone res' w  -> return (CompilerDone (Right res') w)+        CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))+        CompilerRequire i c  -> return (CompilerRequire i (compilerTry c))+        CompilerError e      -> return (CompilerDone (Left e) mempty)+{-# INLINE compilerTry #-}+++--------------------------------------------------------------------------------+-- | Allows you to recover from 'CompilerError's.+-- Uses the same parameter order as 'catchError' so that it can be used infix.+--+-- prop> c `compilerCatch` f = compilerTry c >>= either f return+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a compilerCatch (Compiler x) f = Compiler $ \r -> do     res <- x r     case res of         CompilerDone res' w  -> return (CompilerDone res' w)         CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))-        CompilerError e      -> unCompiler (f e) r         CompilerRequire i c  -> return (CompilerRequire i (compilerCatch c f))+        CompilerError e      -> unCompiler (f e) r {-# INLINE compilerCatch #-}   ----------------------------------------------------------------------------------- | Put the result back in a compiler-compilerResult :: CompilerResult a -> Compiler a-compilerResult x = Compiler $ \_ -> return x-{-# INLINE compilerResult #-}-+compilerDebugLog :: [String] -> Compiler ()+compilerDebugLog ms = do+  logger <- compilerLogger <$> compilerAsk+  compilerUnsafeIO $ forM_ ms $ Logger.debug logger  ---------------------------------------------------------------------------------compilerUnsafeIO :: IO a -> Compiler a-compilerUnsafeIO io = Compiler $ \_ -> do-    x <- io-    return $ CompilerDone x mempty-{-# INLINE compilerUnsafeIO #-}+-- | Pass a list of messages with a heading to the debug logger+compilerDebugEntries :: String -> [String] -> Compiler ()+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent+  where+    indent = unlines . map ("    "++) . lines   -------------------------------------------------------------------------------- compilerTellDependencies :: [Dependency] -> Compiler () compilerTellDependencies ds = do-  logger <- compilerLogger <$> compilerAsk-  forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $-      "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d+  compilerDebugLog $ map (\d ->+      "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds   compilerTell mempty {compilerDependencies = ds} {-# INLINE compilerTellDependencies #-} 
lib/Hakyll/Core/Compiler/Require.hs view
@@ -91,6 +91,7 @@   --------------------------------------------------------------------------------+-- | A shortcut for only requiring the body for a specific snapshot of an item loadSnapshotBody :: (Binary a, Typeable a)                  => Identifier -> Snapshot -> Compiler a loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot@@ -103,6 +104,7 @@   --------------------------------------------------------------------------------+-- | Load a specific snapshot for each of dynamic list of items loadAllSnapshots :: (Binary a, Typeable a)                  => Pattern -> Snapshot -> Compiler [Item a] loadAllSnapshots pattern snapshot = do
lib/Hakyll/Core/Logger.hs view
@@ -18,6 +18,7 @@ import           Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar) import           Control.Monad           (forever) import           Control.Monad.Trans     (MonadIO, liftIO)+import           Data.List               (intercalate) import           Prelude                 hiding (error)  @@ -79,7 +80,7 @@  -------------------------------------------------------------------------------- error :: MonadIO m => Logger -> String -> m ()-error l m = string l Error $ "  [ERROR] " ++ m+error l m = string l Error $ "  [ERROR] " ++ indent m   --------------------------------------------------------------------------------@@ -89,9 +90,14 @@  -------------------------------------------------------------------------------- message :: MonadIO m => Logger -> String -> m ()-message l m = string l Message $ "  " ++ m+message l m = string l Message $ "  " ++ indent m   -------------------------------------------------------------------------------- debug :: MonadIO m => Logger -> String -> m ()-debug l m = string l Debug $ "  [DEBUG] " ++ m+debug l m = string l Debug $ "  [DEBUG] " ++ indent m+++--------------------------------------------------------------------------------+indent :: String -> String+indent = intercalate "\n    " . lines
lib/Hakyll/Core/Provider/Metadata.hs view
@@ -27,6 +27,7 @@ import           Hakyll.Core.Metadata import           Hakyll.Core.Provider.Internal import           System.IO                     as IO+import           System.IO.Error               (modifyIOError, ioeSetLocation)   --------------------------------------------------------------------------------@@ -51,7 +52,7 @@ -------------------------------------------------------------------------------- loadMetadataHeader :: FilePath -> IO (Metadata, String) loadMetadataHeader fp = do-    fileContent <- readFile fp+    fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp     case parsePage fileContent of         Right x   -> return x         Left  err -> throwIO $ MetadataException fp err@@ -60,7 +61,7 @@ -------------------------------------------------------------------------------- loadMetadataFile :: FilePath -> IO Metadata loadMetadataFile fp = do-    fileContent <- B.readFile fp+    fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp     let errOrMeta = Yaml.decodeEither' fileContent     either (fail . show) return errOrMeta 
lib/Hakyll/Core/Runtime.hs view
@@ -199,9 +199,9 @@         result <- liftIO $ runCompiler compiler read'         case result of             -- Rethrow error-            CompilerError [] -> throwError-                "Compiler failed but no info given, try running with -v?"-            CompilerError es -> throwError $ intercalate "; " es+            CompilerError e -> throwError $ case compilerErrorMessages e of+                [] -> "Compiler failed but no info given, try running with -v?"+                es -> intercalate "; " es              -- Signal that a snapshot was saved ->             CompilerSnapshot snapshot c -> do
lib/Hakyll/Core/Store.hs view
@@ -16,7 +16,6 @@   ---------------------------------------------------------------------------------import           Control.Exception    (IOException, handle) import qualified Crypto.Hash.MD5      as MD5 import           Data.Binary          (Binary, decode, encodeFile) import qualified Data.ByteString      as B@@ -32,6 +31,8 @@ import           System.Directory     (doesFileExist, removeFile) import           System.FilePath      ((</>)) import           System.IO            (IOMode (..), hClose, openFile)+import           System.IO.Error      (catchIOError, ioeSetFileName,+                                       ioeSetLocation, modifyIOError)   --------------------------------------------------------------------------------@@ -84,6 +85,14 @@   where     csize = Just 500 +--------------------------------------------------------------------------------+withStore :: Store -> String -> (String -> FilePath -> IO a) -> [String] -> IO a+withStore store loc run identifier = modifyIOError handle $ run key path+  where+    key = hash identifier+    path = storeDirectory store </> key+    handle e = e `ioeSetFileName` (path ++ " for " ++ intercalate "/" identifier)+                 `ioeSetLocation` ("Store." ++ loc)  -------------------------------------------------------------------------------- -- | Auxiliary: add an item to the in-memory cache@@ -124,17 +133,16 @@ -------------------------------------------------------------------------------- -- | Store an item set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()-set store identifier value = do-    encodeFile (storeDirectory store </> key) value+set store identifier value = withStore store "set" (\key path -> do+    encodeFile path value     cacheInsert store key value-  where-    key = hash identifier+  ) identifier   -------------------------------------------------------------------------------- -- | Load an item get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)-get store identifier = do+get store = withStore store "get" $ \key path -> do     -- First check the in-memory map     ref <- cacheLookup store key     case ref of@@ -146,17 +154,14 @@                 then return NotFound                 -- Found in the filesystem                 else do-                    v <- decodeClose+                    v <- decodeClose path                     cacheInsert store key v                     return $ Found v         -- Found in the in-memory map (or wrong type), just return         s -> return s   where-    key  = hash identifier-    path = storeDirectory store </> key-     -- 'decodeFile' from Data.Binary which closes the file ASAP-    decodeClose = do+    decodeClose path = do         h   <- openFile path ReadMode         lbs <- BL.hGetContents h         BL.length lbs `seq` hClose h@@ -166,28 +171,23 @@ -------------------------------------------------------------------------------- -- | Strict function isMember :: Store -> [String] -> IO Bool-isMember store identifier = do+isMember store = withStore store "isMember" $ \key path -> do     inCache <- cacheIsMember store key     if inCache then return True else doesFileExist path-  where-    key  = hash identifier-    path = storeDirectory store </> key   -------------------------------------------------------------------------------- -- | Delete an item delete :: Store -> [String] -> IO ()-delete store identifier = do+delete store = withStore store "delete" $ \key path -> do     cacheDelete store key-    deleteFile $ storeDirectory store </> key-  where-    key  = hash identifier+    deleteFile path   -------------------------------------------------------------------------------- -- | Delete a file unless it doesn't exist... deleteFile :: FilePath -> IO ()-deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile+deleteFile = (`catchIOError` \_ -> return ()) . removeFile   --------------------------------------------------------------------------------
lib/Hakyll/Web/Feed.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell   #-}  -------------------------------------------------------------------------------- -- | A Module that allows easy rendering of RSS feeds.@@ -37,28 +38,27 @@   ---------------------------------------------------------------------------------import           Data.FileEmbed              (makeRelativeToProject, embedFile)-import qualified Data.Text                   as T-import qualified Data.Text.Encoding          as T+import           Data.FileEmbed              (makeRelativeToProject)   ---------------------------------------------------------------------------------rssTemplate :: String-rssTemplate = T.unpack $-    T.decodeUtf8 $(makeRelativeToProject "data/templates/rss.xml" >>= embedFile)+rssTemplate :: Template+rssTemplate =+    $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate) -rssItemTemplate :: String-rssItemTemplate = T.unpack $-    T.decodeUtf8 $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedFile)+rssItemTemplate :: Template+rssItemTemplate =+    $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate) -atomTemplate :: String-atomTemplate = T.unpack $-    T.decodeUtf8 $(makeRelativeToProject "data/templates/atom.xml" >>= embedFile)+atomTemplate :: Template+atomTemplate =+    $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate) -atomItemTemplate :: String-atomItemTemplate = T.unpack $-    T.decodeUtf8 $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedFile)+atomItemTemplate :: Template+atomItemTemplate =+    $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate) + -------------------------------------------------------------------------------- -- | This is a data structure to keep the configuration of a feed. data FeedConfiguration = FeedConfiguration@@ -77,16 +77,13 @@  -------------------------------------------------------------------------------- -- | Abstract function to render any feed.-renderFeed :: String                  -- ^ Default feed template-           -> String                  -- ^ Default item template+renderFeed :: Template                -- ^ Default feed template+           -> Template                -- ^ Default item template            -> FeedConfiguration       -- ^ Feed configuration            -> Context String          -- ^ Context for the items            -> [Item String]           -- ^ Input items            -> Compiler (Item String)  -- ^ Resulting item-renderFeed defFeed defItem config itemContext items = do-    feedTpl <- readTemplateFile defFeed-    itemTpl <- readTemplateFile defItem-+renderFeed feedTpl itemTpl config itemContext items = do     protectedItems <- mapM (applyFilter protectCDATA) items     body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems     applyTemplate feedTpl feedContext body@@ -120,18 +117,14 @@     updatedField = field "updated" $ \_ -> case items of         []      -> return "Unknown"         (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of-            ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"             StringField s -> return s--    readTemplateFile :: String -> Compiler Template-    readTemplateFile value = pure $ template $ readTemplateElems value-+            _             -> fail "Hakyll.Web.Feed.renderFeed: Internal error"  -------------------------------------------------------------------------------- -- | Render an RSS feed using given templates with a number of items. renderRssWithTemplates ::-       String                  -- ^ Feed template-    -> String                  -- ^ Item template+       Template                -- ^ Feed template+    -> Template                -- ^ Item template     -> FeedConfiguration       -- ^ Feed configuration     -> Context String          -- ^ Item context     -> [Item String]           -- ^ Feed items@@ -144,8 +137,8 @@ -------------------------------------------------------------------------------- -- | Render an Atom feed using given templates with a number of items. renderAtomWithTemplates ::-       String                  -- ^ Feed template-    -> String                  -- ^ Item template+       Template                -- ^ Feed template+    -> Template                -- ^ Item template     -> FeedConfiguration       -- ^ Feed configuration     -> Context String          -- ^ Item context     -> [Item String]           -- ^ Feed items
lib/Hakyll/Web/Tags.hs view
@@ -113,7 +113,7 @@   ----------------------------------------------------------------------------------- | Obtain categories from a page.+-- | Obtain category from a page. getCategory :: MonadMetadata m => Identifier -> m [String] getCategory = return . return . takeBaseName . takeDirectory . toFilePath 
lib/Hakyll/Web/Template.hs view
@@ -138,19 +138,41 @@ -- >     3...2...1 -- > </p> --+{-# LANGUAGE TemplateHaskell #-} module Hakyll.Web.Template     ( Template-    , template-    , readTemplateElems     , templateBodyCompiler     , templateCompiler     , applyTemplate     , loadAndApplyTemplate     , applyAsTemplate     , readTemplate+    , compileTemplateItem     , unsafeReadTemplateFile+    , embedTemplate     ) where   -------------------------------------------------------------------------------- import           Hakyll.Web.Template.Internal+++--------------------------------------------------------------------------------+import           Data.FileEmbed               (embedFile)+import qualified Data.Text                    as T+import qualified Data.Text.Encoding           as T+import           Language.Haskell.TH          (Exp, Q)+++--------------------------------------------------------------------------------+-- | Embed template allows you embed a template within the Haskell binary.+-- Example:+--+-- > myTemplate :: Template+-- > myTemplate = $(embedTemplate "test.html")+embedTemplate :: FilePath -> Q Exp+embedTemplate filePath = [|+    let source = T.unpack $ T.decodeUtf8 $(embedFile filePath) in+    case parseTemplateElemsFile filePath source of+        Left  err -> error err+        Right tpl -> template filePath tpl |]
lib/Hakyll/Web/Template/Context.hs view
@@ -1,3 +1,23 @@+-- | This module provides 'Context's which are used to expand expressions in+-- templates and allow for arbitrary customisation.+--+-- 'Template's define a small expression DSL which consists of strings,+-- identifiers and function application. There is no type system, every value is+-- a string and on the top level they get substituted verbatim into the page.+--+-- For example, you can build a context that contains+--+-- > … <> functionField "concat" (const . concat) <> …+--+-- which will allow you to use the @concat@ identifier as a function that takes+-- arbitrarily many stings and concatenates them to a new string:+--+-- > $partial(concat("templates/categories/", category))$+--+-- This will evaluate the @category@ field in the context, then prepend he path,+-- and include the referenced file as a template.++ -------------------------------------------------------------------------------- {-# LANGUAGE CPP                       #-} {-# LANGUAGE ExistentialQuantification #-}@@ -34,7 +54,7 @@ -------------------------------------------------------------------------------- import           Control.Applicative           (Alternative (..)) import           Control.Monad                 (msum)-import           Data.List                     (intercalate)+import           Data.List                     (intercalate, tails) #if MIN_VERSION_base(4,9,0) import           Data.Semigroup                (Semigroup (..)) #endif@@ -50,13 +70,16 @@ import           Hakyll.Core.Provider import           Hakyll.Core.Util.String       (needlePrefix, splitAll) import           Hakyll.Web.Html-import           System.FilePath               (splitDirectories, takeBaseName, dropExtension)+import           Prelude                       hiding (id)+import           System.FilePath               (dropExtension, splitDirectories,+                                                takeBaseName)   -------------------------------------------------------------------------------- -- | Mostly for internal usage data ContextField-    = StringField String+    = EmptyField+    | StringField String     | forall a. ListField (Context a) [Item a]  @@ -81,6 +104,8 @@   --------------------------------------------------------------------------------+-- | Tries to find a key in the left context,+-- or when that fails in the right context. #if MIN_VERSION_base(4,9,0) instance Semigroup (Context a) where     (<>) (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i@@ -97,64 +122,101 @@  -------------------------------------------------------------------------------- field' :: String -> (Item a -> Compiler ContextField) -> Context a-field' key value = Context $ \k _ i -> if k == key then value i else empty+field' key value = Context $ \k _ i ->+    if k == key+        then value i+        else noResult $ "Tried field " ++ key   ----------------------------------------------------------------------------------- | Constructs a new field in the 'Context.'+-- | Constructs a new field for a 'Context'.+-- If the key matches, the compiler is run and its result is substituted in the+-- template.+--+-- If the compiler fails, the field will be considered non-existent+-- in an @$if()$@ macro or ultimately break the template application+-- (unless the key is found in another context when using '<>').+-- Use 'empty' or 'noResult' for intentional failures of fields used in+-- @$if()$@, to distinguish them from exceptions thrown with 'fail'. field     :: String                      -- ^ Key     -> (Item a -> Compiler String) -- ^ Function that constructs a value based-                                   -- on the item+                                   -- on the item (e.g. accessing metadata)     -> Context a field key value = field' key (fmap StringField . value)   -------------------------------------------------------------------------------- -- | Creates a 'field' to use with the @$if()$@ template macro.+-- Attempting to substitute the field into the template will cause an error. boolField     :: String     -> (Item a -> Bool)     -> Context a-boolField name f = field name (\i -> if f i-    then pure (error $ unwords ["no string value for bool field:",name])-    else empty)+boolField name f = field' name (\i -> if f i+    then return EmptyField+    else noResult $ "Field " ++ name ++ " is false")   ----------------------------------------------------------------------------------- | Creates a 'field' that does not depend on the 'Item'-constField :: String -> String -> Context a+-- | Creates a 'field' that does not depend on the 'Item' but always yields+-- the same string+constField :: String     -- ^ Key+           -> String     -- ^ Value+           -> Context a constField key = field key . const . return   --------------------------------------------------------------------------------+-- | Creates a list field to be consumed by a @$for(…)$@ expression.+-- The compiler returns multiple items which are rendered in the loop body+-- with the supplied context. listField :: String -> Context a -> Compiler [Item a] -> Context b listField key c xs = listFieldWith key c (const xs)   --------------------------------------------------------------------------------+-- | Creates a list field like 'listField', but supplies the current page+-- to the compiler. listFieldWith     :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b listFieldWith key c f = field' key $ fmap (ListField c) . f   ---------------------------------------------------------------------------------functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a+-- | Creates a variadic function field.+--+-- The function will be called with the dynamically evaluated string arguments+-- from the template as well as the page that is currently rendered.+functionField :: String                                  -- ^ Key+              -> ([String] -> Item a -> Compiler String) -- ^ Function+              -> Context a functionField name value = Context $ \k args i ->     if k == name         then StringField <$> value args i-        else empty+        else noResult $ "Tried function field " ++ name   --------------------------------------------------------------------------------+-- | Transform the respective string results of all fields in a context.+-- For example,+--+-- > mapContext (++"c") (constField "x" "a" <> constField "y" "b")+--+-- is equivalent to+--+-- > constField "x" "ac" <> constField "y" "bc"+-- mapContext :: (String -> String) -> Context a -> Context a mapContext f (Context c) = Context $ \k a i -> do     fld <- c k a i     case fld of+        EmptyField      -> wrongType "boolField"         StringField str -> return $ StringField (f str)-        ListField _ _   -> fail $-            "Hakyll.Web.Template.Context.mapContext: " ++-            "can't map over a ListField!"+        _               -> wrongType "ListField"+  where+    wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " +++        "can't map over a " ++ typ ++ "!"  -------------------------------------------------------------------------------- -- | A context that allows snippet inclusion. In processed file, use as:@@ -163,15 +225,15 @@ -- > $snippet("path/to/snippet/")$ -- > ... ----- The contents of the included file will not be interpolated.+-- The contents of the included file will not be interpolated like @partial@+-- does it. -- snippetField :: Context String snippetField = functionField "snippet" f   where     f [contentsPath] _ = loadBody (fromFilePath contentsPath)-    f _              i = error $-        "Too many arguments to function 'snippet()' in item " ++-            show (itemIdentifier i)+    f []             _ = fail "No argument to function 'snippet()'"+    f _              _ = fail "Too many arguments to function 'snippet()'"  -------------------------------------------------------------------------------- -- | A context that contains (in that order)@@ -191,8 +253,7 @@     metadataField            `mappend`     urlField      "url"      `mappend`     pathField     "path"     `mappend`-    titleField    "title"    `mappend`-    missingField+    titleField    "title"   --------------------------------------------------------------------------------@@ -210,15 +271,20 @@ -- | Map any field to its metadata value, if present metadataField :: Context a metadataField = Context $ \k _ i -> do-    value <- getMetadataField (itemIdentifier i) k-    maybe empty (return . StringField) value+    let id = itemIdentifier i+        empty' = noResult $ "No '" ++ k ++ "' field in metadata " +++            "of item " ++ show id+    value <- getMetadataField id k+    maybe empty' (return . StringField) value   -------------------------------------------------------------------------------- -- | Absolute url to the resulting item urlField :: String -> Context a-urlField key = field key $-    fmap (maybe empty toUrl) . getRoute . itemIdentifier+urlField key = field key $ \i -> do+    let id = itemIdentifier i+        empty' = fail $ "No route url found for item " ++ show id+    fmap (maybe empty' toUrl) $ getRoute id   --------------------------------------------------------------------------------@@ -269,6 +335,12 @@ -- that begins with @yyyy-mm-dd@ . For example: -- @folder//yyyy-mm-dd-title//dist//main.extension@ . -- In case of multiple matches, the rightmost one is used.+--+-- As another alternative, if none of the above matches, and the file has a+-- path which contains nested directories specifying a date, then that date+-- will be used. In other words, if the path is of the form+-- @**//yyyy//mm//dd//**//main.extension@ .+-- As above, in case of multiple matches, the rightmost one is used.  dateField :: String     -- ^ Key in which the rendered date should be placed           -> String     -- ^ Format to use on the date@@ -279,7 +351,7 @@ -------------------------------------------------------------------------------- -- | This is an extended version of 'dateField' that allows you to -- specify a time locale that is used for outputting the date. For more--- details, see 'dateField'.+-- details, see 'dateField' and 'formatTime'. dateFieldWith :: TimeLocale  -- ^ Output time locale               -> String      -- ^ Destination key               -> String      -- ^ Format to use on the date@@ -305,7 +377,8 @@     maybe empty' return $ msum $         [tryField "published" fmt | fmt <- formats] ++         [tryField "date"      fmt | fmt <- formats] ++-        [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths]+        [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths] +++        [parseTime' "%Y-%m-%d" $ intercalate "-" $ fnCand | fnCand <- map (take 3) $ reverse . tails $ paths]   where     empty'     = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++         "could not parse time for " ++ show id'@@ -333,6 +406,7 @@   --------------------------------------------------------------------------------+-- | Creates a field with the last modification date of the underlying item. modificationTimeField :: String     -- ^ Key                       -> String     -- ^ Format                       -> Context  a -- ^ Resulting context@@ -340,6 +414,8 @@   --------------------------------------------------------------------------------+-- | Creates a field with the last modification date of the underlying item+-- in a custom localisation format (see 'formatTime'). modificationTimeFieldWith :: TimeLocale  -- ^ Time output locale                           -> String      -- ^ Key                           -> String      -- ^ Format@@ -378,10 +454,11 @@   --------------------------------------------------------------------------------+-- | Constantly reports any field as missing. Mostly for internal usage,+-- it is the last choice in every context used in a template application. missingField :: Context a-missingField = Context $ \k _ i -> fail $-    "Missing field $" ++ k ++ "$ in context for item " ++-    show (itemIdentifier i)+missingField = Context $ \k _ _ -> noResult $+    "Missing field '" ++ k ++ "' in context"  parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime #if MIN_VERSION_time(1,5,0)
lib/Hakyll/Web/Template/Internal.hs view
@@ -1,15 +1,18 @@+{-# LANGUAGE DeriveAnyClass             #-}+{-# LANGUAGE DeriveGeneric              #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables        #-}+ module Hakyll.Web.Template.Internal     ( Template (..)     , template     , templateBodyCompiler     , templateCompiler     , applyTemplate-    , applyTemplate'     , loadAndApplyTemplate     , applyAsTemplate     , readTemplate+    , compileTemplateItem     , unsafeReadTemplateFile      , module Hakyll.Web.Template.Internal.Element@@ -18,16 +21,18 @@   ---------------------------------------------------------------------------------import           Control.Monad.Except                 (MonadError (..))+import           Control.Monad.Except                 (catchError) import           Data.Binary                          (Binary) import           Data.List                            (intercalate)+import qualified Data.List.NonEmpty                   as NonEmpty import           Data.Typeable                        (Typeable) import           GHC.Exts                             (IsString (..))-import           Prelude                              hiding (id)+import           GHC.Generics                         (Generic)   -------------------------------------------------------------------------------- import           Hakyll.Core.Compiler+import           Hakyll.Core.Compiler.Internal import           Hakyll.Core.Identifier import           Hakyll.Core.Item import           Hakyll.Core.Writable@@ -38,9 +43,10 @@  -------------------------------------------------------------------------------- -- | Datatype used for template substitutions.-newtype Template = Template-    { unTemplate :: [TemplateElement]-    } deriving (Show, Eq, Binary, Typeable)+data Template = Template+    { tplElements :: [TemplateElement]+    , tplOrigin   :: FilePath  -- Only for error messages.+    } deriving (Show, Eq, Generic, Binary, Typeable)   --------------------------------------------------------------------------------@@ -56,41 +62,70 @@  -------------------------------------------------------------------------------- -- | Wrap the constructor to ensure trim is called.-template :: [TemplateElement] -> Template-template = Template . trim+template :: FilePath -> [TemplateElement] -> Template+template p = flip Template p . trim   --------------------------------------------------------------------------------+-- | Parse a string into a template.+-- You should prefer 'compileTemplateItem' over this. readTemplate :: String -> Template-readTemplate = Template . trim . readTemplateElems+readTemplate = either error (template origin) . parseTemplateElemsFile origin+  where+    origin = "{literal}"+{-# DEPRECATED readTemplate "Use templateCompiler instead" #-}  --------------------------------------------------------------------------------+-- | Parse an item body into a template.+-- Provides useful error messages in the 'Compiler' monad.+compileTemplateItem :: Item String -> Compiler Template+compileTemplateItem item = let file = itemIdentifier item+                           in compileTemplateFile file (itemBody item)++--------------------------------------------------------------------------------+compileTemplateFile :: Identifier -> String -> Compiler Template+compileTemplateFile file = either fail (return . template origin)+                         . parseTemplateElemsFile origin+  where+    origin = show file++-------------------------------------------------------------------------------- -- | Read a template, without metadata header templateBodyCompiler :: Compiler (Item Template) templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do     item <- getResourceBody-    file <- getResourceFilePath-    return $ fmap (template . readTemplateElemsFile file) item+    file <- getUnderlying+    withItemBody (compileTemplateFile file) item  -------------------------------------------------------------------------------- -- | Read complete file contents as a template templateCompiler :: Compiler (Item Template) templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do     item <- getResourceString-    file <- getResourceFilePath-    return $ fmap (template . readTemplateElemsFile file) item+    file <- getUnderlying+    withItemBody (compileTemplateFile file) item   --------------------------------------------------------------------------------+-- | Interpolate template expressions from context values in a page applyTemplate :: Template                -- ^ Template               -> Context a               -- ^ Context               -> Item a                  -- ^ Page               -> Compiler (Item String)  -- ^ Resulting item applyTemplate tpl context item = do-    body <- applyTemplate' (unTemplate tpl) context item+    body <- applyTemplate' (tplElements tpl) context item `catchError` handler     return $ itemSetBody body item+  where+    tplName = tplOrigin tpl+    itemName = show $ itemIdentifier item+    handler es = fail $ "Hakyll.Web.Template.applyTemplate: Failed to " +++        (if tplName == itemName+          then "interpolate template in item " ++ itemName+          else "apply template " ++ tplName ++ " to item " ++ itemName) +++        ":\n" ++ intercalate ",\n" es  + -------------------------------------------------------------------------------- applyTemplate'     :: forall a.@@ -105,9 +140,6 @@      go = fmap concat . mapM applyElem -    trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++-        "fully trimmed."-     ---------------------------------------------------------------------------      applyElem :: TemplateElement -> Compiler String@@ -118,29 +150,43 @@      applyElem (Chunk c) = return c -    applyElem (Expr e) = applyExpr e >>= getString e+    applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)+      where+        evalMsg = "In expr '$" ++ show e ++ "$'"+        typeMsg = "expr '$" ++ show e ++ "$'"      applyElem Escaped = return "$" -    applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler+    applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle       where-        handler _ = case mf of-            Nothing -> return ""-            Just f  -> go f+        f = maybe (return "") go mf+        handle (Right _)                      = go t+        handle (Left (CompilationNoResult _)) = f+        handle (Left (CompilationFailure es)) = debug (NonEmpty.toList es) >> f+        debug = compilerDebugEntries ("Hakyll.Web.Template.applyTemplate: " +++            "[ERROR] in 'if' condition on expr '" ++ show e ++ "':") -    applyElem (For e b s) = applyExpr e >>= \cf -> case cf of-        StringField _  -> fail $-            "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++-            "got StringField for expr " ++ show e-        ListField c xs -> do+    applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of+        EmptyField     -> expected "list" "boolean" typeMsg+        StringField _  -> expected "list" "string" typeMsg+        ListField c xs -> withErrorMessage bodyMsg $ do             sep <- maybe (return "") go s             bs  <- mapM (applyTemplate' b c) xs             return $ intercalate sep bs+      where+        headMsg = "In expr '$for(" ++ show e ++ ")$'"+        typeMsg = "loop expr '" ++ show e ++ "'"+        bodyMsg = "In loop context of '$for(" ++ show e ++ ")$'" -    applyElem (Partial e) = do-        p             <- applyExpr e >>= getString e-        Template tpl' <- loadBody (fromFilePath p)-        applyTemplate' tpl' context x+    applyElem (Partial e) = withErrorMessage headMsg $+        applyStringExpr typeMsg e >>= \p ->+        withErrorMessage inclMsg $ do+            tpl' <- loadBody (fromFilePath p)+            itemBody <$> applyTemplate tpl' context x+      where+        headMsg = "In expr '$partial(" ++ show e ++ ")$'"+        typeMsg = "partial expr '" ++ show e ++ "'"+        inclMsg = "In inclusion of '$partial(" ++ show e ++ ")$'"      --------------------------------------------------------------------------- @@ -149,19 +195,31 @@     applyExpr (Ident (TemplateKey k)) = context' k [] x      applyExpr (Call (TemplateKey k) args) = do-        args' <- mapM (\e -> applyExpr e >>= getString e) args+        args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args         context' k args' x+      where+        typeMsg e = "argument '" ++ show e ++ "'"      applyExpr (StringLiteral s) = return (StringField s)      ---------------------------------------------------------------------------- -    getString _ (StringField s) = return s-    getString e (ListField _ _) = fail $-        "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++-        "got ListField for expr " ++ show e+    applyStringExpr :: String -> TemplateExpr -> Compiler String+    applyStringExpr msg expr =+        applyExpr expr >>= getString+      where+        getString EmptyField      = expected "string" "boolean" msg+        getString (StringField s) = return s+        getString (ListField _ _) = expected "string" "list" msg +    expected typ act expr = fail $ unwords ["Hakyll.Web.Template.applyTemplate:",+        "expected", typ, "but got", act, "for", expr] +    -- expected to never happen with all templates constructed by 'template'+    trimError = fail $+        "Hakyll.Web.Template.applyTemplate: template not fully trimmed."++ -------------------------------------------------------------------------------- -- | The following pattern is so common: --@@ -189,14 +247,14 @@ applyAsTemplate :: Context String          -- ^ Context                 -> Item String             -- ^ Item and template                 -> Compiler (Item String)  -- ^ Resulting item-applyAsTemplate context item =-    let tpl = template $ readTemplateElemsFile file (itemBody item)-        file = toFilePath $ itemIdentifier item-    in applyTemplate tpl context item+applyAsTemplate context item = do+    tpl <- compileTemplateItem item+    applyTemplate tpl context item   -------------------------------------------------------------------------------- unsafeReadTemplateFile :: FilePath -> Compiler Template unsafeReadTemplateFile file = do     tpl <- unsafeCompiler $ readFile file-    pure $ template $ readTemplateElemsFile file tpl+    compileTemplateFile (fromFilePath file) tpl+{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
lib/Hakyll/Web/Template/Internal/Element.hs view
@@ -7,14 +7,14 @@     , TemplateExpr (..)     , TemplateElement (..)     , templateElems-    , readTemplateElems-    , readTemplateElemsFile+    , parseTemplateElemsFile     ) where   ---------------------------------------------------------------------------------import           Control.Applicative     ((<|>))+import           Control.Applicative     ((<|>), (<*)) import           Control.Monad           (void)+import           Control.Arrow           (left) import           Data.Binary             (Binary, get, getWord8, put, putWord8) import           Data.List               (intercalate) import           Data.Maybe              (isJust)@@ -107,17 +107,10 @@         2 -> StringLiteral <$> get         _ -> error "Hakyll.Web.Template.Internal: Error reading cached template" - ---------------------------------------------------------------------------------readTemplateElems :: String -> [TemplateElement]-readTemplateElems = readTemplateElemsFile "{literal}"------------------------------------------------------------------------------------readTemplateElemsFile :: FilePath -> String -> [TemplateElement]-readTemplateElemsFile file input = case P.parse templateElems file input of-    Left err -> error $ "Cannot parse template: " ++ show err-    Right t  -> t+parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]+parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)+                            . P.parse (templateElems <* P.eof) file   --------------------------------------------------------------------------------@@ -167,7 +160,7 @@ -------------------------------------------------------------------------------- trimClose :: P.Parser Bool trimClose = do-    trimIfR <- P.optionMaybe $ P.try (P.char '-')+    trimIfR <- P.optionMaybe $ (P.char '-')     void $ P.char '$'     pure $ isJust trimIfR 
tests/Hakyll/Core/UnixFilter/Tests.hs view
@@ -6,18 +6,16 @@   ---------------------------------------------------------------------------------import           Data.List                     (isInfixOf)-import           Test.Tasty                    (TestTree, testGroup)-import           Test.Tasty.HUnit              (testCase)-import qualified Test.Tasty.HUnit              as H+import           Test.Tasty             (TestTree, testGroup)+import           Test.Tasty.HUnit       (testCase)+import qualified Test.Tasty.HUnit       as H   -------------------------------------------------------------------------------- import           Hakyll.Core.Compiler-import           Hakyll.Core.Compiler.Internal+import           Hakyll.Core.Identifier import           Hakyll.Core.Item import           Hakyll.Core.UnixFilter-import           Hakyll.Core.Identifier import           TestSuite.Util  @@ -51,10 +49,7 @@ unixFilterFalse = do     store    <- newTestStore     provider <- newTestProvider store-    result   <- testCompiler store provider testMarkdown compiler-    case result of-        CompilerError es -> True H.@=? any ("exit code" `isInfixOf`) es-        _                -> H.assertFailure "Expecting CompilerError"+    testCompilerError store provider testMarkdown compiler "exit code"     cleanTestEnv   where     compiler = getResourceString >>= withItemBody (unixFilter "false" [])@@ -65,12 +60,7 @@ unixFilterError = do     store    <- newTestStore     provider <- newTestProvider store-    result   <- testCompiler store provider testMarkdown compiler-    case result of-        CompilerError es -> True H.@=? any containsIncorrectOptionMessage es-        _                -> H.assertFailure "Expecting CompilerError"+    testCompilerError store provider testMarkdown compiler "option"     cleanTestEnv   where     compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])-    incorrectOptionMessages = ["invalid option", "illegal option"]-    containsIncorrectOptionMessage output = any (`isInfixOf` output) incorrectOptionMessages
+ tests/Hakyll/Web/Tags/Tests.hs view
@@ -0,0 +1,42 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+module Hakyll.Web.Tags.Tests+    ( tests+    ) where++--------------------------------------------------------------------------------+import           Test.Tasty                  (TestTree, testGroup)+import           Test.Tasty.HUnit            (Assertion, testCase, (@?=))++--------------------------------------------------------------------------------+import           Hakyll.Core.Identifier+import           Hakyll.Core.Provider+import           Hakyll.Core.Store           (Store)+import           Hakyll.Web.Tags+import           TestSuite.Util++tests :: TestTree+tests = testGroup "Hakyll.Web.Tags"+    [ testCase "testGetCategory" testGetCategory+    ]++testGetCategory :: Assertion+testGetCategory = do+    store    <- newTestStore+    provider <- newTestProvider store++    noCategory <- testCategoryDone store provider "example.md"+    noCategory @?= [""]++    oneCategory1 <- testCategoryDone store provider "posts/2010-08-26-birthday.md"+    oneCategory1 @?= ["posts"]++    oneCategory2 <- testCategoryDone store provider "posts/2019/05/10/tomorrow.md"+    oneCategory2 @?= ["10"]++    cleanTestEnv++--------------------------------------------------------------------------------+testCategoryDone :: Store -> Provider -> Identifier -> IO [String]+testCategoryDone store provider identifier =+    testCompilerDone store provider identifier $ getCategory identifier
tests/Hakyll/Web/Template/Context/Tests.hs view
@@ -45,6 +45,11 @@         "posts/2018-09-26.md" "date" $             dateField "date" "%B %e, %Y"     date3 @=? "September 26, 2018"++    date4 <- testContextDone store provider+        "posts/2019/05/10/tomorrow.md" "date" $+            dateField "date" "%B %e, %Y"+    date4 @=? "May 10, 2019"     cleanTestEnv  @@ -57,6 +62,6 @@         cf   <- unContext context key [] item         case cf of             StringField str -> return str-            ListField _ _   -> error $+            _               -> error $                 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++-                "Didn't expect ListField"+                "expected StringField"
tests/Hakyll/Web/Template/Tests.hs view
@@ -1,5 +1,6 @@ -------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell   #-} module Hakyll.Web.Template.Tests     ( tests     ) where@@ -7,9 +8,10 @@  -------------------------------------------------------------------------------- import           Test.Tasty                   (TestTree, testGroup)-import           Test.Tasty.HUnit             (Assertion, testCase, (@=?),-                                               (@?=))+import           Test.Tasty.HUnit             (Assertion, assertBool, testCase,+                                               (@=?), (@?=)) +import           Data.Either                  (isLeft)  -------------------------------------------------------------------------------- import           Hakyll.Core.Compiler@@ -32,13 +34,13 @@       , testCase "applyJoinTemplateList" testApplyJoinTemplateList       ] -    , fromAssertions "readTemplate"-        [ [Chunk "Hello ", Expr (Call "guest" [])]-            @=? readTemplateElems "Hello $guest()$"-        , [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]-            @=? readTemplateElems "$if(a(\"bar\"))$foo$endif$"+    , fromAssertions "parseTemplate"+        [ Right [Chunk "Hello ", Expr (Call "guest" [])]+            @=? parse "Hello $guest()$"+        , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]+            @=? parse "$if(a(\"bar\"))$foo$endif$"         -- 'If' trim check.-        , [ TrimL+        , Right [ TrimL           , If (Ident (TemplateKey "body"))                [ TrimR                , Chunk "\n"@@ -54,29 +56,39 @@                      ])           , TrimR           ]-          @=? readTemplateElems "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"+          @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"         -- 'For' trim check.-        , [ TrimL+        , Right [ TrimL           , For (Ident (TemplateKey "authors"))                 [TrimR, Chunk "\n   body   \n", TrimL]                 Nothing           , TrimR           ]-          @=? readTemplateElems "$-for(authors)-$\n   body   \n$-endfor-$"+          @=? parse "$-for(authors)-$\n   body   \n$-endfor-$"         -- 'Partial' trim check.-        , [ TrimL+        , Right [ TrimL           , Partial (StringLiteral "path")           , TrimR           ]-          @=? readTemplateElems "$-partial(\"path\")-$"+          @=? parse "$-partial(\"path\")-$"         -- 'Expr' trim check.-        , [ TrimL+        , Right [ TrimL           , Expr (Ident (TemplateKey "foo"))           , TrimR           ]-          @=? readTemplateElems "$-foo-$"+          @=? parse "$-foo-$"+        -- fail on incomplete template.+        , assertBool "did not yield error" $ isLeft $+          parse "a$b"+        -- fail on mismatched template syntax.+        , assertBool "did not fail to parse" $ isLeft $+          parse "$for(xs)$\n  <p>foo</p>\n$endif$"         ]++    , [testCase "embeddedTemplate" testEmbeddedTemplate]     ]+  where+    parse = parseTemplateElemsFile ""   --------------------------------------------------------------------------------@@ -113,6 +125,8 @@ testApplyJoinTemplateList = do     store    <- newTestStore     provider <- newTestProvider store+    tpl      <- testCompilerDone store provider "tpl" $+        compileTemplateItem (Item "tpl" "<b>$body$</b>")     str      <- testCompilerDone store provider "item3" $         applyJoinTemplateList ", " tpl defaultContext [i1, i2] @@ -121,4 +135,22 @@   where     i1  = Item "item1" "Hello"     i2  = Item "item2" "World"-    tpl = readTemplate "<b>$body$</b>"+++--------------------------------------------------------------------------------+embeddedTemplate :: Template+embeddedTemplate = $(embedTemplate "tests/data/embed.html")++--------------------------------------------------------------------------------+testEmbeddedTemplate :: Assertion+testEmbeddedTemplate = do+    store    <- newTestStore+    provider <- newTestProvider store+    str      <- testCompilerDone store provider "item3" $+        applyTemplate embeddedTemplate defaultContext item++    itemBody str @?= "<p>Hello, world</p>\n"+    cleanTestEnv+  where+    item = Item "item1" "Hello, world"+
tests/TestSuite.hs view
@@ -28,6 +28,7 @@ #endif import qualified Hakyll.Web.Template.Context.Tests import qualified Hakyll.Web.Template.Tests+import qualified Hakyll.Web.Tags.Tests   --------------------------------------------------------------------------------@@ -49,6 +50,7 @@ #ifdef USE_PANDOC     , Hakyll.Web.Pandoc.FileType.Tests.tests #endif+    , Hakyll.Web.Tags.Tests.tests     , Hakyll.Web.Template.Context.Tests.tests     , Hakyll.Web.Template.Tests.tests     ]
tests/TestSuite/Util.hs view
@@ -6,6 +6,7 @@     , newTestProvider     , testCompiler     , testCompilerDone+    , testCompilerError     , testConfiguration     , cleanTestEnv     , renderParagraphs@@ -13,7 +14,7 @@   ---------------------------------------------------------------------------------import           Data.List                     (intercalate)+import           Data.List                     (intercalate, isInfixOf) import           Data.Monoid                   (mempty) import qualified Data.Set                      as S import           Test.Tasty@@ -80,13 +81,21 @@         CompilerDone x _    -> return x         CompilerError e     -> fail $             "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++-            " threw: " ++ intercalate "; " e+            " threw: " ++ intercalate "; " (compilerErrorMessages e)         CompilerRequire i _ -> fail $             "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++             " requires: " ++ show i         CompilerSnapshot _ _ -> fail             "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot" +testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()+testCompilerError store provider underlying compiler expectedMessage = do+    result   <- testCompiler store provider underlying compiler+    case result of+        CompilerError e ->+            any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?+           "Expecting '" ++ expectedMessage ++ "' error"+        _               -> assertFailure "Expecting CompilerError"  -------------------------------------------------------------------------------- testConfiguration :: Configuration