shakebook 0.4.0.0 → 0.5.0.0
raw patch · 12 files changed
+198/−318 lines, 12 filesdep +hashable-timedep +ixset-typeddep +path-extensionsdep −extradep −optparse-applicativedep −path
Dependencies added: hashable-time, ixset-typed, path-extensions
Dependencies removed: extra, optparse-applicative, path, shake, within
Files
- ChangeLog.md +11/−0
- README.md +5/−2
- app/Main.hs +0/−105
- shakebook.cabal +8/−48
- src/Shakebook.hs +25/−2
- src/Shakebook/Conventions.hs +3/−3
- src/Shakebook/Data.hs +6/−33
- src/Shakebook/Defaults.hs +2/−3
- src/Shakebook/Feed.hs +0/−1
- src/Shakebook/Mustache.hs +7/−9
- src/Shakebook/Pandoc.hs +22/−24
- test/Spec.hs +109/−88
ChangeLog.md view
@@ -1,5 +1,16 @@ # Changelog for Shakebook +## (v0.5.0.0)++* Upgrade to [shake-plus](https://hackage.haskell.org/package/shake-plus) v0.1.6.0.+* Drop `enrichFullUrl`, `enrichUrl` and `enrichSupposedUrl`.+* Drop `immediateShoots`.+* Drop dependency on `extra`.+* Remove `withXExtension` function and Depend on new library [path-extensions](https://hackage.haskell.org/package/path-extensions).+* Re-export `Development.Shake.Plus` and `Data.Aeson`+* Re-expot most `Shakebook` submodules.+* Re-export Text.Pandoc.Highlighting+ ## (v0.4.0.0) * Remove `SBConfig` and constraints from this library. This was only here for refactoring
README.md view
@@ -1,5 +1,8 @@ # Shakebook - Static Site & Technical Documentation Generator Shakebook is a collection of data handlers, shake rules and useful conventions-for generating technical documentation and blogs. This is the API library. For-a running example see http://shakebook.site.+for generating technical documentation and blogs. This is the API library.++To get started you can use the template repository at http://gitlab.com/shakebook-template/shakebook-template++For full documentation see http://shakebook.site.
− app/Main.hs
@@ -1,105 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Main where--import Control.Comonad.Env as E-import Development.Shake.Plus-import Options.Applicative-import Path-import RIO-import qualified RIO.HashMap as HM-import RIO.List-import qualified RIO.Text as T-import Shakebook-import Shakebook.Conventions-import Shakebook.Mustache-import Shakebook.Defaults-import Text.Pandoc.Options-import Within--sample :: Parser SimpleOpts-sample = SimpleOpts- <$> strOption ( long "src" <> metavar "SOURCEDIR" <> help "The source directory."- <> showDefault <> value "site" )- <*> strOption ( long "out" <> metavar "OUTPUTDIR" <> help "The output directory."- <> showDefault <> value "public")- <*> strOption ( long "baseUrl" <> metavar "BASEURL" <> help "The base url for your site.")- <*> option auto ( long "ppp" <> metavar "POSTSPERPAGE" <> help "Num of posts per page.")---data SimpleOpts = SimpleOpts- { srcDir :: String- , outDir :: String- , baseUrl :: String- , ppp :: Int- }--data BuildOpts = BuildOpts- { sbSrcDir :: Path Rel Dir- , sbOutDir :: Path Rel Dir- , sbBaseUrl :: Text- , sbMdRead :: ReaderOptions- , sbHtWrite :: WriterOptions- , sbPPP :: Int- }-data AppConfig = AppConfig- { _opts :: BuildOpts- , _lf :: LogFunc- }--class HasBuildOpts a where- buildOptsL :: Lens' a BuildOpts--instance HasLogFunc AppConfig where- logFuncL = lens _lf undefined--instance HasBuildOpts AppConfig where- buildOptsL = lens _opts undefined--opts :: ParserInfo SimpleOpts-opts = info (sample <**> helper)- ( fullDesc- <> progDesc "Creates a simple blog from source with default settings."- <> header "shakebook-simple-blog - A simple blog using standard shakebook conventions." )--main :: IO ()-main = do- (x :: SimpleOpts) <- execParser opts- s' <- parseRelDir (srcDir x)- o' <- parseRelDir (outDir x)- app $ BuildOpts s' o' (T.pack $ baseUrl x) defaultMarkdownReaderOptions defaultHtml5WriterOptions (ppp x)--app :: BuildOpts -> IO ()-app sbc = do- logOptions' <- logOptionsHandle stdout True- lf <- newLogFunc logOptions'- let f = AppConfig sbc (fst lf)-- shake shakeOptions $ do-- want ["all"]-- runShakePlus f $ do- BuildOpts{..} <- view buildOptsL-- readMDC <- newCache $ loadMarkdownAsJSON sbMdRead sbHtWrite-- postsC <- newCache $ \w -> do- xs <- batchLoadWithin' w readMDC- return $ defaultEnrichPost <$> xs-- getRecentPosts <- newCache $ \fp -> do- allPosts <- postsC fp- return $ take 5 (sortOn (Down . viewPostTime) $ HM.elems allPosts)-- ("index.html" `within` sbOutDir) %^> \out -> do- src <- blinkAndMapM sbSrcDir withMarkdownExtension $ out- v <- readMDC src- r <- getRecentPosts (["posts/*.md"] `within` sbSrcDir)- let v' = withRecentPosts r v- buildPageAction (sbSrcDir </> $(mkRelFile "template/index.html")) v' (fromWithin out)-- phony "index" $ needP [sbOutDir </> $(mkRelFile "index.html")]-- phony "all" $ need ["index"]-- snd lf
shakebook.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.33.1. -- -- see: https://github.com/sol/hpack ----- hash: 468c86683bdf3cc44a2aaa53465f140ee14fe822330207c10c6e16febcbd3b35+-- hash: 166b471f6106f0f5cbd0ce94e44b3042fb7f4d1b5da4deed5e14c7aea3195b8c name: shakebook-version: 0.4.0.0+version: 0.5.0.0 synopsis: Shake-based technical documentation generator; HTML & PDF description: Shakebook is a documentation generator aimed at covering all the bases for mathematical, technical and scientific diagrams and typesetting. Shakebook provides combinators for taking markdown files and combining them into documents, but allowing the user to control how. Shakebook provides general combinators for templating single pages, cofree comonads for representing tables of contents, and zipper comonads for representing pagers. category: Web@@ -43,63 +43,24 @@ , comonad , comonad-extras , doctemplates- , extra , feed , free+ , ixset-typed , lens , lens-aeson , mustache , pandoc , pandoc-types- , path+ , path-extensions , relude , rio- , shake , shake-plus , slick , split , text-time- , within , zipper-extra default-language: Haskell2010 -executable shakebook-simple-blog- main-is: Main.hs- other-modules:- Paths_shakebook- hs-source-dirs:- app- default-extensions: BangPatterns BinaryLiterals ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DoAndIfThenElse EmptyDataDecls ExistentialQuantification FlexibleContexts FlexibleInstances FunctionalDependencies GADTs GeneralizedNewtypeDeriving InstanceSigs KindSignatures LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude OverloadedStrings PartialTypeSignatures PatternGuards PolyKinds RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TupleSections TypeFamilies TypeSynonymInstances ViewPatterns- ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N- build-depends:- aeson- , aeson-with- , base >=4.7 && <5- , comonad- , comonad-extras- , doctemplates- , extra- , feed- , free- , lens- , lens-aeson- , mustache- , optparse-applicative- , pandoc- , pandoc-types- , path- , relude- , rio- , shake- , shake-plus- , shakebook- , slick- , split- , text-time- , within- , zipper-extra- default-language: Haskell2010- test-suite shakebook-test type: exitcode-stdio-1.0 main-is: Spec.hs@@ -116,18 +77,18 @@ , comonad , comonad-extras , doctemplates- , extra , feed , free+ , hashable-time+ , ixset-typed , lens , lens-aeson , mustache , pandoc , pandoc-types- , path+ , path-extensions , relude , rio- , shake , shake-plus , shakebook , slick@@ -135,6 +96,5 @@ , tasty , tasty-golden , text-time- , within , zipper-extra default-language: Haskell2010
src/Shakebook.hs view
@@ -1,6 +1,29 @@ module Shakebook (-- module Shakebook.Data+ module Control.Comonad.Cofree+, module Control.Comonad.Store+, module Control.Comonad.Store.Zipper+, module Control.Comonad.Zipper.Extra+, module Data.Aeson+, module Development.Shake.Plus+, module Shakebook.Conventions+, module Shakebook.Data+, module Shakebook.Defaults+, module Shakebook.Feed+, module Shakebook.Mustache+, module Shakebook.Pandoc+, module Text.Pandoc.Highlighting ) where +import Control.Comonad.Cofree+import Control.Comonad.Store+import Control.Comonad.Store.Zipper+import Control.Comonad.Zipper.Extra+import Data.Aeson+import Development.Shake.Plus+import Shakebook.Conventions import Shakebook.Data+import Shakebook.Defaults+import Shakebook.Feed+import Shakebook.Mustache+import Shakebook.Pandoc+import Text.Pandoc.Highlighting
src/Shakebook/Conventions.hs view
@@ -53,7 +53,6 @@ import Control.Comonad.Store import Control.Comonad.Zipper.Extra import Control.Lens hiding ((:<))-import Control.Monad.Extra import Data.Aeson as A import Data.Aeson.Lens import Data.Aeson.With@@ -165,14 +164,15 @@ extendNextPrevious :: Zipper [] Value -> Zipper [] Value extendNextPrevious = extendPrevious . extendNext --- | Extend a Zipper of JSON Values to add "previous" objects.+-- | Extend a Zipper of Values to add "previous" objects. extendPrevious :: Zipper [] Value -> Zipper [] Value extendPrevious = extend (liftA2 withPrevious zipperPreviousMaybe extract) --- | Extend a Zipper of JSON Values to add "next" objects.+-- | Extend a Zipper of Values to add "next" objects. extendNext :: Zipper [] Value -> Zipper [] Value extendNext = extend (liftA2 withNext zipperNextMaybe extract) +-- | Extend a Zipper of Values to add list of "pages" within r hops either side of the focus. extendPageNeighbours :: Int -> Zipper [] Value -> Zipper [] Value extendPageNeighbours r = extend (liftA2 withPages (zipperWithin r) extract)
src/Shakebook/Data.hs view
@@ -2,19 +2,16 @@ module Shakebook.Data where import Control.Comonad.Env as E-import Control.Comonad.Cofree import Control.Lens hiding ((:<))-import Control.Monad.Extra import Data.Aeson as A import Data.Aeson.Lens import Data.Aeson.With import Development.Shake.Plus-import Path as P+import Path.Extensions import RIO hiding (Lens', lens, view) import qualified RIO.Text as T import Shakebook.Pandoc import Text.Pandoc-import Within newtype PathDisplay a t = PathDisplay (Path a t) @@ -23,10 +20,10 @@ newtype WithinDisplay a t = WithinDisplay (Within a t) -instance Display t => Display (WithinDisplay a t) where- display (WithinDisplay (WithinT (EnvT e (Identity a)))) = display (PathDisplay e) <> "[" <> display a <> "]"+instance Display (WithinDisplay a (Path b t)) where+ display (WithinDisplay (WithinT (EnvT e (Identity a)))) = display (PathDisplay e) <> "[" <> display (PathDisplay a) <> "]" -instance Display t => Display [WithinDisplay a t] where+instance Display [WithinDisplay a (Path b t)] where display [] = "" display (x : xs) = display x <> " : " <> display xs @@ -74,35 +71,14 @@ withUrl :: Text -> Value -> Value withUrl = withStringField "url" --- | Assuming a "url" field, enrich via a baseURL-enrichFullUrl :: Text -> Value -> Value-enrichFullUrl base v = withFullUrl (base <> viewUrl v) v---- | Assuming a 'src-path' field, enrich using withUrl using a Text -> Text transformation.-enrichUrl :: (Text -> Text) -> Value -> Value-enrichUrl f v = withUrl (f (viewSrcPath v)) v--withHtmlExtension :: MonadThrow m => Path Rel File -> m (Path Rel File)-withHtmlExtension = replaceExtension ".html"--withMarkdownExtension :: MonadThrow m => Path Rel File -> m (Path Rel File)-withMarkdownExtension = replaceExtension ".md"--withHaskellExtension :: MonadThrow m => Path Rel File -> m (Path Rel File)-withHaskellExtension = replaceExtension ".hs"-+-- | Add a leading slash to a `Path Rel File` to turn it into a url as `Text`. toGroundedUrl :: Path Rel File -> Text toGroundedUrl = T.pack . toFilePath . ($(mkAbsDir "/") </>) +-- | Generate a "supposed" url, the grounded version of the markdown source path. generateSupposedUrl :: MonadThrow m => Path Rel File -> m Text generateSupposedUrl srcPath = toGroundedUrl <$> withHtmlExtension srcPath -enrichSupposedUrl :: MonadThrow m => Value -> m Value-enrichSupposedUrl v = do- x <- parseRelFile $ T.unpack $ viewSrcPath v- y <- generateSupposedUrl x- return $ withUrl y v- {-| Get a JSON Value of Markdown Data with markdown body as "contents" field and the srcPath as "srcPath" field.@@ -120,6 +96,3 @@ return $ withContent outText . withSrcPath (T.pack . toFilePath $ extract srcPath) . withUrl supposedUrl $ meta'--immediateShoots :: Cofree [] a -> [a]-immediateShoots(_ :< xs) = extract <$> xs
src/Shakebook/Defaults.hs view
@@ -2,11 +2,11 @@ module Shakebook.Defaults where import Data.Aeson as A+import Development.Shake.Plus import RIO import qualified RIO.Map as M import qualified RIO.Text as T import RIO.Time-import Path import Shakebook.Conventions import Text.DocTemplates import Text.Pandoc.Definition@@ -28,7 +28,6 @@ defaultEnrichPost = enrichTeaser "<!--more-->" . enrichTagLinks ("/posts/tags/" <>) . enrichPrettyDate defaultPrettyTimeFormat--- . enrichTypicalUrl defaultMarkdownReaderOptions :: ReaderOptions defaultMarkdownReaderOptions = def { readerExtensions = pandocExtensions }@@ -45,7 +44,7 @@ } defaultVideoReplacement :: Text -> Text -> Inline-defaultVideoReplacement baseUrl = \x -> Str $ "[Video available at [" <> baseUrl <> "/" <> x <> "]"+defaultVideoReplacement baseUrl x = Str $ "[Video available at [" <> baseUrl <> "/" <> x <> "]" defaultPagePaths :: MonadThrow m => [Int] -> m [Path Rel File] defaultPagePaths xs = do
src/Shakebook/Feed.hs view
@@ -5,7 +5,6 @@ import Data.Aeson import Development.Shake.Plus-import Path import RIO import RIO.List.Partial import qualified RIO.Text.Lazy as TL
src/Shakebook/Mustache.hs view
@@ -12,20 +12,17 @@ , compileTemplate' ) where -import Data.Aeson-import Path-import RIO+import Data.Aeson+import Development.Shake.Plus+import RIO import qualified Slick.Mustache-import Development.Shake.Plus-import Text.Mustache-import Within+import Text.Mustache +-- | Lifted version of `Slick.Mustache.compileTemplate'` with well-typed `Path`. compileTemplate' :: MonadAction m => Path Rel File -> m Template compileTemplate' = liftAction . Slick.Mustache.compileTemplate' . toFilePath -{-| - Build a single page straight from a template.--}+-- | Build a single page straight from a template. buildPageAction :: MonadAction m => Path Rel File -- ^ The HTML templatate. -> Value -- ^ A JSON value.@@ -35,6 +32,7 @@ pageT <- compileTemplate' template writeFile' out $ substitute pageT value +-- | Like `buildPageAction`, but uses `Within` values. buildPageActionWithin :: MonadAction m => Within Rel (Path Rel File) -> Value
src/Shakebook/Pandoc.hs view
@@ -12,26 +12,24 @@ , flattenMeta ) where -import Control.Comonad.Cofree-import Data.Aeson-import Development.Shake.Plus-import RIO-import qualified RIO.ByteString.Lazy as LBS-import qualified RIO.Text as T-import Path+import Control.Comonad.Cofree+import Data.Aeson+import Development.Shake.Plus+import RIO+import qualified RIO.ByteString.Lazy as LBS+import qualified RIO.Text as T import qualified Slick.Pandoc-import Text.Pandoc.Class-import Text.Pandoc.Definition-import Text.Pandoc.Readers-import Text.Pandoc.Options-import Text.Pandoc.PDF-import Text.Pandoc.Templates-import Text.Pandoc.Walk-import Text.Pandoc.Writers-import Within+import Text.Pandoc.Class+import Text.Pandoc.Definition+import Text.Pandoc.Options+import Text.Pandoc.PDF+import Text.Pandoc.Readers+import Text.Pandoc.Templates+import Text.Pandoc.Walk+import Text.Pandoc.Writers -data PandocActionException = PandocActionException String- deriving (Show, Eq, Typeable)+newtype PandocActionException = PandocActionException String+ deriving (Show, Eq, Typeable) instance Exception PandocActionException where displayException (PandocActionException s) = s@@ -57,10 +55,10 @@ -- | Find all the images in a `Pandoc` data structure and call `Development.Shake.Plus.need` on them. needPandocImagesIn :: (MonadAction m, MonadThrow m) => Path Rel Dir -> Pandoc -> m () needPandocImagesIn outDir pdoc =- mapM parseRelFile (fmap (drop 1 . T.unpack) $ pullImages pdoc) >>= needIn outDir where+ mapM parseRelFile (drop 1 . T.unpack <$> pullImages pdoc) >>= needIn outDir where pullImages = query f f (Image _ _ (src, _)) = [src]- f _ = []+ f _ = [] -- | Make a pdflatex in an `Action`. makePDFLaTeX :: (MonadAction m, MonadThrow m) => WriterOptions -> Pandoc -> m LBS.ByteString@@ -74,7 +72,7 @@ -- in the Cofree. This is so that Headers that H1s that would correctly display for an HTML page -- will be lower in the table of contents in the PDF equivalent. progressivelyDemoteHeaders :: Cofree [] Pandoc -> Cofree [] Pandoc-progressivelyDemoteHeaders = pushHeaders (0) where+progressivelyDemoteHeaders = pushHeaders 0 where handleHeaders :: Int -> Block -> Block handleHeaders i (Header a as xs) = Header (max 1 (a + i)) as xs handleHeaders _ x = x@@ -83,16 +81,16 @@ pushHeaders i (x :< xs) = walk (handleHeaders i) x :< map (pushHeaders (i+1)) xs -- | For a list of file extensions, replace the images with an Inline based on its src path.-replaceUnusableImages :: MonadThrow m => [String] -> (Text -> Inline) -> Pandoc -> m (Pandoc)+replaceUnusableImages :: MonadThrow m => [String] -> (Text -> Inline) -> Pandoc -> m Pandoc replaceUnusableImages exts f = walkM handleImages where handleImages i@(Image _ _ (src, _)) = do x <- parseAbsFile (T.unpack src) >>= fileExtension- return $ if elem x exts then f src else i+ return $ if x `elem` exts then f src else i handleImages x = return x prefixAllImages :: Path Rel Dir -> Pandoc -> Pandoc prefixAllImages dir = walk handleImages where- handleImages (Image attr ins (src, txt)) = Image attr ins ((T.pack $ toFilePath dir) <> "/" <> src, txt)+ handleImages (Image attr ins (src, txt)) = Image attr ins (T.pack (toFilePath dir) <> "/" <> src, txt) handleImages x = x flattenMeta :: MonadAction m => (Pandoc -> PandocIO Text) -> Meta -> m Value
test/Spec.hs view
@@ -1,29 +1,20 @@ {-# LANGUAGE TemplateHaskell #-} -import Control.Comonad.Cofree-import Control.Comonad.Store-import Control.Comonad.Store.Zipper-import Control.Comonad.Zipper.Extra-import Data.Aeson+import Control.Lens hiding ((:<)) import Data.Aeson.With+import Data.Hashable.Time() import Data.List.Split import Data.Text.Time-import Development.Shake.Plus-import Path+import Path.Extensions import RIO import RIO.Partial import qualified RIO.HashMap as HM import RIO.List import RIO.List.Partial import qualified RIO.Text as T-import Shakebook.Data-import Shakebook.Defaults-import Shakebook.Mustache-import Shakebook.Conventions+import Shakebook import Test.Tasty import Test.Tasty.Golden-import Text.Pandoc.Highlighting-import Within sourceFolder :: Path Rel Dir sourceFolder = $(mkRelDir "test/site")@@ -80,120 +71,150 @@ allPosts <- postsC fp return $ genBlogNavbarData "Blog" "/posts/" (T.pack . defaultPrettyMonthFormat)- (defaultMonthUrlFragment) (HM.elems allPosts)+ defaultMonthUrlFragment (HM.elems allPosts) + postsZ <- newCache $ \fp -> do+ xs <- sortedPosts fp+ zs <- ifor xs $ \i (k, _) -> do+ z <- zipper' (snd <$> xs)+ return (k, seek i z)+ return $ HM.fromList zs++ blogIndexPageData <- newCache $ \fp -> do+ xs <- sortedPosts fp+ genIndexPageData (snd <$> xs) "Posts" ("/posts/pages/" <>) postsPerPage++ blogTagIndexPageData <- newCache $ \fp -> do+ xs <- sortedPosts fp+ zs <- forM (viewAllPostTags (snd <$> xs)) $ \t -> do+ let ys = filter (elem t . viewTags . snd) xs+ k <- genIndexPageData (snd <$> ys) ("Posts tagged " <> t) (("/posts/tags/" <> t <> "/pages/") <>) postsPerPage+ return (t, k)+ return $ HM.fromList zs++ blogMonthIndexPageData <- newCache $ \fp -> do+ xs <- sortedPosts fp+ zs <- forM (nub $ defaultMonthUrlFormat <$> viewAllPostTimes (snd <$> xs)) $ \t -> do+ let t' = parseISODateTime . T.pack $ t+ let ys = filter (sameMonth t' . viewPostTime . snd) xs+ k <- genIndexPageData (snd <$> ys)+ (("Posts from " <>) . T.pack . defaultPrettyMonthFormat $ t')+ (("/posts/months/" <> T.pack (defaultMonthUrlFormat t') <> "/pages/") <>) postsPerPage+ return (t', k)+ return $ HM.fromList zs+ let myPosts = ["posts/*.md"] `within` sourceFolder + o' = (`within` outputFolder)+ s' = (`within` sourceFolder)+ myBuildPage tmpl v out = do rs <- getRecentPosts myPosts let v' = withHighlighting pygments . withSocialLinks mySocial . withSiteTitle siteTitle . withRecentPosts rs $ v- buildPageActionWithin (tmpl `within` sourceFolder) v' out+ buildPageActionWithin (s' tmpl) v' out + myBuildBlogPage tmpl v out = do+ k <- getBlogNavbar myPosts+ myBuildPage tmpl (withJSON k v) out - ("index.html" `within` outputFolder) %^> \out -> do- src <- blinkAndMapM sourceFolder withMarkdownExtension $ out+ myBuildPostListPage z out = do+ let v = extract . extendPageNeighbours numPageNeighbours $ z+ myBuildBlogPage $(mkRelFile "templates/post-list.html") v out++ o' "index.html" %^> \out -> do+ src <- blinkAndMapM sourceFolder withMdExtension out v <- readMDC src myBuildPage $(mkRelFile "templates/index.html") v out - ("posts/*.html" `within` outputFolder) %^> \out -> do- src <- blinkAndMapM sourceFolder withMarkdownExtension $ out- xs <- sortedPosts myPosts- let k = elemIndex src (fst <$> xs)- let z = fromJust $ liftA2 seek k $ zipper (snd <$> xs)- n <- getBlogNavbar myPosts- let v' = withJSON n $ (extract z) - myBuildPage $(mkRelFile "templates/post.html") v' out+ o' "posts/*.html" %^> \out -> do+ src <- blinkAndMapM sourceFolder withMdExtension out+ xs <- postsZ myPosts+ case HM.lookup src xs of+ Nothing -> logError $ "Attempting to lookup non-existent post " <> display (WithinDisplay src)+ Just x -> myBuildBlogPage $(mkRelFile "templates/post.html") (extract x) out - toc' <- mapM (mapM withHtmlExtension) $ fmap (`within` outputFolder) tableOfContents- void . sequence . flip extend toc' $ \xs -> (fmap toFilePath $ extract xs) %^> \out -> do- let getDoc = readMDC <=< blinkAndMapM sourceFolder withMarkdownExtension + toc' <- mapM (mapM withHtmlExtension) $ fmap o' tableOfContents+ void . sequence . flip extend toc' $ \xs -> (toFilePath <$> extract xs) %^> \out -> do+ let getDoc = readMDC <=< blinkAndMapM sourceFolder withMdExtension ys <- mapM getDoc toc'- zs <- mapM getDoc $ (immediateShoots xs)- v <- getDoc $ out+ zs <- mapM getDoc (fmap extract . unwrap $ xs)+ v <- getDoc out let v' = withJSON (genTocNavbarData ys) . withSubsections zs $ v myBuildPage $(mkRelFile "templates/docs.html") v' out - ("posts/index.html" `within` outputFolder) %^>- copyFileChangedWithin ($(mkRelFile "posts/pages/1/index.html") `within` outputFolder)+ o' "posts/index.html" %^>+ copyFileChangedWithin (o' $(mkRelFile "posts/pages/1/index.html")) - ("posts/pages/*/index.html" `within` outputFolder) %^> \out -> do- xs <- sortedPosts myPosts+ o' "posts/pages/*/index.html" %^> \out -> do let n = (+ (-1)) . read . (!! 2) . splitOn "/" . toFilePath . extract $ out- p <- seek n <$> genIndexPageData (snd <$> xs) "Posts" ("/posts/pages/" <>) postsPerPage- k <- getBlogNavbar myPosts- let v = withJSON k $ extract $ extendPageNeighbours numPageNeighbours p- myBuildPage $(mkRelFile "templates/post-list.html") v out+ xs <- blogIndexPageData myPosts+ myBuildPostListPage (seek n xs) out - ("posts/tags/*/index.html" `within` outputFolder) %^> \out -> do+ o' "posts/tags/*/index.html" %^> \out -> do let t = (!! 2) . splitOn "/" . toFilePath . extract $ out i <- parseRelFile $ "posts/tags/" <> t <> "/pages/1/index.html"- copyFileChangedWithin (i `within` outputFolder) out+ copyFileChangedWithin (o' i) out - ("posts/tags/*/pages/*/index.html" `within` outputFolder) %^> \out -> do- let t = T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out- xs <- filter (elem t . viewTags . snd) <$> sortedPosts myPosts- let n = (+ (-1)) . read . (!! 4) . splitOn "/" . toFilePath . extract $ out- p <- seek n <$> genIndexPageData (snd <$> xs) ("Posts tagged " <> t) (("/posts/tags/" <> t <> "/pages/") <>) postsPerPage- k <- getBlogNavbar myPosts- let v = withJSON k $ extract $ extendPageNeighbours numPageNeighbours p- myBuildPage $(mkRelFile "templates/post-list.html") v out+ o' "posts/tags/*/pages/*/index.html" %^> \out -> do+ let t = T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out+ let n = (+ (-1)) . read . (!! 4) . splitOn "/" . toFilePath . extract $ out+ xs <- blogTagIndexPageData myPosts+ case HM.lookup t xs of+ Nothing -> logError $ "Attempting to lookup non-existant tag " <> display t+ Just x -> myBuildPostListPage (seek n x) out - ("posts/months/*/index.html" `within` outputFolder) %^> \out -> do+ o' "posts/months/*/index.html" %^> \out -> do let t = (!! 2) . splitOn "/" . toFilePath . extract $ out i <- parseRelFile $ "posts/months/" <> t <> "/pages/1/index.html"- copyFileChangedWithin (i `within` outputFolder) out+ copyFileChangedWithin (o' i) out - ("posts/months/*/pages/*/index.html" `within` outputFolder) %^> \out -> do- let t = parseISODateTime . T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out- xs <- filter (sameMonth t . viewPostTime . snd) <$> sortedPosts myPosts- let n = (+ (-1)) . read . (!! 4) . splitOn "/" . toFilePath . extract $ out- p <- seek n <$> genIndexPageData (snd <$> xs)- (("Posts from " <>) . T.pack . defaultPrettyMonthFormat $ t)- (("/posts/months/" <> T.pack (defaultMonthUrlFormat t) <> "/pages/") <>) postsPerPage- k <- getBlogNavbar myPosts- let v = withJSON k $ extract $ extendPageNeighbours numPageNeighbours p- myBuildPage $(mkRelFile "templates/post-list.html") v out+ o' "posts/months/*/pages/*/index.html" %^> \out -> do+ let t = parseISODateTime . T.pack . (!! 2) . splitOn "/" . toFilePath . extract $ out+ let n = (+ (-1)) . read . (!! 4) . splitOn "/" . toFilePath . extract $ out+ xs <- blogMonthIndexPageData myPosts+ case HM.lookup t xs of+ Nothing -> logError $ "Attempting to lookup non-existant month " <> displayShow t+ Just x -> myBuildPostListPage (seek n x) out - phony "index" $- needIn outputFolder [$(mkRelFile "index.html")]+ o' ["css//*", "js//*", "webfonts//*", "images//*"] |%^> \out ->+ copyFileChangedWithin (blinkLocalDir sourceFolder out) out + let simplePipeline f = getDirectoryFiles sourceFolder >=> mapM f >=> needIn outputFolder+ verbatimPipeline = simplePipeline return++ phony "statics" $ verbatimPipeline ["css//*", "js//*", "webfonts//*", "images//*"]++ phony "index" $ needIn outputFolder [$(mkRelFile "index.html")]+ phony "post-index" $ do- xs <- sortedPosts myPosts- ps <- paginate' postsPerPage xs- fs <- defaultPagePaths [1..size ps]- needIn (outputFolder </> $(mkRelDir "posts")) ($(mkRelFile "index.html") : fs)+ ps <- blogIndexPageData myPosts+ fs <- defaultPagePaths [1..size ps]+ needIn (outputFolder </> $(mkRelDir "posts")) ($(mkRelFile "index.html") : fs) phony "by-tag-index" $ do- xs <- sortedPosts myPosts- forM_ (viewAllPostTags (snd <$> xs)) $ \t -> do- u <- parseRelDir $ T.unpack t- let xs' = filter (elem t . viewTags . snd) xs- ps <- paginate' postsPerPage xs'- fs <- defaultPagePaths [1..size ps]- let tagFolder = outputFolder </> $(mkRelDir "posts/tags") </> u- needIn tagFolder ($(mkRelFile "index.html") : fs)+ ps <- blogTagIndexPageData myPosts+ void $ flip HM.traverseWithKey ps $ \t z -> do+ u <- parseRelDir $ T.unpack t+ fs <- defaultPagePaths [1..size z]+ let tagFolder = outputFolder </> $(mkRelDir "posts/tags") </> u+ needIn tagFolder ($(mkRelFile "index.html") : fs) phony "by-month-index" $ do- xs <- sortedPosts myPosts- forM_ (viewAllPostTimes (snd <$> xs)) $ \t -> do- u <- parseRelDir $ defaultMonthUrlFormat t- let monthFolder = outputFolder </> $(mkRelDir "posts/months") </> u- let xs' = filter (sameMonth t . viewPostTime . snd) xs- ps <- paginate' postsPerPage xs'- fs <- defaultPagePaths [1..size ps]- needIn monthFolder ($(mkRelFile "index.html") : fs)+ ps <- blogMonthIndexPageData myPosts+ void $ flip HM.traverseWithKey ps $ \t z -> do+ u <- parseRelDir $ defaultMonthUrlFormat t+ fs <- defaultPagePaths [1..size z]+ let monthFolder = outputFolder </> $(mkRelDir "posts/months") </> u+ needIn monthFolder ($(mkRelFile "index.html") : fs) phony "docs" $ mapM withHtmlExtension tableOfContents >>= needIn outputFolder- - phony "posts" $- getDirectoryFilesWithin' (["posts/*.md"] `within` sourceFolder) >>= - mapM (blinkAndMapM outputFolder withHtmlExtension) >>=- needWithin' + phony "posts" $ simplePipeline withHtmlExtension ["posts/*.md"]+ phony "clean" $ do logInfo $ "Cleaning files in " <> display (PathDisplay outputFolder) removeFilesAfter outputFolder ["//*"]@@ -203,7 +224,7 @@ map ( \x -> goldenVsFile x x (replace "golden" "public" x) (return ())) xs- where replace from to' = intercalate to' . splitOn from+ where replace fr to' = intercalate to' . splitOn fr main :: IO () main = do