basic stuff is done

This commit is contained in:
Pagwin 2024-10-02 18:18:10 -04:00
parent db24be34dd
commit 93bfd31abf
No known key found for this signature in database
GPG key ID: 81137023740CA260
2 changed files with 123 additions and 108 deletions

View file

@ -7,49 +7,50 @@
module Main where module Main where
import Config
import Control.Monad (forM, when) import Control.Monad (forM, when)
import Data.List (sortOn)
import Development.Shake (Action, Rules, (|%>), (~>), (%>))
import Development.Shake.FilePath ((</>))
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import Data.List (sortOn)
import qualified Data.Ord as Ord import qualified Data.Ord as Ord
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time
import Development.Shake (Action, Rules, (%>), (|%>), (~>))
import qualified Development.Shake as Shake import qualified Development.Shake as Shake
import Development.Shake.FilePath ((</>))
import qualified Development.Shake.FilePath as FP import qualified Development.Shake.FilePath as FP
import Config import Templates
import Types import Types
import Utilities import Utilities
import Templates
import Data.Time
-- target = thing we want -- target = thing we want
-- Rule = pattern of thing being made + actions to produce the thing -- Rule = pattern of thing being made + actions to produce the thing
-- Action = actions to produce a thing -- Action = actions to produce a thing
main :: IO () main :: IO ()
main = do main = do
Shake.shakeArgs Shake.shakeOptions $ do Shake.shakeArgs Shake.shakeOptions $ do
Shake.withTargetDocs "Build the site" $ Shake.withTargetDocs "Build the site" $
"build" ~> buildSite "build" ~> buildSite
Shake.withTargetDocs "Clean the built site" $ Shake.withTargetDocs "Clean the built site" $
"clean" ~> Shake.removeFilesAfter outputDir ["//*"] "clean" ~> Shake.removeFilesAfter outputDir ["//*"]
Shake.withoutTargets buildRules Shake.withoutTargets buildRules
buildSite :: Action () buildSite :: Action ()
buildSite = do buildSite = do
-- static files -- static files
assetPaths <- Shake.getDirectoryFiles "" assetGlobs assetPaths <- Shake.getDirectoryFiles "" assetGlobs
-- path concat each asset path so it's output into the outputDir -- path concat each asset path so it's output into the outputDir
Shake.need $ map (outputDir </>) assetPaths Shake.need $ map (outputDir </>) assetPaths
-- take the misc pages which aren't blog posts and make their html files
Shake.need $ map indexHtmlOutputPath pagePaths
-- handle posts -- take the misc pages which aren't blog posts and make their html files
postPaths <- Shake.getDirectoryFiles "" postGlobs Shake.need $ map indexHtmlOutputPath pagePaths
Shake.need $ map indexHtmlOutputPath postPaths
-- remaining pages, index.xml = rss feed -- handle posts
Shake.need $ map (outputDir </>) ["index.html", "index.xml"] postPaths <- Shake.getDirectoryFiles "" postGlobs
Shake.need $ map indexHtmlOutputPath postPaths
-- remaining pages, index.xml = rss feed
Shake.need $ map (outputDir </>) ["index.html", "index.xml"]
buildRules :: Rules () buildRules :: Rules ()
buildRules = do buildRules = do
@ -61,36 +62,43 @@ buildRules = do
-- make a rule of the pattern outputDir/asset_name which copes from outputDir/../pages -- make a rule of the pattern outputDir/asset_name which copes from outputDir/../pages
assets :: Rules () assets :: Rules ()
assets = map (outputDir </>) assetGlobs |%> \target -> do assets =
let src = FP.dropDirectory1 target map (outputDir </>) assetGlobs |%> \target -> do
Shake.copyFileChanged src target let src = FP.dropDirectory1 target
Shake.putInfo $ "Copied " <> target <> " from " <> src Shake.copyFileChanged src target
Shake.putInfo $ "Copied " <> target <> " from " <> src
-- handling typst only because pages should only be typst no reason for backwards compat on that
pages :: Rules () pages :: Rules ()
pages = map indexHtmlOutputPath pagePaths |%> \target -> do pages =
let src = indexHtmlTypstSourcePath target map indexHtmlOutputPath pagePaths |%> \target -> do
let metaSrc = indexHtmlTypstMetaPath target let src = indexHtmlTypstSourcePath target
html <- typstToHtml src let metaSrc = indexHtmlTypstMetaPath target
meta <- yamlToPost metaSrc html <- typstToHtml src
let page = Page (postTitle meta) html meta <- yamlToPost metaSrc
applyTemplateAndWrite "default.html" page target let page = Page (postTitle meta) html
Shake.putInfo $ "Built " <> target <> " from " <> src applyTemplateAndWrite "default.html" page target
Shake.putInfo $ "Built " <> target <> " from " <> src
-- there's probably a better way of doing this that allows for the target's origin file extension to get passed in but for now we're doing brute force -- there's probably a better way of doing this that allows for the target's origin file extension to get passed in but for now we're doing brute force
postsRule :: Rules () postsRule :: Rules ()
postsRule = map indexHtmlOutputPath postGlobs |%> \target -> do postsRule =
map indexHtmlOutputPath postGlobs |%> \target -> do
let potentials = indexHtmlSourcePaths target let potentials = indexHtmlSourcePaths target
Shake.forP potentials (\path->do Shake.forP
exists <- Shake.doesFileExist path potentials
when exists ( \path -> do
(case FP.takeExtension path of exists <- Shake.doesFileExist path
when
exists
( case FP.takeExtension path of
".typ" -> typstPost path ".typ" -> typstPost path
".md" -> markdownPost path ".md" -> markdownPost path
_ -> error $ "invalid file extension for post " <> target) _ -> error $ "invalid file extension for post " <> target
) )
)
return () return ()
typstPost :: FP.FilePath -> Action () typstPost :: FP.FilePath -> Action ()
typstPost src = do typstPost src = do
Shake.need [src] Shake.need [src]
@ -103,7 +111,6 @@ typstPost src = do
applyTemplateAndWrite "default.html" page target applyTemplateAndWrite "default.html" page target
Shake.putInfo $ "Built " <> target <> " from " <> src Shake.putInfo $ "Built " <> target <> " from " <> src
markdownPost :: FP.FilePath -> Action () markdownPost :: FP.FilePath -> Action ()
markdownPost src = do markdownPost src = do
Shake.need [src] Shake.need [src]
@ -117,49 +124,52 @@ markdownPost src = do
Shake.putInfo $ "Built " <> target <> " from " <> src Shake.putInfo $ "Built " <> target <> " from " <> src
home :: Rules () home :: Rules ()
home = outputDir </> "index.html" %> \target -> do home =
postPaths <- Shake.getDirectoryFiles "" postGlobs outputDir </> "index.html" %> \target -> do
posts <- take 3 postPaths <- Shake.getDirectoryFiles "" postGlobs
. sortOn (Ord.Down . postDate) posts <-
<$> forM postPaths readPost take 3
html <- applyTemplate "home.html" $ HM.singleton "posts" posts . sortOn (Ord.Down . postDate)
<$> forM postPaths readPost
html <- applyTemplate "home.html" $ HM.singleton "posts" posts
let page = Page (T.pack "Home") html let page = Page (T.pack "Home") html
applyTemplateAndWrite "default.html" page target applyTemplateAndWrite "default.html" page target
Shake.putInfo $ "Built " <> target Shake.putInfo $ "Built " <> target
rss :: Rules () rss :: Rules ()
rss = outputDir </> "index.xml" %> \target -> do rss =
outputDir </> "index.xml" %> \target -> do
postPaths <- Shake.getDirectoryFiles "" postGlobs postPaths <- Shake.getDirectoryFiles "" postGlobs
posts <- sortOn (Ord.Down . postDate) <$> forM postPaths readPost posts <- sortOn (Ord.Down . postDate) <$> forM postPaths readPost
applyTemplateAndWrite "feed.xml" (HM.singleton "posts" posts) target applyTemplateAndWrite "feed.xml" (HM.singleton "posts" posts) target
Shake.putInfo $ "Built " <> target Shake.putInfo $ "Built " <> target
readPost :: FilePath -> Action Post readPost :: FilePath -> Action Post
readPost postPath = do readPost postPath = do
case FP.takeExtension postPath of case FP.takeExtension postPath of
".typ" -> readTypstPost postPath ".typ" -> readTypstPost postPath
".md" -> readMarkdownPost postPath ".md" -> readMarkdownPost postPath
_ -> error $ "unknown file extension for file" <> postPath _ -> error $ "unknown file extension for file" <> postPath
readTypstPost :: FilePath -> Action Post readTypstPost :: FilePath -> Action Post
readTypstPost postPath = do readTypstPost postPath = do
html <- typstToHtml postPath html <- typstToHtml postPath
post <- yamlToPost $ typstMetaPath postPath post <- yamlToPost $ typstMetaPath postPath
Shake.putInfo $ "Read " <> postPath Shake.putInfo $ "Read " <> postPath
return $ post return $
{ post
postContent = Just html, { postContent = Just html,
postLink = Just . T.pack $ "/" <> FP.dropExtension postPath <> "/" postLink = Just . T.pack $ "/" <> FP.dropExtension postPath <> "/"
} }
readMarkdownPost :: FilePath -> Action Post readMarkdownPost :: FilePath -> Action Post
readMarkdownPost postPath = do readMarkdownPost postPath = do
(post, html) <- markdownToHtml postPath (post, html) <- markdownToHtml postPath
Shake.putInfo $ "Read " <> postPath Shake.putInfo $ "Read " <> postPath
return $ post return $
{ post
postContent = Just html, { postContent = Just html,
postLink = Just . T.pack $ "/" <> FP.dropExtension postPath <> "/" postLink = Just . T.pack $ "/" <> FP.dropExtension postPath <> "/"
} }

View file

@ -1,41 +1,40 @@
module Utilities where module Utilities where
import Data.Text (Text)
import Development.Shake.FilePath ((<.>), (</>))
import qualified Data.Text as T
import Data.Yaml.Aeson
import qualified Development.Shake as Shake
import qualified Development.Shake.FilePath as Shake
import qualified Text.Pandoc as Pandoc
import Config import Config
import Development.Shake (Action) import Data.Aeson (Result (Error, Success))
import Types
import Data.Maybe (fromMaybe)
import Data.Time
import Text.Pandoc (Block (Plain), Meta (..), MetaValue (..), Pandoc (..))
import Data.Aeson (Result(Success, Error))
import qualified Data.Aeson as A import qualified Data.Aeson as A
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time
import Data.Yaml.Aeson
import Development.Shake (Action)
import qualified Development.Shake as Shake
import Development.Shake.FilePath ((<.>), (</>))
import qualified Development.Shake.FilePath as Shake
import Text.Pandoc (Block (Plain), Meta (..), MetaValue (..), Pandoc (..))
import qualified Text.Pandoc as Pandoc
import Types
indexHtmlOutputPath :: FilePath -> FilePath indexHtmlOutputPath :: FilePath -> FilePath
indexHtmlOutputPath srcPath = indexHtmlOutputPath srcPath =
outputDir </> Shake.dropExtension srcPath </> "index.html" outputDir </> Shake.dropExtension srcPath </> "index.html"
-- were applicative shenanigans necessary? no -- were applicative shenanigans necessary? no
-- but using them felt cool -- but using them felt cool
indexHtmlSourcePaths :: FilePath -> [FilePath] indexHtmlSourcePaths :: FilePath -> [FilePath]
indexHtmlSourcePaths path = [indexHtmlTypstSourcePath, indexHtmlMarkdownSourcePath] <*> [path] indexHtmlSourcePaths path = [indexHtmlTypstSourcePath, indexHtmlMarkdownSourcePath] <*> [path]
indexHtmlTypstSourcePath :: FilePath -> FilePath indexHtmlTypstSourcePath :: FilePath -> FilePath
indexHtmlTypstSourcePath = indexHtmlTypstSourcePath =
Shake.dropDirectory1 Shake.dropDirectory1
. (<.> "typ") . (<.> "typ")
. Shake.dropTrailingPathSeparator . Shake.dropTrailingPathSeparator
. Shake.dropFileName . Shake.dropFileName
indexHtmlMarkdownSourcePath :: FilePath -> FilePath indexHtmlMarkdownSourcePath :: FilePath -> FilePath
indexHtmlMarkdownSourcePath = indexHtmlMarkdownSourcePath =
Shake.dropDirectory1 Shake.dropDirectory1
. (<.> "md") . (<.> "md")
. Shake.dropTrailingPathSeparator . Shake.dropTrailingPathSeparator
. Shake.dropFileName . Shake.dropFileName
@ -46,7 +45,6 @@ indexHtmlTypstMetaPath = typstMetaPath . indexHtmlTypstSourcePath
typstMetaPath :: FilePath -> FilePath typstMetaPath :: FilePath -> FilePath
typstMetaPath typstPath = Shake.dropExtension typstPath <.> "yaml" typstMetaPath typstPath = Shake.dropExtension typstPath <.> "yaml"
typstToHtml :: FilePath -> Action Text typstToHtml :: FilePath -> Action Text
typstToHtml filePath = do typstToHtml filePath = do
content <- Shake.readFile' filePath content <- Shake.readFile' filePath
@ -59,18 +57,24 @@ typstToHtml filePath = do
Pandoc.def {Pandoc.readerExtensions = Pandoc.pandocExtensions} Pandoc.def {Pandoc.readerExtensions = Pandoc.pandocExtensions}
writerOptions = writerOptions =
Pandoc.def {Pandoc.writerExtensions = Pandoc.pandocExtensions} Pandoc.def {Pandoc.writerExtensions = Pandoc.pandocExtensions}
markdownToHtml :: FromJSON a => FilePath -> Action (a, Text)
markdownToHtml :: (FromJSON a) => FilePath -> Action (a, Text)
markdownToHtml filePath = do markdownToHtml filePath = do
content <- Shake.readFile' filePath content <- Shake.readFile' filePath
Shake.quietly . Shake.traced "Markdown to HTML" $ do Shake.quietly . Shake.traced "Markdown to HTML" $ do
pandoc@(Pandoc meta _) <- pandoc@(Pandoc meta _) <-
runPandoc . Pandoc.readMarkdown readerOptions . T.pack $ content runPandoc . Pandoc.readMarkdown readerOptions . T.pack $ content
-- WARNING markdown needs to have no whitespace before/after dashes
-- print meta
meta' <- fromMeta meta meta' <- fromMeta meta
html <- runPandoc . Pandoc.writeHtml5String writerOptions $ pandoc html <- runPandoc . Pandoc.writeHtml5String writerOptions $ pandoc
return (meta', html) return (meta', html)
where where
readerOptions = readerOptions =
Pandoc.def {Pandoc.readerExtensions = Pandoc.pandocExtensions} Pandoc.def
{ Pandoc.readerStandalone = True,
Pandoc.readerExtensions = Pandoc.enableExtension Pandoc.Ext_yaml_metadata_block Pandoc.pandocExtensions
}
writerOptions = writerOptions =
Pandoc.def {Pandoc.writerExtensions = Pandoc.pandocExtensions} Pandoc.def {Pandoc.writerExtensions = Pandoc.pandocExtensions}
fromMeta (Meta meta) = fromMeta (Meta meta) =
@ -91,24 +95,25 @@ markdownToHtml filePath = do
runPandoc :: Pandoc.PandocIO b -> IO b runPandoc :: Pandoc.PandocIO b -> IO b
runPandoc action = runPandoc action =
Pandoc.runIO (Pandoc.setVerbosity Pandoc.ERROR >> action) Pandoc.runIO (Pandoc.setVerbosity Pandoc.ERROR >> action)
>>= either (fail . show) return >>= either (fail . show) return
yamlToPost :: FilePath -> Action Post yamlToPost :: FilePath -> Action Post
yamlToPost path = do yamlToPost path = do
post <- decodeFileThrow path post <- decodeFileThrow path
let post' = dateTransform post let post' = dateTransform post
return $ fromMaybe post post' return $ fromMaybe post post'
where where
dateTransform post@(Post{postDate}) = do dateTransform post@(Post {postDate}) = do
postDate' <- postDate postDate' <- postDate
let postDate'' = dateStrTransform $ T.unpack postDate' let postDate'' = dateStrTransform $ T.unpack postDate'
Just post { Just
postDate = postDate'' post
} { postDate = postDate''
dateStrTransform date = do }
date' <- parseTimeM False defaultTimeLocale "%Y-%-m-%-d" date dateStrTransform date = do
Just $ T.pack $ formatTime @UTCTime defaultTimeLocale "%b %e, %Y" date' date' <- parseTimeM False defaultTimeLocale "%Y-%-m-%-d" date
Just $ T.pack $ formatTime @UTCTime defaultTimeLocale "%b %e, %Y" date'
isTypstPost :: FilePath -> Bool isTypstPost :: FilePath -> Bool
isTypstPost path = Shake.takeExtension path == ".typ" isTypstPost path = Shake.takeExtension path == ".typ"