her.esy.fun/Shakefile.hs

268 lines
8.7 KiB
Haskell
Raw Normal View History

2020-05-25 20:30:22 +00:00
{-# LANGUAGE OverloadedStrings #-}
2020-06-22 07:39:44 +00:00
{-# LANGUAGE NoImplicitPrelude #-}
2020-06-22 09:44:11 +00:00
import Protolude
2020-05-25 20:30:22 +00:00
2020-06-22 09:44:11 +00:00
import Development.Shake
import Development.Shake.Command
import Development.Shake.FilePath
2020-06-14 11:19:13 +00:00
2020-06-22 09:44:11 +00:00
import Control.Monad.Fail
2020-06-22 21:01:47 +00:00
import Data.Aeson
2020-06-23 17:28:41 +00:00
-- import qualified Text.Megaparsec as Megaparsec
2020-06-22 21:01:47 +00:00
import Data.Default ( Default(def) )
import qualified Data.Text as T
import Text.Mustache
2020-06-23 17:28:41 +00:00
import Text.Pandoc.Class (PandocMonad)
2020-06-22 21:01:47 +00:00
import qualified Text.Pandoc.Class as Pandoc
import Text.Pandoc.Definition ( Pandoc(..)
, Block(..)
, Inline
, nullMeta
, docTitle
, docDate
, docAuthors
)
import Text.Pandoc.Options ( ReaderOptions(..)
2020-06-23 07:15:03 +00:00
, WriterOptions(..)
2020-06-23 17:28:41 +00:00
, ObfuscationMethod(..)
2020-06-23 07:15:03 +00:00
)
2020-06-22 21:01:47 +00:00
import qualified Text.Pandoc.Readers as Readers
import qualified Text.Pandoc.Writers as Writers
2020-05-25 20:28:06 +00:00
main :: IO ()
2020-06-22 10:08:07 +00:00
main = shakeArgs shOpts buildRules
where
shOpts =
shakeOptions
{ shakeVerbosity = Chatty
, shakeLintInside = ["\\"]
}
-- Configuration
-- Should probably go in a Reader Monad
2020-06-23 17:28:41 +00:00
srcDir :: FilePath
srcDir = "src"
2020-06-22 10:08:07 +00:00
siteDir :: FilePath
siteDir = "_site"
optimDir :: FilePath
optimDir = "_optim"
-- BlogPost data structure (a bit of duplication because the metas are in Pandoc)
2020-05-25 20:28:06 +00:00
2020-06-22 07:39:44 +00:00
data BlogPost =
BlogPost { postTitle :: T.Text
, postDate :: T.Text
, postAuthors :: [T.Text]
, postUrl :: FilePath
2020-06-23 07:15:03 +00:00
, postToc :: Bool
2020-06-22 09:44:11 +00:00
, postBody :: Pandoc
2020-06-22 07:39:44 +00:00
}
inlineToText :: PandocMonad m => [Inline] -> m T.Text
inlineToText inline =
2020-06-22 09:44:11 +00:00
Writers.writeAsciiDoc def (Pandoc nullMeta [Plain inline])
getBlogpostFromMetas
2020-06-23 07:15:03 +00:00
:: (MonadIO m, MonadFail m) => [Char] -> Bool -> Pandoc -> m BlogPost
getBlogpostFromMetas path toc pandoc@(Pandoc meta _) = do
2020-06-22 09:44:11 +00:00
eitherBlogpost <- liftIO $ Pandoc.runIO $ do
title <- inlineToText $ docTitle meta
date <- inlineToText $ docDate meta
authors <- mapM inlineToText $ docAuthors meta
2020-06-23 07:15:03 +00:00
return $ BlogPost title date authors path toc pandoc
2020-06-22 09:44:11 +00:00
case eitherBlogpost of
Left _ -> fail "BAD"
Right bp -> return bp
sortByPostDate :: [BlogPost] -> [BlogPost]
sortByPostDate =
2020-06-23 17:28:41 +00:00
sortBy (\a b-> compare (Down (postDate a)) (Down (postDate b)))
2020-06-22 07:39:44 +00:00
2020-06-22 10:27:45 +00:00
2020-06-22 20:13:12 +00:00
build :: FilePath -> FilePath
2020-06-22 10:27:45 +00:00
build = (</>) siteDir
2020-06-23 17:28:41 +00:00
genAllDeps :: [FilePattern] -> Action [FilePath]
genAllDeps patterns = do
allMatchedFiles <- getDirectoryFiles srcDir patterns
allMatchedFiles &
filter ((/= "html") . takeExtension) &
filter (null . takeExtension) &
map (siteDir </>) &
return
2020-06-14 11:19:13 +00:00
buildRules :: Rules ()
2020-05-25 20:28:06 +00:00
buildRules = do
2020-06-23 17:28:41 +00:00
cleanRule
-- build "//*" %> copy
allRule
getPost <- mkGetPost
getPosts <- mkGetPosts getPost
getTemplate <- mkGetTemplate
-- build "articles.html" %> \out -> do
-- css <- genAllDeps ["//*.css"]
-- posts <- getPosts ()
-- need $ css <> map postUrl (sortByPostDate posts)
-- let titles = toS $ T.intercalate "\n" $ map postTitle posts
-- writeFile' out titles
2020-06-23 21:58:09 +00:00
build "//*" %> \out -> do
let asset = dropDirectory1 out
case (takeExtension asset) of
".html" -> genHtmlAction getPost getTemplate out
".txt" -> do
txtExists <- doesFileExist (srcDir </> asset)
if txtExists
then copyFileChanged (srcDir </> asset) out
else genAsciiAction getPost out
".jpg" -> compressImage asset
".jpeg" -> compressImage asset
".gif" -> compressImage asset
".png" -> compressImage asset
_ -> copyFileChanged (srcDir </> asset) out
2020-06-23 17:28:41 +00:00
-- build "//*.org" %> copy
-- build "//*.jpg" %> copy
copy :: FilePath -> Action ()
copy out = do
let src = srcDir </> (dropDirectory1 out)
copyFileChanged src out
genHtml :: (MonadIO m, MonadFail m) => BlogPost -> m Text
genHtml bp = do
eitherHtml <- liftIO $
Pandoc.runIO $
Writers.writeHtml5String
(def { writerTableOfContents = (postToc bp)
, writerEmailObfuscation = ReferenceObfuscation
})
(postBody bp)
case eitherHtml of
Left _ -> fail "BAD"
Right innerHtml -> return innerHtml
genHtmlAction
:: (FilePath -> Action BlogPost)
-> (FilePath -> Action Template) -> [Char] -> Action ()
genHtmlAction getPost getTemplate out = do
2020-06-23 21:58:09 +00:00
let isPost = takeDirectory1 (dropDirectory1 out) == "post"
template <- getTemplate ("templates" </> if isPost then "post.mustache" else "main.mustache")
2020-06-23 17:28:41 +00:00
let srcFile = srcDir </> (dropDirectory1 (out -<.> "org"))
liftIO $ putText $ "need: " <> (toS srcFile) <> " -> " <> (toS out)
need [srcFile]
bp <- getPost srcFile
innerHtml <- genHtml bp
let htmlContent =
2020-06-23 21:58:09 +00:00
renderMustache template
$ object [ "title" .= postTitle bp
, "authors" .= postAuthors bp
, "date" .= postDate bp
, "body" .= innerHtml
]
2020-06-23 17:28:41 +00:00
writeFile' out (toS htmlContent)
2020-06-23 21:58:09 +00:00
genAscii :: (MonadIO m, MonadFail m) => BlogPost -> m Text
genAscii bp = do
eitherAscii <- liftIO $ Pandoc.runIO $ Writers.writePlain def (postBody bp)
case eitherAscii of
Left _ -> fail "BAD"
Right innerAscii -> return innerAscii
genAsciiAction
:: (FilePath -> Action BlogPost)
-> [Char] -> Action ()
genAsciiAction getPost out = do
let srcFile = srcDir </> (dropDirectory1 (out -<.> "org"))
need [srcFile]
bp <- getPost srcFile
innerAscii <- genAscii bp
let preamble = postTitle bp <> "\n"
<> postDate bp <> "\n\n"
writeFile' out (toS (preamble <> toS innerAscii))
2020-06-23 17:28:41 +00:00
allHtmlAction :: Action ()
allHtmlAction = do
allOrgFiles <- getDirectoryFiles srcDir ["//*.org"]
let allHtmlFiles = map (-<.> "html") allOrgFiles
2020-06-23 21:58:09 +00:00
need (map build allHtmlFiles)
allAsciiAction :: Action ()
allAsciiAction = do
allOrgFiles <- getDirectoryFiles srcDir ["//*.org"]
let allAsciiFiles = map (-<.> "txt") allOrgFiles
need (map build allAsciiFiles)
2020-06-23 17:28:41 +00:00
compressImage :: CmdResult b => FilePath -> Action b
compressImage img = do
let src = srcDir </> img
dst = siteDir </> img
need [src]
let dir = takeDirectory dst
dirExists <- doesDirectoryExist dir
when (not dirExists) $
command [] "mkdir" ["-p", dir]
command [] "convert" [src
, "-strip"
, "-resize","320x320>"
, "-interlace","Plane"
, "-quality","85"
, "-define","filter:blur=0.75"
, "-filter","Gaussian"
, "-ordered-dither","o4x4,4"
, dst ]
2020-06-22 10:27:45 +00:00
allRule :: Rules ()
allRule =
2020-06-22 20:13:12 +00:00
phony "all" $ do
2020-06-23 17:28:41 +00:00
allAssets <- filter (/= ".DS_Store") <$> getDirectoryFiles srcDir ["//*.*"]
2020-06-23 21:58:09 +00:00
need (map build allAssets)
-- forM_ allAssets $ \asset ->
-- case (takeExtension asset) of
-- ".jpg" -> compressImage asset
-- ".jpeg" -> compressImage asset
-- ".gif" -> compressImage asset
-- ".png" -> compressImage asset
-- _ -> copyFileChanged (srcDir </> asset) (siteDir </> asset)
2020-06-23 17:28:41 +00:00
allHtmlAction
2020-06-23 21:58:09 +00:00
allAsciiAction
2020-06-22 10:27:45 +00:00
2020-06-22 10:08:07 +00:00
cleanRule :: Rules ()
cleanRule =
phony "clean" $ do
putInfo "Cleaning files in _site and _optim"
forM_ [siteDir,optimDir] $ flip removeFilesAfter ["//*"]
2020-06-22 21:01:47 +00:00
mkGetTemplate :: Rules (FilePath -> Action Template)
mkGetTemplate = newCache $ \path -> do
fileContent <- readFile' path
let res = compileMustacheText "page" (toS fileContent)
case res of
Left _ -> fail "BAD"
Right template -> return template
2020-06-23 07:15:03 +00:00
tocRequested :: Text -> Bool
tocRequested fc =
let toc = fc & T.lines
& map T.toLower
& filter (T.isPrefixOf (T.pack "#+options: "))
& head
& fmap (filter (T.isPrefixOf (T.pack "toc:")) . T.words)
in toc == Just ["toc:t"]
2020-06-23 06:44:02 +00:00
2020-06-22 20:13:12 +00:00
mkGetPost :: Rules (FilePath -> Action BlogPost)
2020-06-22 10:08:07 +00:00
mkGetPost = newCache $ \path -> do
fileContent <- readFile' path
2020-06-23 07:15:03 +00:00
let toc = tocRequested (toS fileContent)
2020-06-23 17:28:41 +00:00
eitherResult <- liftIO $ Pandoc.runIO $ Readers.readOrg (def { readerStandalone = True }) (toS fileContent)
2020-06-22 10:08:07 +00:00
case eitherResult of
Left _ -> fail "BAD"
2020-06-23 07:15:03 +00:00
Right pandoc -> getBlogpostFromMetas path toc pandoc
2020-06-14 11:19:13 +00:00
2020-06-22 20:13:12 +00:00
mkGetPosts :: (FilePath -> Action b) -> Rules (() -> Action [b])
2020-06-22 10:08:07 +00:00
mkGetPosts getPost =
newCache $ \() -> mapM getPost =<< getDirectoryFiles "" ["src/posts//*.org"]