packages feed

hakyll 4.15.1.1 → 4.16.0.0

raw patch · 39 files changed

+702/−287 lines, 39 filesdep ~aesondep ~basedep ~mtlPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, base, mtl, optparse-applicative, pandoc, template-haskell, time

API changes (from Hackage documentation)

+ Hakyll.Core.Configuration: [checkHtmlFile] :: Configuration -> FilePath -> Bool
+ Hakyll.Core.Configuration: [previewSettings] :: Configuration -> FilePath -> StaticSettings
+ Hakyll.Main: defaultCommands :: Configuration -> [(String, Parser Command, InfoMod a)]
+ Hakyll.Web.Html: withTagListM :: Monad m => ([Tag String] -> m [Tag String]) -> String -> m String
+ Hakyll.Web.Pandoc.Biblio: pandocBibliosCompiler :: String -> String -> Compiler (Item String)
+ Hakyll.Web.Tags: simpleRenderLink :: String -> Maybe FilePath -> Maybe Html
- Hakyll.Core.Configuration: Configuration :: FilePath -> FilePath -> FilePath -> FilePath -> (FilePath -> Bool) -> (FilePath -> Bool) -> String -> (Configuration -> IO ExitCode) -> Bool -> String -> Int -> Configuration
+ Hakyll.Core.Configuration: Configuration :: FilePath -> FilePath -> FilePath -> FilePath -> (FilePath -> Bool) -> (FilePath -> Bool) -> (FilePath -> Bool) -> String -> (Configuration -> IO ExitCode) -> Bool -> String -> Int -> (FilePath -> StaticSettings) -> Configuration
- Hakyll.Core.Store: get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
+ Hakyll.Core.Store: get :: forall a. (Binary a, Typeable a) => Store -> [String] -> IO (Result a)

Files

CHANGELOG.md view
@@ -4,6 +4,59 @@  # Releases +## Hakyll 4.16.0.0 (2023-04-27)++- Bump `base` *lower* bound to 4.12 (GHC >= 8.6). Hakyll already failed to build+    on earlier versions due to the template-haskell requirement, and nobody+    complained about that, so I assume nobody cares if the support is properly+    dropped (contribution by Alexander Batischev)+- Export `Hakyll.Tags.simpleRenderLink` (contribution by Alexander Batischev)+- Add `Hakyll.Web.Pandoc.Biblio.pandocBibliosCompiler` to load multiple bib+    files by glob (contribution by Liang-Ting Chen)+- Fix "Store.set: resource busy" error (contribution by Jasper Van der Jeugt)+- Teach `Hakyll.Web.Template.Context.getItemUTC` about another date format,+    "%d.%m.%Y" (contribution by dukzcry)+- Add `Hakyll.Web.Html.withTagListM`, a monadic version of `withTagList`+    (contribution by 0xd34df00d)+- Fix all the warnings and enable `-Werror` in CI (contribution by Alexander+    Batischev)+- Miscellaneous updates and fixes to the docs (contributions by Tony Zorman,+    Alexander Batischev, malteneuss, Agustín Mista, Martin Bukatovič, Muhammad+    Aviv Burhanudin, Jacek Galowicz, Daniel Mlot, Yoo Chung, Robert Pearce)+- Export `Hakyll.defaultCommands`, i.e. Hakyll's set of commands (`build`,+    `check`, `clean` etc.) (contribution by Alexander Batischev)+- Add a `Hakyll.Core.Configuration.Configuration.previewSettings` field which+    lets the user override the settings used by the preview server (contribution+    by Christopher League and Brian McKenna)+- Make email address in RSS/Atom feeds optional (just set it to an empty string)+    (contribution by Robert)+- `Hakyll.Web.Meta.TwitterCard`: use `name` instead of `property` for better+    spec compliance (contribution by ncaq)+- Add a `Hakyll.Core.Configuration.Configuration.checkHtmlFile` predicate which+    dictates what files will get link-checked by the `check` command. Default+    predicate accepts files with .html and .xhtml extensions (contribution by+    Yoo Chung, with earlier contribution from Michael Orlitzky)+- Add support for GHC 9.2 (contribution by Laurent P. René de Cotret)+- Bump `optparse-applicative` upper bound to allow 0.17 (contribution by+    Alexander Batischev)+- Bump `pandoc` upper bound to allow 2.19 (contribution by Alexander Batischev)+- Allow `text` 2.0 (contribution by Alexander Batischev)+- Bump `vector` upper bound to allow 0.13 (contribution by Alexander Batischev)+- Allow `aeson` 2.1 (contribution by Alexander Batischev)+- Bump `fsnotify` upper bound to allow 0.4 (contribution by Alexander Batischev)+- Bump `resourcet` upper bound to allow 1.3 (contribution by Alexander+    Batischev)+- Bump `template-haskell` upper bound to 2.20 (GHC 9.4.3) (contribution by+    Alexander Batischev)+- Allow `pandoc` 3.0. Note that the behavior of Hakyll's `readPandocBiblios` and+    `readPandocBiblio` is different whether pandoc 2 or 3 is installed+    (contribution by Laurent P. René de Cotret) +- Bump `mtl` upper bound to allow 2.3 (contribution by Alexander Batischev)+- Bump `pandoc` upper bound to allow 3.1 (contribution by Laurent P. René de+    Cotret)+- Bump `template-haskell` upper bound to 2.21 and `time` to 1.12 (GHC 9.6.1)+    (contribution by Laurent P. René de Cotret)+ ## Hakyll 4.15.1.1 (2022-01-20)  - Extend the documentation for `Hakyll.Core.Identifier` (contribution by
data/templates/atom.xml view
@@ -6,7 +6,9 @@     <id>$root$$url$</id>     <author>         <name>$authorName$</name>+        $if(authorEmail)$         <email>$authorEmail$</email>+        $endif$     </author>     <updated>$updated$</updated>     $body$
hakyll.cabal view
@@ -1,5 +1,5 @@ Name:    hakyll-Version: 4.15.1.1+Version: 4.16.0.0  Synopsis: A static website compiler library Description:@@ -60,8 +60,10 @@ Extra-source-files:   CHANGELOG.md   tests/data/biblio/chicago.csl-  tests/data/biblio/cites-meijer.golden-  tests/data/biblio/cites-multiple.golden+  tests/data/biblio/cites-meijer-pandoc2.golden+  tests/data/biblio/cites-meijer-pandoc3.golden+  tests/data/biblio/cites-multiple-pandoc2.golden+  tests/data/biblio/cites-multiple-pandoc3.golden   tests/data/biblio/cites-multiple.markdown   tests/data/biblio/default.html   tests/data/biblio/page.markdown@@ -177,8 +179,8 @@     Paths_hakyll    Build-Depends:-    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.1,-    base                 >= 4.8      && < 5,+    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.2,+    base                 >= 4.12     && < 5,     binary               >= 0.5      && < 0.10,     blaze-html           >= 0.5      && < 0.10,     blaze-markup         >= 0.5.1    && < 0.9,@@ -192,31 +194,31 @@     hashable             >= 1.0      && < 2,     lifted-async         >= 0.10     && < 1,     lrucache             >= 1.1.1    && < 1.3,-    mtl                  >= 1        && < 2.3,+    mtl                  >= 1        && < 2.4,     network-uri          >= 2.6      && < 2.7,-    optparse-applicative >= 0.12     && < 0.17,+    optparse-applicative >= 0.12     && < 0.18,     parsec               >= 3.0      && < 3.2,     process              >= 1.6      && < 1.7,     random               >= 1.0      && < 1.3,     regex-tdfa           >= 1.1      && < 1.4,-    resourcet            >= 1.1      && < 1.3,+    resourcet            >= 1.1      && < 1.4,     scientific           >= 0.3.4    && < 0.4,     tagsoup              >= 0.13.1   && < 0.15,-    template-haskell     >= 2.14     && < 2.18,-    text                 >= 0.11     && < 1.3,-    time                 >= 1.8      && < 1.12,+    template-haskell     >= 2.14     && < 2.21,+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,+    time                 >= 1.8      && < 1.13,     time-locale-compat   >= 0.1      && < 0.2,     unordered-containers >= 0.2      && < 0.3,-    vector               >= 0.11     && < 0.13,+    vector               >= 0.11     && < 0.14,+    wai-app-static       >= 3.1      && < 3.2,     yaml                 >= 0.8.11   && < 0.12    If flag(previewServer)     Build-depends:       wai             >= 3.2   && < 3.3,       warp            >= 3.2   && < 3.4,-      wai-app-static  >= 3.1   && < 3.2,       http-types      >= 0.9   && < 0.13,-      fsnotify        >= 0.2   && < 0.4+      fsnotify        >= 0.2   && < 0.5     Cpp-options:       -DPREVIEW_SERVER     Other-modules:@@ -225,7 +227,7 @@    If flag(watchServer)     Build-depends:-      fsnotify        >= 0.2   && < 0.4+      fsnotify        >= 0.2   && < 0.5     Cpp-options:       -DWATCH_SERVER     Other-modules:@@ -246,7 +248,7 @@     Other-Modules:       Hakyll.Web.Pandoc.Binary     Build-Depends:-      pandoc >= 2.11 && < 2.18+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2     Cpp-options:       -DUSE_PANDOC @@ -284,13 +286,13 @@     tasty-hunit                >= 0.9  && < 0.11,     tasty-quickcheck           >= 0.8  && < 0.11,     -- Copy pasted from hakyll dependencies:-    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.1,-    base                 >= 4.8      && < 5,+    aeson                >= 1.0      && < 1.6 || >= 2.0 && < 2.2,+    base                 >= 4.12     && < 5,     bytestring           >= 0.9      && < 0.12,     containers           >= 0.3      && < 0.7,     filepath             >= 1.0      && < 1.5,     tagsoup              >= 0.13.1   && < 0.15,-    text                 >= 0.11     && < 1.3,+    text                 >= 0.11     && < 1.3 || >= 2.0 && < 2.1,     unordered-containers >= 0.2      && < 0.3,     yaml                 >= 0.8.11   && < 0.12 @@ -312,7 +314,10 @@       Hakyll.Web.Pandoc.FileType.Tests     Cpp-options:       -DUSE_PANDOC+    Build-Depends:+      pandoc >= 2.11 && < 2.20 || >= 3.0 && < 3.2 + Executable hakyll-init   Main-is:          Init.hs   Ghc-options:      -Wall -threaded@@ -324,9 +329,9 @@    Build-depends:     hakyll,-    base      >= 4   && < 5,-    directory >= 1.0 && < 1.4,-    filepath  >= 1.0 && < 1.5+    base      >= 4.12 && < 5,+    directory >= 1.0  && < 1.4,+    filepath  >= 1.0  && < 1.5  Executable hakyll-website   Main-is:          site.hs@@ -341,7 +346,7 @@    Build-depends:     hakyll,-    base      >= 4     && < 5,+    base      >= 4.12  && < 5,     directory >= 1.0   && < 1.4,     filepath  >= 1.0   && < 1.5,-    pandoc    >= 2.11  && < 2.18+    pandoc    >= 2.11  && < 2.20 || >= 3.0 && < 3.2
lib/Hakyll/Check.hs view
@@ -19,15 +19,11 @@ import           Control.Monad.Trans.Resource (runResourceT) import           Data.List                    (isPrefixOf) import qualified Data.Map.Lazy                as Map-#if MIN_VERSION_base(4,9,0)-import           Data.Semigroup               (Semigroup (..))-#endif import           Network.URI                  (unEscapeString) import           System.Directory             (doesDirectoryExist,                                                doesFileExist) import           System.Exit                  (ExitCode (..))-import           System.FilePath              (takeDirectory, takeExtension,-                                               (</>))+import           System.FilePath              (takeDirectory, (</>)) import qualified Text.HTML.TagSoup            as TS  @@ -88,7 +84,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0) instance Semigroup CheckerWrite where     (<>) (CheckerWrite f1 o1) (CheckerWrite f2 o2) =         CheckerWrite (f1 + f2) (o1 + o2)@@ -96,12 +91,6 @@ instance Monoid CheckerWrite where     mempty  = CheckerWrite 0 0     mappend = (<>)-#else-instance Monoid CheckerWrite where-    mempty                                            = CheckerWrite 0 0-    mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =-        CheckerWrite (f1 + f2) (o1 + o2)-#endif   --------------------------------------------------------------------------------@@ -139,7 +128,7 @@     let htmls =             [ destinationDirectory config </> file             | file <- files-            , takeExtension file == ".html"+            , checkHtmlFile config file             ]      forM_ htmls checkFile
lib/Hakyll/Commands.hs view
@@ -124,8 +124,8 @@ server :: Configuration -> Logger -> String -> Int -> IO () #ifdef PREVIEW_SERVER server conf logger host port = do-    let destination = destinationDirectory conf-    staticServer logger destination host port+    let settings = previewSettings conf $ destinationDirectory conf+    staticServer logger settings host port #else server _ _ _ _ = previewServerDisabled #endif
lib/Hakyll/Core/Compiler/Internal.hs view
@@ -44,9 +44,6 @@ 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 import           Data.Set                       (Set) import qualified Data.Set                       as S @@ -97,7 +94,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0) instance Semigroup CompilerWrite where     (<>) (CompilerWrite d1 h1) (CompilerWrite d2 h2) =         CompilerWrite (d1 ++ d2) (h1 + h2)@@ -105,12 +101,6 @@ instance Monoid CompilerWrite where     mempty  = CompilerWrite [] 0     mappend = (<>)-#else-instance Monoid CompilerWrite where-    mempty = CompilerWrite [] 0-    mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =-        CompilerWrite (d1 ++ d2) (h1 + h2)-#endif   --------------------------------------------------------------------------------@@ -161,7 +151,7 @@  -------------------------------------------------------------------------------- instance Monad Compiler where-    return x = compilerResult $ CompilerDone x mempty+    return = pure     {-# INLINE return #-}      Compiler c >>= f = Compiler $ \r -> do@@ -195,7 +185,7 @@  -------------------------------------------------------------------------------- instance Applicative Compiler where-    pure x = return x+    pure x = compilerResult $ CompilerDone x mempty     {-# INLINE pure #-}      f <*> x = f >>= \f' -> fmap f' x
lib/Hakyll/Core/Configuration.hs view
@@ -11,9 +11,11 @@ -------------------------------------------------------------------------------- import           Data.Default     (Default (..)) import           Data.List        (isPrefixOf, isSuffixOf)+import qualified Network.Wai.Application.Static as Static import           System.Directory (canonicalizePath) import           System.Exit      (ExitCode)-import           System.FilePath  (isAbsolute, normalise, takeFileName, makeRelative)+import           System.FilePath  (isAbsolute, makeRelative, normalise,+                                   takeExtension, takeFileName) import           System.IO.Error  (catchIOError) import           System.Process   (system) @@ -46,6 +48,10 @@       -- want to use the test, you should use 'shouldIgnoreFile'.       --       ignoreFile           :: FilePath -> Bool+    , -- | Function to determine HTML files whose links are to be checked.+      --+      -- In 'defaultConfiguration', files with the @.html@ extension are checked.+      checkHtmlFile        :: FilePath -> Bool     , -- | Function to determine files and directories that should not trigger       -- a rebuild when touched in watch mode.       --@@ -87,6 +93,9 @@       -- One can also override the port as a command line argument:       -- ./site preview -p 1234       previewPort          :: Int+    , -- | Override other settings used by the preview server. Default is+      -- 'Static.defaultFileServerSettings'.+      previewSettings      :: FilePath -> Static.StaticSettings     }  --------------------------------------------------------------------------------@@ -102,12 +111,14 @@     , tmpDirectory         = "_cache/tmp"     , providerDirectory    = "."     , ignoreFile           = ignoreFile'+    , checkHtmlFile        = flip elem [".html", ".xhtml"] . takeExtension     , watchIgnore          = const False     , deployCommand        = "echo 'No deploy command specified' && exit 1"     , deploySite           = system . deployCommand     , inMemoryCache        = True     , previewHost          = "127.0.0.1"     , previewPort          = 8000+    , previewSettings      = Static.defaultFileServerSettings     }   where     ignoreFile' path
lib/Hakyll/Core/Identifier.hs view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- | An identifier is a type used to uniquely name an item. An identifier--- is similar to a file path, but can contain additional details (e.g. +-- is similar to a file path, but can contain additional details (e.g. -- item's version). Examples of identifiers are: -- -- * @posts/foo.markdown@@@ -24,9 +24,7 @@  -------------------------------------------------------------------------------- import           Control.DeepSeq     (NFData (..))-import           Data.List           (intercalate)-import           System.FilePath     (dropTrailingPathSeparator, splitPath,-                                      pathSeparator, normalise)+import           System.FilePath     (normalise)   --------------------------------------------------------------------------------@@ -130,7 +128,7 @@   ---------------------------------------------------------------------------------{- | Parse an identifier from a file path string. For example, +{- | Parse an identifier from a file path string. For example,  @ -- e.g. file on disk: 'posts\/hakyll.md'@@ -154,7 +152,7 @@   ---------------------------------------------------------------------------------{- | Set or override the version of an identifier in order to specify which version of an 'Hakyll.Core.Item.Item' +{- | Set or override the version of an identifier in order to specify which version of an 'Hakyll.Core.Item.Item' to 'Hakyll.Core.Compiler.load' from the 'Hakyll.Core.Store.Store'. For example,  @
lib/Hakyll/Core/Identifier/Pattern/Internal.hs view
@@ -1,6 +1,5 @@ -- | This internal module is mostly here to prevent CPP conflicting with Haskell -- comments.-{-# LANGUAGE CPP #-} module Hakyll.Core.Identifier.Pattern.Internal     ( GlobComponent (..)     , Pattern (..)@@ -13,12 +12,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0)-import           Data.Semigroup         (Semigroup (..))-#endif----------------------------------------------------------------------------------- import           Hakyll.Core.Identifier  @@ -78,15 +71,9 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0) instance Semigroup Pattern where     (<>) = And  instance Monoid Pattern where     mempty  = Everything     mappend = (<>)-#else-instance Monoid Pattern where-    mempty  = Everything-    mappend = And-#endif
lib/Hakyll/Core/Item.hs view
@@ -12,7 +12,6 @@  -------------------------------------------------------------------------------- import           Data.Binary                   (Binary (..))-import           Data.Foldable                 (Foldable (..)) import           Data.Typeable                 (Typeable) import           Prelude                       hiding (foldr) 
lib/Hakyll/Core/Metadata.hs view
@@ -16,7 +16,9 @@  -------------------------------------------------------------------------------- import           Control.Monad                  (forM)+#if !MIN_VERSION_base(4,13,0) import           Control.Monad.Fail             (MonadFail)+#endif import           Data.Binary                    (Binary (..), getWord8,                                                  putWord8, Get) #if MIN_VERSION_aeson(2,0,0)
lib/Hakyll/Core/Provider/Metadata.hs view
@@ -19,7 +19,6 @@ import           Data.List.Extended            (breakWhen) import qualified Data.Map                      as M import           Data.Maybe                    (fromMaybe)-import           Data.Monoid                   ((<>)) import qualified Data.Text                     as T import qualified Data.Text.Encoding            as T import qualified Data.Yaml                     as Yaml
lib/Hakyll/Core/Routes.hs view
@@ -39,7 +39,6 @@  Take a look at the built-in routes here for detailed usage examples. -}-{-# LANGUAGE CPP        #-} {-# LANGUAGE Rank2Types #-} module Hakyll.Core.Routes     ( Routes@@ -57,9 +56,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0)-import           Data.Semigroup                 (Semigroup (..))-#endif import           System.FilePath                (replaceExtension, normalise)  @@ -92,7 +88,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0) instance Semigroup Routes where     (<>) (Routes f) (Routes g) = Routes $ \p id' -> do         (mfp, um) <- f p id'@@ -103,15 +98,6 @@ instance Monoid Routes where     mempty  = Routes $ \_ _ -> return (Nothing, False)     mappend = (<>)-#else-instance Monoid Routes where-    mempty = Routes $ \_ _ -> return (Nothing, False)-    mappend (Routes f) (Routes g) = Routes $ \p id' -> do-        (mfp, um) <- f p id'-        case mfp of-            Nothing -> g p id'-            Just _  -> return (mfp, um)-#endif   --------------------------------------------------------------------------------@@ -247,8 +233,8 @@ source file being processed. See 'Hakyll.Core.Identifier.Identifier' for details. -Hint: The name "gsub" comes from a similar function in -[R](https://www.r-project.org) and can be read as "globally substituting" +Hint: The name "gsub" comes from a similar function in+[R](https://www.r-project.org) and can be read as "globally substituting" (globally in the Unix sense of repeated, not just once).  === __Examples__
lib/Hakyll/Core/Rules.hs view
@@ -1,19 +1,43 @@ ----------------------------------------------------------------------------------- | This module provides a declarative DSL in which the user can specify the--- different rules used to run the compilers.------ The convention is to just list all items in the 'Rules' monad, routes and--- compilation rules.------ A typical usage example would be:---+-- | This module provides a declarative Domain Specific Language (DSL) to+-- generate a static site by specifying transformation 'Rules' (although the+-- use case is not limited to static sites).+-- Each rule normally consists of three parts:+-- +-- 1. Source files (like Markdown files) to process (collected with e.g.+-- 'match' or 'create').+-- 2. Compilation steps (like Markdown to HTML) to transform files' content+-- to some output content (steps are collected within +-- 'Hakyll.Core.Compiler.Compiler' and executed with 'compile').+-- 3. Routing to determine if and where an output content will be written out. +-- For a static site this determines under which URL the output content will +-- be available (configured with 'route' and 'Hakyll.Core.Routes.Routes').+-- +-- A typical usage example looks as follows:+-- +-- > -- write 'match "posts/**.md"' instead of 'match $ fromGlob "posts/**.md"'+-- > {-# LANGUAGE OverloadedStrings #-}    +-- > ...+-- > -- > main = hakyll $ do--- >     match "posts/*" $ do--- >         route   (setExtension "html")--- >         compile someCompiler+-- >+-- >     -- Rule 1+-- >     -- Source files: all Markdown files like 'hakyll.md' in the 'posts' directory+-- >     match "posts/**.md" $ do          +-- >         -- Routing: Only replace extension, so '<destination-directory>/posts/hakyll.html'.+-- >         route $ setExtension "html"   +-- >         -- Compilation step(s): Transform Markdown file content into HTML output.+-- >         compile pandocCompiler        +-- >+-- >     -- Rule 2 -- >     match "css/*" $ do--- >         route   idRoute+-- >         route idRoute -- >         compile compressCssCompiler+-- >     ...+-- __The order of rules doesn't matter.__ +-- +-- See official [Hakyll Rules tutorial](https://jaspervdj.be/hakyll/tutorials/03-rules-routes-compilers.html)+-- for other examples. {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings          #-} module Hakyll.Core.Rules@@ -130,18 +154,122 @@     setMatches ids env = env {rulesMatches = ids}  ---------------------------------------------------------------------------------match :: Pattern -> Rules () -> Rules ()+{- | Add a selection of which source files to process (using the+given [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming\))) to the+given remaining 'Rules' value.++The expanded, relative path of the matched source file on disk (relative to the+project directory configured with 'Hakyll.Core.Configuration.providerDirectory')+becomes the identifier under which the compilation result is saved to the+'Hakyll.Core.Store.Store' (in case you want to 'Hakyll.Core.Compiler.load' it+within another rule).+See 'Hakyll.Core.Identifier.Identifier' for details.++=== __Examples__++__Select all markdown files within a directory (but without subdirectories)__++> -- Match all Markdown files in the immediate 'posts' directory+> -- e.g. '<project-directory>/posts/hakyll.md'+> -- but NOT  '<project-directory>/posts/haskell/monad.md'+> match "posts/*.md" $ do+>     route $ setExtension "html"+>     compile pandocCompiler++__Select all markdown files within a directory (including subdirectories recursively)__++> -- Match all Markdown files in the 'posts' directory and any subdirectory+> -- e.g. '<project-directory>/posts/hakyll.md'+> -- and  '<project-directory>/posts/haskell/monad.md'+> match "posts/**.md" $ do+>     route $ setExtension "html"+>     compile pandocCompiler+See 'Hakyll.Core.Identifier.Pattern.Pattern' or search "glob patterns" online+for more details. To control where the compilation result will be written out,+use routing functions like 'Hakyll.Core.Routes.setExtension'.+-}+match :: Pattern  -- ^ Glob pattern+      -> Rules () -- ^ Remaining processing parts+      -> Rules () -- ^ Result match pattern = matchInternal pattern $ getMatches pattern   ---------------------------------------------------------------------------------matchMetadata :: Pattern -> (Metadata -> Bool) -> Rules () -> Rules ()+{- | Add a selection of which source files to process (using the+given [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming\)) and+metadata predicate) to the given remaining 'Rules' values. Same as 'match' but+allows to filter files further based on their (metadata) content (a file is+added only when the metadata predicate returns @True@).++The expanded, relative path of the matched source file on disk (relative to the+project directory configured with 'Hakyll.Core.Configuration.providerDirectory')+becomes the identifier under which the compilation result is saved to+the 'Hakyll.Core.Store.Store' (in case you want to 'Hakyll.Core.Compiler.load'+it within another rule).+See 'Hakyll.Core.Identifier.Identifier' for details.++=== __Examples__+__Select all markdown files with enabled draft flag within a directory__++> matchMetadata "posts/*.md" (\meta -> maybe False (=="true") $ lookupString "draft" meta) $ do+>     route $ setExtension "html"+>     compile pandocCompiler++For example, the following 'posts/hakyll.md' file with @draft: true@ metadata+would match:++> ---+> draft: true+> title: Hakyll Post+> ...+> ---+> In this blog post we learn about Hakyll ...+Note that files that have @draft: false@ or no such draft field at all, would+not match. You can use helper functions like 'Hakyll.Core.Metadata.lookupString'+to access a specific metadata field, and 'Data.Maybe.maybe' to work with+'Data.Maybe.Maybe'. To control where the compilation result will be written out,+use routing functions like 'Hakyll.Core.Routes.setExtension'.+-}+matchMetadata :: Pattern             -- ^ Glob pattern+              -> (Metadata -> Bool)  -- ^ Metadata predicate+              -> Rules ()            -- ^ Remaining processing parts+              -> Rules ()            -- ^ Result matchMetadata pattern metadataPred = matchInternal pattern $     map fst . filter (metadataPred . snd) <$> getAllMetadata pattern   ---------------------------------------------------------------------------------create :: [Identifier] -> Rules () -> Rules ()+{- | Assign (and thereby create) the given identifier(s) to content that has no+underlying source file on disk. That content must be created within the+'compile' part of the given remaining 'Rules' value. The given identifier is the+id under which the compilation is saved to the 'Hakyll.Core.Store.Store' (in+case you want to 'Hakyll.Core.Compiler.load' it within another rule).+See 'Hakyll.Core.Identifier.Identifier' for details.++Use this function for example to create an overview page that doesn't have or+need its content prepared in a file (unlike blog posts which normally have a+corresponding Markdown source file on disk).++=== __Examples__+__Create a webpage without an underlying source file__++> -- saved with implicit identifier 'index.html' to Store+> create ["index.html"] $ do+>+>     -- compilation result is written to '<destination-directory>/index.html'+>     route idRoute+>+>     -- create content without a source file from disk+>     compile $ makeItem ("<h1>Hello World</h1>" :: String)+Note how you can use 'Hakyll.Core.Compiler.makeItem' to create content inline+(to be processed as a 'Hakyll.Core.Compiler.Compiler' value) as if that content+was loaded from a file (as it's the case when using 'match').+To control where the compilation result will be written out, use routing+functions like 'Hakyll.Core.Routes.idRoute'.+-}+create :: [Identifier] -- ^ Identifiers to assign to created content in next argument+       -> Rules ()     -- ^ Remaining processing parts that must create content+       -> Rules ()     -- ^ Resulting rule create ids rules = do     flush     -- TODO Maybe check if the resources exist and call tellResources on that@@ -151,7 +279,54 @@   ---------------------------------------------------------------------------------version :: String -> Rules () -> Rules ()+{- | Add the given version name to the implicit identifier(s) under which the+compilation result of the given remaining 'Rules' value is saved to the+'Hakyll.Core.Store.Store'.+See 'Hakyll.Core.Identifier.Identifier' for details.++Use this wrapper function for example when you need to compile the same source+file into two or more different results, each with a different version name.+The version is needed to distinguish between these different compilation results+in the store, otherwise they would get the same conflicting identifier in the+store.++Warning:+__If you add a version name with this function, you need to supply the same name__+when you 'Hakyll.Core.Compiler.load' the content from the store from+within another rule.++=== __Examples__+__Compile source file into differently versioned outputs and load both__++> -- e.g. file on disk: 'posts/hakyll.md'+>+> -- saved with implicit identifier ('posts/hakyll.md', no-version)+> match "posts/*" $ do+>     route $ setExtension "html"+>     compile pandocCompiler+>+> -- saved with implicit identifier ('posts/hakyll.md', version 'raw')+> match "posts/*" $ version "raw" $ do+>     route idRoute+>     compile getResourceBody+>+> -- use compilation results from rules above+> create ["index.html"] $ do+>     route idRoute+>     compile $ do+>         -- load no-version version+>         compiledPost <- load (fromFilePath "posts/hakyll.md")+>         -- load version 'raw'+>         rawPost <- load . setVersion (Just "raw") $ fromFilePath "posts/hakyll.md"+>         ...+Note how a version name is needed to distinguish the unversioned and the "raw"+version when loading the Hakyll post for the @index.html@ page.+To control where the compilation result will be written out, use routing+functions like 'Hakyll.Core.Routes.idRoute' and 'Hakyll.Core.Routes.setExtension'.+-}+version :: String   -- ^ Version name to add+        -> Rules () -- ^ Remaining processing parts+        -> Rules () -- ^ Result version v rules = do     flush     Rules $ local setVersion' $ unRules $ rules >> flush@@ -160,19 +335,116 @@   ----------------------------------------------------------------------------------- | Add a compilation rule to the rules.------ This instructs all resources to be compiled using the given compiler.-compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -> Rules ()+{- | Add (or replace) the given compilation steps within the given+'Hakyll.Core.Compiler.Compiler' value to the current 'Rules' value.+__This functions controls HOW the content within a rule is processed__ (use one+of the 'match' functions to control WHAT content is processed).++The compilation result is saved to the 'Hakyll.Core.Store.Store' under an+implicit identifier.+See 'Hakyll.Core.Identifier.Identifier' for details.++If there's routing attached to the rule where this function is used, the+compilation result is also written out to a file according to that route.+See 'route' and 'Hakyll.Core.Routes.Routes' for details.++=== __Examples__+__Compile Markdown to HTML__++> -- Select all Markdown files in 'posts' directory+> match "posts/**.md" $ do+>+>     route $ setExtension "html"+>+>     -- use pandoc to transform Markdown to HTML in a single step+>     compile pandocCompiler+Note how we set the content to be processed with+'Hakyll.Web.Pandoc.pandocCompiler'. The content comes implicitly from the+matched Markdown files on disk. We don't have to pass that content around+manually. Every file is processed the same way within this one rule.++To control where the compilation result will be written out, use routing+functions like 'Hakyll.Core.Routes.setExtension'.+Here the compilation result of a file like @posts\/hakyll.md@ is written out+to @posts\/hakyll.html@.++__Compile Markdown to HTML and embed it in a template__++> -- Select all Markdown files in 'posts' directory+> match "posts/**.md" $ do+>     route $ setExtension "html"+>     compile $+>         pandocCompiler >>=+>           loadAndApplyTemplate "templates/post.html" defaultContext+>+> -- To Hakyll templates are just plain files that have to be processed+> -- and placed into the store like any other file (but without routing).+> -- e.g. file on disk: 'templates/post.html'+> match "templates/*" $ compile templateBodyCompiler+Note how a Markdown post that is compiled to HTML using+'Hakyll.Web.Pandoc.pandocCompiler' in a first step and then embedded into+a HTMl 'Hakyll.Web.Template.Template' in a second step by using+'Hakyll.Web.Template.loadAndApplyTemplate'.+We can use templates to control the design and layout of a webpage.+A template may look as follows:++> <h1>$title$</h1>+> $body$+See "Hakyll.Web.Template" to see examples of the templating syntax.+-}+compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -- ^ How to transform content+                                              -> Rules ()          -- ^ Result compile compiler = Rules $ modify $ \s ->     s {rulesCompiler = Just (fmap SomeItem compiler)}   ----------------------------------------------------------------------------------- | Add a route.------ This adds a route for all items matching the current pattern.-route :: Routes -> Rules ()+{- | Add (or replace) routing in the current 'Rules' value.+__This functions controls IF and WHERE the compiled results are written out__+(use one of the 'match' functions to control WHAT content is processed and+'compile' to control HOW).+See 'Hakyll.Core.Routes.Routes' and 'Hakyll.Core.Identifier.Identifier' for+details on how output filepaths are computed.++Hint:+__If there's no route attached to a rule, the compilation result is not written out__.+However, the compilation result is saved to the 'Hakyll.Core.Store.Store'+and can be loaded and used within another rule. This behavior is needed,+for example, for templates.++=== __Examples__+__Rules with and without routing__++> -- e.g. file on disk: 'templates/post.html'+>+> -- Rule 1 (without routing)+> match "templates/*" $ do+>     -- compilation result saved to store with implicit identifier, e.g. 'templates/post.html'+>     compile templateCompiler+>+> -- Rule 2 (with routing)+> match "posts/**.md" $ do+>     route $ setExtension "html"+>     compile $ do+>        -- load compiled result of other rule with explicit identifier.+>        postTemplate <- loadBody "templates/post.html"+>        pandocCompiler >>= applyTemplate postTemplate defaultContext+Note that we don't set a route in the first rule to avoid writing out our+compiled templates.+However, we can still 'Hakyll.Core.Compiler.load' (or+'Hakyll.Core.Compiler.loadBody') the compiled templates to apply them in a+second rule.+The content for 'Hakyll.Web.Template.templateCompiler' comes implicitly from the+matched template files on disk. We don't have to pass that content around+manually. See 'match' and 'compile' for details.++To control where a compilation result will be written out (as done in the second+rule), use routing functions like 'Hakyll.Core.Routes.setExtension'.++See "Hakyll.Web.Template" for examples of templates and the templating syntax.+-}+route :: Routes   -- ^ Where to output compilation results+      -> Rules () -- ^ Result route route' = Rules $ modify $ \s -> s {rulesRoute = Just route'}  
lib/Hakyll/Core/Rules/Internal.hs view
@@ -13,14 +13,13 @@   --------------------------------------------------------------------------------+#if !MIN_VERSION_base(4,13,0) import           Control.Monad.Fail             (MonadFail)+#endif import           Control.Monad.Reader           (ask) import           Control.Monad.RWS              (RWST, runRWST) import           Control.Monad.Trans            (liftIO) import qualified Data.Map                       as M-#if MIN_VERSION_base(4,9,0)-import           Data.Semigroup                 (Semigroup (..))-#endif import           Data.Set                       (Set)  @@ -57,7 +56,6 @@   ---------------------------------------------------------------------------------#if MIN_VERSION_base(4,9,0) instance Semigroup RuleSet where     (<>) (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =         RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)@@ -65,12 +63,6 @@ instance Monoid RuleSet where     mempty  = RuleSet mempty mempty mempty mempty     mappend = (<>)-#else-instance Monoid RuleSet where-    mempty = RuleSet mempty mempty mempty mempty-    mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =-        RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)-#endif   --------------------------------------------------------------------------------
lib/Hakyll/Core/Store.hs view
@@ -16,14 +16,18 @@   ---------------------------------------------------------------------------------import qualified Data.Hashable        as DH+import           Control.Monad        (when) import           Data.Binary          (Binary, decode, encodeFile) import qualified Data.ByteString.Lazy as BL import qualified Data.Cache.LRU.IO    as Lru+import qualified Data.Hashable        as DH+import qualified Data.IORef           as IORef import           Data.List            (intercalate)+import qualified Data.Map             as Map import           Data.Maybe           (isJust) import           Data.Typeable        (TypeRep, Typeable, cast, typeOf)-import           System.Directory     (createDirectoryIfMissing, doesFileExist, removeFile)+import           System.Directory     (createDirectoryIfMissing, doesFileExist,+                                       removeFile) import           System.FilePath      ((</>)) import           System.IO            (IOMode (..), hClose, openFile) import           System.IO.Error      (catchIOError, ioeSetFileName,@@ -38,9 +42,11 @@ -------------------------------------------------------------------------------- data Store = Store     { -- | All items are stored on the filesystem-      storeDirectory :: FilePath-    , -- | Optionally, items are also kept in-memory-      storeMap       :: Maybe (Lru.AtomicLRU FilePath Box)+      storeDirectory  :: FilePath+    , -- | See 'set'+      storeWriteAhead :: IORef.IORef (Map.Map String Box)+      -- | Optionally, items are also kept in-memory+    , storeMap        :: Maybe (Lru.AtomicLRU FilePath Box)     }  @@ -72,10 +78,12 @@     -> IO Store  -- ^ Store new inMemory directory = do     createDirectoryIfMissing True directory+    writeAhead <- IORef.newIORef Map.empty     ref <- if inMemory then Just <$> Lru.newAtomicLRU csize else return Nothing     return Store-        { storeDirectory = directory-        , storeMap       = ref+        { storeDirectory  = directory+        , storeWriteAhead = writeAhead+        , storeMap        = ref         }   where     csize = Just 500@@ -92,16 +100,16 @@ -------------------------------------------------------------------------------- -- | Auxiliary: add an item to the in-memory cache cacheInsert :: Typeable a => Store -> String -> a -> IO ()-cacheInsert (Store _ Nothing)    _   _     = return ()-cacheInsert (Store _ (Just lru)) key x =+cacheInsert (Store _ _ Nothing)    _   _     = return ()+cacheInsert (Store _ _ (Just lru)) key x =     Lru.insert key (Box x) lru   -------------------------------------------------------------------------------- -- | Auxiliary: get an item from the in-memory cache cacheLookup :: forall a. Typeable a => Store -> String -> IO (Result a)-cacheLookup (Store _ Nothing)    _   = return NotFound-cacheLookup (Store _ (Just lru)) key = do+cacheLookup (Store _ _ Nothing)    _   = return NotFound+cacheLookup (Store _ _ (Just lru)) key = do     res <- Lru.lookup key lru     return $ case res of         Nothing      -> NotFound@@ -112,15 +120,15 @@  -------------------------------------------------------------------------------- cacheIsMember :: Store -> String -> IO Bool-cacheIsMember (Store _ Nothing)    _   = return False-cacheIsMember (Store _ (Just lru)) key = isJust <$> Lru.lookup key lru+cacheIsMember (Store _ _ Nothing)    _   = return False+cacheIsMember (Store _ _ (Just lru)) key = isJust <$> Lru.lookup key lru   -------------------------------------------------------------------------------- -- | Auxiliary: delete an item from the in-memory cache cacheDelete :: Store -> String -> IO ()-cacheDelete (Store _ Nothing)    _   = return ()-cacheDelete (Store _ (Just lru)) key = do+cacheDelete (Store _ _ Nothing)    _   = return ()+cacheDelete (Store _ _ (Just lru)) key = do     _ <- Lru.delete key lru     return () @@ -129,31 +137,61 @@ -- | Store an item set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO () set store identifier value = withStore store "set" (\key path -> do-    encodeFile path value+    -- We need to avoid concurrent writes to the filesystem.  Imagine the+    -- follow scenario:+    --+    --  *  We compile multiple posts+    --  *  All of these fetch some common metadata+    --  *  This metadata is missing; we fetch it and then store it.+    --+    -- To solve this, we skip duplicate writes by tracking their status+    -- in 'storeWriteAhead'.  Since this set will usually be small, the+    -- required locking should be fast.  Additionally the actual IO operation+    -- still happens outside of the locking.+    first <- IORef.atomicModifyIORef' (storeWriteAhead store) $+        \wa -> case Map.lookup key wa of+            Nothing -> (Map.insert key (Box value) wa, True)+            Just _  -> (wa, False)+     cacheInsert store key value++    -- Only the thread that stored the writeAhead should actually write this+    -- file.  That way, only one thread at a time will try to write this.+    -- Release the writeAhead value once we're done.+    when first $ do+        encodeFile path value+        IORef.atomicModifyIORef' (storeWriteAhead store) $+            \wa -> (Map.delete key wa, ())   ) identifier   -------------------------------------------------------------------------------- -- | Load an item-get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)+get :: forall a. (Binary a, Typeable a) => Store -> [String] -> IO (Result a) get store = withStore store "get" $ \key path -> do-    -- First check the in-memory map-    ref <- cacheLookup store key-    case ref of-        -- Not found in the map, try the filesystem-        NotFound -> do-            exists <- doesFileExist path-            if not exists-                -- Not found in the filesystem either-                then return NotFound-                -- Found in the filesystem-                else do-                    v <- decodeClose path-                    cacheInsert store key v-                    return $ Found v-        -- Found in the in-memory map (or wrong type), just return-        s -> return s+    -- Check the writeAhead value+    writeAhead <- IORef.readIORef $ storeWriteAhead store+    case Map.lookup key writeAhead of+        Just (Box x) -> case cast x of+            Just x' -> pure $ Found x'+            Nothing -> pure $ WrongType (typeOf (undefined :: a)) (typeOf x)+        Nothing -> do+            -- Check the in-memory map+            ref <- cacheLookup store key+            case ref of+                -- Not found in the map, try the filesystem+                NotFound -> do+                    exists <- doesFileExist path+                    if not exists+                        -- Not found in the filesystem either+                        then return NotFound+                        -- Found in the filesystem+                        else do+                            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     -- 'decodeFile' from Data.Binary which closes the file ASAP     decodeClose path = do@@ -167,8 +205,12 @@ -- | Strict function isMember :: Store -> [String] -> IO Bool isMember store = withStore store "isMember" $ \key path -> do-    inCache <- cacheIsMember store key-    if inCache then return True else doesFileExist path+    writeAhead <- IORef.readIORef $ storeWriteAhead store+    if Map.member key writeAhead+        then pure True+        else do+            inCache <- cacheIsMember store key+            if inCache then return True else doesFileExist path   --------------------------------------------------------------------------------
lib/Hakyll/Core/Util/File.hs view
@@ -10,13 +10,18 @@   ---------------------------------------------------------------------------------import           Control.Concurrent  (threadDelay)-import           Control.Exception   (SomeException, catch)-import           Control.Monad       (filterM, forM, when)+import           Control.Monad       (filterM, forM) import           System.Directory    (createDirectoryIfMissing,-                                      doesDirectoryExist, getDirectoryContents,-                                      removeDirectoryRecursive, removePathForcibly)+                                      doesDirectoryExist, getDirectoryContents) import           System.FilePath     (takeDirectory, (</>))+#ifndef mingw32_HOST_OS+import           Control.Monad       (when)+import           System.Directory    (removeDirectoryRecursive)+#else+import           Control.Concurrent  (threadDelay)+import           Control.Exception   (SomeException, catch)+import           System.Directory    (removePathForcibly)+#endif   --------------------------------------------------------------------------------@@ -66,19 +71,17 @@ --      https://github.com/haskell/directory/issues/96 --      https://github.com/haskell/win32/pull/129 ----- The hacky solution is to retry deleting directories a few times, +-- The hacky solution is to retry deleting directories a few times, -- with a delay, on Windows only. removeDirectory = retryWithDelay 10 . removePathForcibly-#endif - -------------------------------------------------------------------------------- -- | Retry an operation at most /n/ times (/n/ must be positive). --   If the operation fails the /n/th time it will throw that final exception. --   A delay of 100ms is introduced between every retry. retryWithDelay :: Int -> IO a -> IO a-retryWithDelay i x +retryWithDelay i x     | i <= 0    = error "Hakyll.Core.Util.File.retry: retry count must be 1 or more"     | i == 1    = x     | otherwise = catch x $ \(_::SomeException) -> threadDelay 100 >> retryWithDelay (i-1) x-+#endif
lib/Hakyll/Main.hs view
@@ -1,7 +1,5 @@ -------------------------------------------------------------------------------- -- | Module providing the main hakyll function and command-line argument parsing-{-# LANGUAGE CPP #-}- module Hakyll.Main     ( -- * Entry points       hakyll@@ -15,6 +13,7 @@     , Command(..)     , optionParser     , commandParser+    , defaultCommands     , defaultParser     , defaultParserPure     , defaultParserPrefs@@ -29,7 +28,6 @@   ---------------------------------------------------------------------------------import           Data.Monoid               ((<>)) import qualified Options.Applicative       as OA  @@ -153,47 +151,49 @@   commandParser :: Config.Configuration -> OA.Parser Command-commandParser conf = OA.subparser $ foldr ((<>) . produceCommand) mempty commands+commandParser conf =  OA.subparser $ foldr ((<>) . produceCommand) mempty (defaultCommands conf)     where-    portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))-    hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))-     produceCommand (c,a,b) = OA.command c (OA.info (OA.helper <*> a) (b)) -    commands =-        [ ( "build"-          , pure Build <*> OA.flag RunModeNormal RunModePrintOutOfDate (OA.long "dry-run" <> OA.help "Don't build, only print out-of-date items")-          , OA.fullDesc <> OA.progDesc "Generate the site"-          )-        , ( "check"-          , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")-          , OA.fullDesc <> OA.progDesc "Validate the site output"-          )-        , ( "clean"-          , pure Clean-          , OA.fullDesc <> OA.progDesc "Clean up and remove cache"-          )-        , ( "deploy"-          , pure Deploy-          , OA.fullDesc <> OA.progDesc "Upload/deploy your site"-           )-        , ( "preview"-          , pure Preview <*> portParser-          , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"-          )-        , ( "rebuild"-          , pure Rebuild-          , OA.fullDesc <> OA.progDesc "Clean and build again"-          )-        , ( "server"-          , pure Server <*> hostParser <*> portParser-          , OA.fullDesc <> OA.progDesc "Start a preview server"-          )-        , ( "watch"-          , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")-          , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server.  You can watch and recompile without running a server with --no-server."-          )-        ]++defaultCommands :: Config.Configuration -> [(String, OA.Parser Command, OA.InfoMod a)]+defaultCommands conf =+    [ ( "build"+      , pure Build <*> OA.flag RunModeNormal RunModePrintOutOfDate (OA.long "dry-run" <> OA.help "Don't build, only print out-of-date items")+      , OA.fullDesc <> OA.progDesc "Generate the site"+      )+    , ( "check"+      , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")+      , OA.fullDesc <> OA.progDesc "Validate the site output"+      )+    , ( "clean"+      , pure Clean+      , OA.fullDesc <> OA.progDesc "Clean up and remove cache"+      )+    , ( "deploy"+      , pure Deploy+      , OA.fullDesc <> OA.progDesc "Upload/deploy your site"+       )+    , ( "preview"+      , pure Preview <*> portParser+      , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"+      )+    , ( "rebuild"+      , pure Rebuild+      , OA.fullDesc <> OA.progDesc "Clean and build again"+      )+    , ( "server"+      , pure Server <*> hostParser <*> portParser+      , OA.fullDesc <> OA.progDesc "Start a preview server"+      )+    , ( "watch"+      , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")+      , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server.  You can watch and recompile without running a server with --no-server."+      )+    ]+    where+    portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))+    hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))   --------------------------------------------------------------------------------
lib/Hakyll/Preview/Poll.hs view
@@ -18,7 +18,7 @@  #ifdef mingw32_HOST_OS import           Control.Concurrent             (threadDelay)-import           Control.Exception              (IOException, throw, try)+import           Control.Exception              (IOException, try) import           System.Directory               (doesFileExist) import           System.Exit                    (exitFailure) import           System.FilePath                ((</>))
lib/Hakyll/Preview/Server.hs view
@@ -18,15 +18,14 @@ import qualified Hakyll.Core.Logger    as Logger  staticServer :: Logger               -- ^ Logger-             -> FilePath             -- ^ Directory to serve+             -> Static.StaticSettings -- ^ Static file server settings              -> String               -- ^ Host to bind on              -> Int                  -- ^ Port to listen on              -> IO ()                -- ^ Blocks forever-staticServer logger directory host port = do+staticServer logger settings host port = do     Logger.header logger $ "Listening on http://" ++ host ++ ":" ++ show port     Logger.flush logger -- ensure this line is logged before Warp errors-    Warp.runSettings warpSettings $-        Static.staticApp (Static.defaultFileServerSettings directory)+    Warp.runSettings warpSettings $ Static.staticApp settings   where     warpSettings = Warp.setLogger noLog         $ Warp.setHost (fromString host)
lib/Hakyll/Web/Feed.hs view
@@ -73,7 +73,8 @@       feedDescription :: String     , -- | Name of the feed author.       feedAuthorName  :: String-    , -- | Email of the feed author.+    , -- | Email of the feed author.  Set this to the empty String to leave out+      -- the email address.       feedAuthorEmail :: String     , -- | Absolute root URL of the feed site (e.g. @http://jaspervdj.be@)       feedRoot        :: String@@ -102,7 +103,7 @@         [ itemContext         , constField "root" (feedRoot config)         , constField "authorName"  (feedAuthorName config)-        , constField "authorEmail" (feedAuthorEmail config)+        , emailField         ]      feedContext = mconcat@@ -110,7 +111,7 @@          , constField "title"       (feedTitle config)          , constField "description" (feedDescription config)          , constField "authorName"  (feedAuthorName config)-         , constField "authorEmail" (feedAuthorEmail config)+         , emailField          , constField "root"        (feedRoot config)          , urlField   "url"          , updatedField@@ -124,6 +125,10 @@         (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of             StringField s -> return s             _             -> fail "Hakyll.Web.Feed.renderFeed: Internal error"++    emailField = case feedAuthorEmail config of+        ""    -> missingField+        email -> constField "authorEmail" email  -------------------------------------------------------------------------------- -- | Render an RSS feed using given templates with a number of items.
lib/Hakyll/Web/Html.hs view
@@ -4,6 +4,7 @@     ( -- * Generic       withTags     , withTagList+    , withTagListM        -- * Headers     , demoteHeaders@@ -23,13 +24,14 @@   --------------------------------------------------------------------------------+import           Control.Monad                   (void)+import           Control.Monad.Identity          (Identity(runIdentity)) import           Data.Char                       (digitToInt, intToDigit,                                                   isDigit, toLower) import           Data.Either                     (fromRight) import           Data.List                       (isPrefixOf, intercalate) import           Data.Maybe                      (fromMaybe) import qualified Data.Set                        as S-import           Control.Monad                   (void) import           System.FilePath                 (joinPath, splitPath,                                                   takeDirectory) import           Text.Blaze.Html                 (toHtml)@@ -51,7 +53,12 @@  -- | Map over all tags (as list) in the document withTagList :: ([TS.Tag String] -> [TS.Tag String]) -> String -> String-withTagList f = renderTags' . f . parseTags'+withTagList f = runIdentity . withTagListM (pure . f)++-- | Map over all tags (as list) in the document, in a monad+withTagListM :: Monad m => ([TS.Tag String] -> m [TS.Tag String]) -> String -> m String+withTagListM f = fmap renderTags' . f . parseTags'+{-# INLINE withTagListM #-}  -------------------------------------------------------------------------------- -- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
lib/Hakyll/Web/Meta/TwitterCard.hs view
@@ -59,5 +59,5 @@ twitterCardTemplateString :: String twitterCardTemplateString =   "<meta name=\"twitter:card\" content=\"summary\" />\-  \$if(twitter-creator)$<meta property=\"twitter:creator\" content=\"$twitter-creator$\" />$endif$\-  \$if(twitter-site)$<meta property=\"twitter:site\" content=\"$twitter-site$\" />$endif$"+  \$if(twitter-creator)$<meta name=\"twitter:creator\" content=\"$twitter-creator$\" />$endif$\+  \$if(twitter-site)$<meta name=\"twitter:site\" content=\"$twitter-site$\" />$endif$"
lib/Hakyll/Web/Pandoc/Biblio.hs view
@@ -10,7 +10,8 @@ -- source into a 'Pandoc' type and need to add processing for the bibliography, -- you can use 'processPandocBiblio' instead. -- 'pandocBiblioCompiler' is a convenience wrapper which works like 'pandocCompiler',--- but also takes paths to compiled bibliography and csl files.+-- but also takes paths to compiled bibliography and csl files;+-- 'pandocBibliosCompiler' is similar but instead takes a glob pattern for bib files. {-# LANGUAGE Arrows                     #-} {-# LANGUAGE DeriveDataTypeable         #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -25,6 +26,7 @@     , processPandocBiblio     , processPandocBiblios     , pandocBiblioCompiler+    , pandocBibliosCompiler     ) where  @@ -40,6 +42,7 @@ import           Hakyll.Core.Compiler import           Hakyll.Core.Compiler.Internal import           Hakyll.Core.Identifier+import           Hakyll.Core.Identifier.Pattern (fromGlob) import           Hakyll.Core.Item import           Hakyll.Core.Writable import           Hakyll.Web.Pandoc@@ -131,7 +134,7 @@             , Pandoc.FileInfo zeroTime . unBiblio . itemBody $ y             )           )-          [0 ..]+          [0 :: Integer ..]           biblios          stFiles = foldr ((.) . uncurry Pandoc.insertInFileTree)@@ -167,6 +170,19 @@     bib <- load $ fromFilePath bibFileName     liftM writePandoc         (getResourceBody >>= readPandocBiblio ropt csl bib)+    where ropt = defaultHakyllReaderOptions+            { -- The following option enables citation rendering+              readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions+            }++--------------------------------------------------------------------------------+-- | Compiles a markdown file via Pandoc. Requires the .csl and .bib files to be known to the compiler via match statements.+pandocBibliosCompiler :: String -> String -> Compiler (Item String)+pandocBibliosCompiler cslFileName bibFileName = do+    csl  <- load    $ fromFilePath cslFileName+    bibs <- loadAll $ fromGlob bibFileName+    liftM writePandoc+        (getResourceBody >>= readPandocBiblios ropt csl bibs)     where ropt = defaultHakyllReaderOptions             { -- The following option enables citation rendering               readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
lib/Hakyll/Web/Redirect.hs view
@@ -6,7 +6,6 @@     , createRedirects     ) where -import           Control.Applicative    ((<$>)) import           Control.Monad          (forM_, when) import           Data.Binary            (Binary (..)) import           Data.List              (sort, group)
lib/Hakyll/Web/Tags.hs view
@@ -58,6 +58,7 @@     , tagsField     , tagsFieldWith     , categoryField+    , simpleRenderLink     , sortTagsBy     , caseInsensitiveTags     ) where@@ -283,7 +284,7 @@ renderTagList = renderTags makeLink (intercalate ", ")   where     makeLink tag url count _ _ = renderHtml $-        H.a ! A.href (toValue url) $ toHtml (tag ++ " (" ++ show count ++ ")")+        H.a ! A.href (toValue url) ! A.rel "tag" $ toHtml (tag ++ " (" ++ show count ++ ")")   --------------------------------------------------------------------------------@@ -335,6 +336,7 @@ simpleRenderLink tag (Just filePath) = Just $     H.a ! A.title (H.stringValue ("All pages tagged '"++tag++"'."))         ! A.href (toValue $ toUrl filePath)+        ! (A.rel "tag")         $ toHtml tag  
lib/Hakyll/Web/Template/Context.hs view
@@ -54,11 +54,10 @@ -------------------------------------------------------------------------------- import           Control.Applicative           (Alternative (..)) import           Control.Monad                 (msum)+#if !MIN_VERSION_base(4,13,0) import           Control.Monad.Fail            (MonadFail)-import           Data.List                     (intercalate, tails)-#if MIN_VERSION_base(4,9,0)-import           Data.Semigroup                (Semigroup (..)) #endif+import           Data.List                     (intercalate, tails) import           Data.Time.Clock               (UTCTime (..)) import           Data.Time.Format              (formatTime, parseTimeM) import           Data.Time.Locale.Compat       (TimeLocale, defaultTimeLocale)@@ -106,18 +105,12 @@ -------------------------------------------------------------------------------- -- | 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  instance Monoid (Context a) where     mempty  = missingField     mappend = (<>)-#else-instance Monoid (Context a) where-    mempty                          = missingField-    mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i-#endif   --------------------------------------------------------------------------------@@ -326,6 +319,8 @@ -- --   * @2010-09-06@ --+--   * @06.09.2010@+-- --   * @September 06, 2010@ -- -- Alternatively, when the metadata has a field called @path@ in a@@ -391,6 +386,7 @@         , "%Y-%m-%d %H:%M:%S%Z"         , "%Y-%m-%d %H:%M:%S"         , "%Y-%m-%d"+        , "%d.%m.%Y"         , "%B %e, %Y %l:%M %p"         , "%B %e, %Y"         , "%b %d, %Y"
lib/Hakyll/Web/Template/Internal/Element.hs view
@@ -12,7 +12,7 @@   ---------------------------------------------------------------------------------import           Control.Applicative     ((<|>), (<*))+import           Control.Applicative     ((<|>)) import           Control.Monad           (void) import           Control.Arrow           (left) import           Data.Binary             (Binary, get, getWord8, put, putWord8)
lib/Hakyll/Web/Template/List.hs view
@@ -7,6 +7,7 @@ -- * An image list in a gallery -- -- * A sitemap+{-# LANGUAGE CPP           #-} {-# LANGUAGE TupleSections #-} module Hakyll.Web.Template.List     ( applyTemplateList@@ -20,7 +21,9 @@  -------------------------------------------------------------------------------- import           Control.Monad               (liftM)+#if !MIN_VERSION_base(4,13,0) import           Control.Monad.Fail          (MonadFail)+#endif import           Data.List                   (intersperse, sortBy) import           Data.Ord                    (comparing) import           Data.Time.Locale.Compat     (defaultTimeLocale)
tests/Hakyll/Core/UnixFilter/Tests.hs view
@@ -22,23 +22,22 @@  -------------------------------------------------------------------------------- tests :: TestTree-tests = testGroup "Hakyll.Core.UnixFilter.Tests"+tests = testGroup "Hakyll.Core.UnixFilter.Tests" $ #ifdef mingw32_HOST_OS-    -- The `rev` utility is not present by default on Windows-    [ testCase "unixFilter false" unixFilterFalse-    , testCase "unixFilter error" unixFilterError-    ]+    [] -- The `rev` utility is not present by default on Windows #else-    [ testCase "unixFilter rev"   unixFilterRev-    , testCase "unixFilter false" unixFilterFalse+    [ testCase "unixFilter rev"   unixFilterRev ]+#endif+    +++    [ testCase "unixFilter false" unixFilterFalse     , testCase "unixFilter error" unixFilterError     ]-#endif  testMarkdown :: Identifier testMarkdown = "russian.md"  --------------------------------------------------------------------------------+#ifndef mingw32_HOST_OS unixFilterRev :: H.Assertion unixFilterRev = do     store    <- newTestStore@@ -50,6 +49,7 @@   where     compiler = getResourceString >>= withItemBody (unixFilter "rev" [])     rev      = map reverse . lines+#endif   --------------------------------------------------------------------------------
tests/Hakyll/Web/Pandoc/Biblio/Tests.hs view
@@ -1,5 +1,6 @@ -------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP               #-} module Hakyll.Web.Pandoc.Biblio.Tests     ( tests     ) where@@ -16,7 +17,6 @@ -------------------------------------------------------------------------------- import           Hakyll import           Hakyll.Core.Runtime-import           Hakyll.Web.Pandoc.Biblio import qualified Hakyll.Core.Logger         as Logger import           TestSuite.Util @@ -38,7 +38,7 @@ goldenTest01 =     goldenVsString         "biblio01"-        (goldenTestsDataDir </> "cites-meijer.golden")+        (goldenTestsDataDir </> goldenTest)         (do             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.             logger <- Logger.new Logger.Error@@ -66,12 +66,19 @@             cleanTestEnv              return output)+    +    where +        goldenTest = +            if pandocMajorVersion == 2+                then "cites-meijer-pandoc2.golden" +                else "cites-meijer-pandoc3.golden" + goldenTest02 :: TestTree goldenTest02 =     goldenVsString         "biblio02"-        (goldenTestsDataDir </> "cites-meijer.golden")+        (goldenTestsDataDir </> goldenTest)         (do             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.             logger <- Logger.new Logger.Error@@ -99,12 +106,17 @@             cleanTestEnv              return output)+    where+        goldenTest = +            if pandocMajorVersion == 2+                then "cites-meijer-pandoc2.golden" +                else "cites-meijer-pandoc3.golden"  goldenTest03 :: TestTree goldenTest03 =     goldenVsString         "biblio03"-        (goldenTestsDataDir </> "cites-multiple.golden")+        (goldenTestsDataDir </> goldenTest)         (do             -- Code lifted from https://github.com/jaspervdj/hakyll-citeproc-example.             logger <- Logger.new Logger.Error@@ -134,3 +146,16 @@             cleanTestEnv              return output)+    where+        goldenTest = +            if pandocMajorVersion == 2 +                then "cites-multiple-pandoc2.golden" +                else "cites-multiple-pandoc3.golden"++--------------------------------------------------------------------------------+pandocMajorVersion :: Int+#if MIN_VERSION_pandoc(3,0,0)+pandocMajorVersion = 3+#else+pandocMajorVersion = 2   +#endif
tests/TestSuite/Util.hs view
@@ -15,7 +15,6 @@  -------------------------------------------------------------------------------- import           Data.List                     (intercalate, isInfixOf)-import           Data.Monoid                   (mempty) import qualified Data.Set                      as S import           Test.Tasty import           Test.Tasty.HUnit
+ tests/data/biblio/cites-meijer-pandoc2.golden view
@@ -0,0 +1,16 @@+<!doctype html>+<html lang="en">+    <head>+        <meta charset="utf-8">+        <title>This page cites a paper.</title>+    </head>+    <body>+        <h1>This page cites a paper.</h1>+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>+<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">+<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.+</div>+</div>+    </body>+</html>
+ tests/data/biblio/cites-meijer-pandoc3.golden view
@@ -0,0 +1,16 @@+<!doctype html>+<html lang="en">+    <head>+        <meta charset="utf-8">+        <title>This page cites a paper.</title>+    </head>+    <body>+        <h1>This page cites a paper.</h1>+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>+<div id="refs" class="references csl-bib-body hanging-indent" role="list">+<div id="ref-meijer1991functional" class="csl-entry" role="listitem">+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.+</div>+</div>+    </body>+</html>
− tests/data/biblio/cites-meijer.golden
@@ -1,16 +0,0 @@-<!doctype html>-<html lang="en">-    <head>-        <meta charset="utf-8">-        <title>This page cites a paper.</title>-    </head>-    <body>-        <h1>This page cites a paper.</h1>-        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>-<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">-<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">-Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.-</div>-</div>-    </body>-</html>
+ tests/data/biblio/cites-multiple-pandoc2.golden view
@@ -0,0 +1,20 @@+<!doctype html>+<html lang="en">+    <head>+        <meta charset="utf-8">+        <title>This page cites a paper and a book.</title>+    </head>+    <body>+        <h1>This page cites a paper and a book.</h1>+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>+<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>+<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">+<div id="ref-lipovaca2012" class="csl-entry" role="doc-biblioentry">+Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.+</div>+<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.+</div>+</div>+    </body>+</html>
+ tests/data/biblio/cites-multiple-pandoc3.golden view
@@ -0,0 +1,20 @@+<!doctype html>+<html lang="en">+    <head>+        <meta charset="utf-8">+        <title>This page cites a paper and a book.</title>+    </head>+    <body>+        <h1>This page cites a paper and a book.</h1>+        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>+<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>+<div id="refs" class="references csl-bib-body hanging-indent" role="list">+<div id="ref-lipovaca2012" class="csl-entry" role="listitem">+Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.+</div>+<div id="ref-meijer1991functional" class="csl-entry" role="listitem">+Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.+</div>+</div>+    </body>+</html>
− tests/data/biblio/cites-multiple.golden
@@ -1,20 +0,0 @@-<!doctype html>-<html lang="en">-    <head>-        <meta charset="utf-8">-        <title>This page cites a paper and a book.</title>-    </head>-    <body>-        <h1>This page cites a paper and a book.</h1>-        <p>I would like to cite one of my favourite papers <span class="citation" data-cites="meijer1991functional">(Meijer, Fokkinga, and Paterson 1991)</span> here.</p>-<p>And also a book <span class="citation" data-cites="lipovaca2012">(Lipovača 2012)</span>.</p>-<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">-<div id="ref-lipovaca2012" class="csl-entry" role="doc-biblioentry">-Lipovača, Miran. 2012. <em>Learn You a Haskell for Great Good! A Beginner’s Guide</em>. San Francisco, CA: No Starch Press.-</div>-<div id="ref-meijer1991functional" class="csl-entry" role="doc-biblioentry">-Meijer, Erik, Maarten Fokkinga, and Ross Paterson. 1991. <span>“Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.”</span> In <em>Conference on Functional Programming Languages and Computer Architecture</em>, 124–44. Springer.-</div>-</div>-    </body>-</html>
web/site.hs view
@@ -1,12 +1,10 @@ -------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-}-import           Control.Arrow    (second) import           Control.Monad    (filterM, forM_)-import           Data.List        (isPrefixOf, sortBy)+import           Data.List        (sortBy) import           Data.Ord         (comparing) import           Hakyll import           System.Directory (copyFile)-import           System.FilePath  (dropTrailingPathSeparator, splitPath) import qualified Text.Pandoc      as Pandoc