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 +53/−0
- data/templates/atom.xml +2/−0
- hakyll.cabal +29/−24
- lib/Hakyll/Check.hs +2/−13
- lib/Hakyll/Commands.hs +2/−2
- lib/Hakyll/Core/Compiler/Internal.hs +2/−12
- lib/Hakyll/Core/Configuration.hs +12/−1
- lib/Hakyll/Core/Identifier.hs +4/−6
- lib/Hakyll/Core/Identifier/Pattern/Internal.hs +0/−13
- lib/Hakyll/Core/Item.hs +0/−1
- lib/Hakyll/Core/Metadata.hs +2/−0
- lib/Hakyll/Core/Provider/Metadata.hs +0/−1
- lib/Hakyll/Core/Routes.hs +2/−16
- lib/Hakyll/Core/Rules.hs +296/−24
- lib/Hakyll/Core/Rules/Internal.hs +2/−10
- lib/Hakyll/Core/Store.hs +77/−35
- lib/Hakyll/Core/Util/File.hs +13/−10
- lib/Hakyll/Main.hs +41/−41
- lib/Hakyll/Preview/Poll.hs +1/−1
- lib/Hakyll/Preview/Server.hs +3/−4
- lib/Hakyll/Web/Feed.hs +8/−3
- lib/Hakyll/Web/Html.hs +9/−2
- lib/Hakyll/Web/Meta/TwitterCard.hs +2/−2
- lib/Hakyll/Web/Pandoc/Biblio.hs +18/−2
- lib/Hakyll/Web/Redirect.hs +0/−1
- lib/Hakyll/Web/Tags.hs +3/−1
- lib/Hakyll/Web/Template/Context.hs +5/−9
- lib/Hakyll/Web/Template/Internal/Element.hs +1/−1
- lib/Hakyll/Web/Template/List.hs +3/−0
- tests/Hakyll/Core/UnixFilter/Tests.hs +8/−8
- tests/Hakyll/Web/Pandoc/Biblio/Tests.hs +29/−4
- tests/TestSuite/Util.hs +0/−1
- tests/data/biblio/cites-meijer-pandoc2.golden +16/−0
- tests/data/biblio/cites-meijer-pandoc3.golden +16/−0
- tests/data/biblio/cites-meijer.golden +0/−16
- tests/data/biblio/cites-multiple-pandoc2.golden +20/−0
- tests/data/biblio/cites-multiple-pandoc3.golden +20/−0
- tests/data/biblio/cites-multiple.golden +0/−20
- web/site.hs +1/−3
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