Clarify IO usage via Applicative interface in Handlers, whitespace cleanup, remove dedicated source file for 404 view

This commit is contained in:
James Eversole 2024-07-24 14:37:10 -05:00
parent bf47f02282
commit 754302e543
10 changed files with 50 additions and 55 deletions

View File

@ -1,6 +1,6 @@
# the sampu Haskell blog engine # the sampu Haskell blog engine
https://eversole.co (not live yet!) https://eversole.co
a _work-in-progress_ blog engine using simple flat-file Markdown content storage a _work-in-progress_ blog engine using simple flat-file Markdown content storage

View File

@ -1,6 +1,6 @@
cabal-version: 3.0 cabal-version: 3.0
name: sampu name: sampu
version: 0.9.1 version: 0.9.2
license: ISC license: ISC
author: James Eversole author: James Eversole
maintainer: james@eversole.co maintainer: james@eversole.co
@ -38,6 +38,5 @@ executable sampu
Core.HTTP Core.HTTP
Core.Rendering Core.Rendering
Fragments.Base Fragments.Base
Fragments.NotFound
Fragments.Styles Fragments.Styles
default-language: GHC2021 default-language: GHC2021

View File

@ -25,7 +25,7 @@ renderFeed = fromJust . Export.textFeedWith def{rsPretty = True} . AtomFeed
-- Convert a Post to an Atom Entry -- Convert a Post to an Atom Entry
toEntry :: Post -> Atom.Entry toEntry :: Post -> Atom.Entry
toEntry (Post date url title content) = (Atom.nullEntry url (Atom.TextString title) date) toEntry (Post date url title content) = (Atom.nullEntry url (Atom.TextString title) date)
{ Atom.entryLinks = [Atom.nullLink url] { Atom.entryLinks = [Atom.nullLink url]
, Atom.entryContent = Just (Atom.HTMLContent content) , Atom.entryContent = Just (Atom.HTMLContent content)
} }

View File

@ -17,10 +17,10 @@ import Web.Twain
main :: IO () main :: IO ()
main = do main = do
port <- Conf.port port <- Conf.port
let app = preProcessors let app = preProcessors
++ routes ++ routes
++ postProcessors ++ postProcessors
run (read port) $ run (read port) $
foldr ($) (notFound Handle.missing) app foldr ($) (notFound Handle.missing) app
-- These Middlewares are executed before any routes are reached. -- These Middlewares are executed before any routes are reached.
@ -36,11 +36,11 @@ postProcessors = []
-- Core routes expressed as a list of WAI Middlewares. -- Core routes expressed as a list of WAI Middlewares.
routes :: [Middleware] routes :: [Middleware]
routes = routes =
[ get "/" Handle.index [ get "/" Handle.index
, get "/style.css" Handle.theme , get "/style.css" Handle.theme
, get "/posts" Handle.postsIndex , get "/posts" Handle.postsIndex
, get "/posts/:name" Handle.posts , get "/posts/:name" Handle.posts
, get "/contact" Handle.contact , get "/contact" Handle.contact
, get "/atom.xml" Handle.feed , get "/atom.xml" Handle.feed
, get "/feed" Handle.feed , get "/feed" Handle.feed
] ]

View File

@ -2,9 +2,8 @@ module Core.Handlers where
import qualified Core.Configuration as Conf import qualified Core.Configuration as Conf
import Core.Feed (Post(..), autoFeed, renderFeed) import Core.Feed (Post(..), autoFeed, renderFeed)
import Core.Rendering import Core.Rendering
import Fragments.Base import Fragments.Base
import Fragments.NotFound
import Fragments.Styles as S import Fragments.Styles as S
import qualified Text.Atom.Feed as Atom import qualified Text.Atom.Feed as Atom
@ -22,28 +21,27 @@ import Web.Twain hiding (fileName)
-- A ResponoderM capable of lifting to IO monad; constructs response to clients -- A ResponoderM capable of lifting to IO monad; constructs response to clients
index :: ResponderM a index :: ResponderM a
index = do index = do
-- Query the system environment for the BLOGTITLE environment variable (title, homeMd, footerMd) <- liftIO $ (,,)
title <- liftIO Conf.title <$> Conf.title
-- Read a Commonmark Markdown file and process it to HTML <*> mdFileToLucid "./data/posts/home.md"
homeMd <- liftIO $ mdFileToLucid "./data/posts/home.md" <*> mdFileToLucid "./data/posts/footer.md"
-- Get the Footer content from Markdown
footerMd <- liftIO $ mdFileToLucid "./data/posts/footer.md"
-- Respond to request with fragments compositionally to create a home page -- Respond to request with fragments compositionally to create a home page
sendLucidFragment $ basePage title (baseHome homeMd) footerMd sendLucidFragment $ basePage title (baseHome homeMd) footerMd
-- Responds with processed Commonmark -> HTML for posts -- Responds with processed Commonmark -> HTML for posts
posts :: ResponderM a posts :: ResponderM a
posts = do posts = do
postName <- param "name" postName <- param "name"
postValid <- liftIO $ postExists postName postValid <- liftIO $ postExists postName
case postValid of case postValid of
False -> missing False -> missing
True -> do True -> do
title <- liftIO Conf.title (title, footerMd, postMd) <- liftIO $ (,,)
footerMd <- liftIO $ mdFileToLucid "./data/posts/footer.md" <$> Conf.title
postMd <- liftIO $ mdFileToLucid $ postPath postName <*> mdFileToLucid "./data/posts/footer.md"
sendLucidFragment $ basePage title (basePost postMd) footerMd <*> (mdFileToLucid $ postPath postName)
sendLucidFragment $ basePage title (basePost postMd) footerMd
where where
postExists :: T.Text -> IO Bool postExists :: T.Text -> IO Bool
postExists postName = doesFileExist $ postPath postName postExists postName = doesFileExist $ postPath postName
@ -54,22 +52,23 @@ posts = do
-- Builds an index of all posts -- Builds an index of all posts
postsIndex :: ResponderM a postsIndex :: ResponderM a
postsIndex = do postsIndex = do
postNames <- liftIO mdPostNames (postNames, title, footerMd) <- liftIO $ (,,)
title <- liftIO Conf.title <$> mdPostNames
footerMd <- liftIO $ mdFileToLucid "./data/posts/footer.md" <*> Conf.title
sendLucidFragment $ basePage title (postIndex postNames) footerMd <*> mdFileToLucid "./data/posts/footer.md"
sendLucidFragment $ basePage title (postIndex postNames) footerMd
-- Generates the XML feed at /feed -- Generates the XML feed at /feed
feed :: ResponderM a feed :: ResponderM a
feed = do feed = do
postNames <- liftIO mdPostNames (postNames, title, baseUrl, time) <- liftIO $ (,,,)
title <- liftIO Conf.title <$> mdPostNames
baseUrl <- liftIO Conf.baseUrl <*> Conf.title
time <- liftIO $ fmap (\x -> timeFormat x) getCurrentTime <*> Conf.baseUrl
-- Create Atom [Post] to populate the feed <*> fmap (\x -> timeFormat x) getCurrentTime
feedData <- liftIO $ mapM (makePost baseUrl) postNames feedData <- liftIO $ mapM (makePost baseUrl) postNames
-- Send an XML response with an automatically populated Atom feed -- Send an XML response with an automatically populated Atom feed
send $ xml $ TLE.encodeUtf8 $ renderFeed send $ xml $ TLE.encodeUtf8 $ renderFeed
$ autoFeed (baseFeed title time baseUrl) feedData $ autoFeed (baseFeed title time baseUrl) feedData
where where
-- Base feed data structure which we populate with entries -- Base feed data structure which we populate with entries
@ -81,12 +80,12 @@ feed = do
-- Create an Atom Post for each markdown post present -- Create an Atom Post for each markdown post present
makePost :: String -> FilePath -> IO (Post) makePost :: String -> FilePath -> IO (Post)
makePost baseUrl postName = do makePost baseUrl postName = do
date <- getModificationTime $ "./data/posts/" ++ postName ++ ".md" date <- getModificationTime $ "./data/posts/" ++ postName ++ ".md"
postContent <- mdFileToText $ "./data/posts/" ++ postName ++ ".md" postContent <- mdFileToText $ "./data/posts/" ++ postName ++ ".md"
return $ Post return $ Post
(T.pack $ (timeFormat date) ++ " UTC") (T.pack $ (timeFormat date) ++ " UTC")
(T.pack $ baseUrl ++ "/posts/" ++ postName) (T.pack $ baseUrl ++ "/posts/" ++ postName)
(T.pack $ show postName) (T.pack $ show postName)
(postContent) (postContent)
@ -97,16 +96,17 @@ feed = do
-- Refer to index comments -- Refer to index comments
contact :: ResponderM a contact :: ResponderM a
contact = do contact = do
title <- liftIO Conf.title (title, contactMd, footerMd) <- liftIO $ (,,)
contactMd <- liftIO $ mdFileToLucid "./data/posts/contact.md" <$> Conf.title
footerMd <- liftIO $ mdFileToLucid "./data/posts/footer.md" <*> mdFileToLucid "./data/posts/contact.md"
<*> mdFileToLucid "./data/posts/footer.md"
sendLucidFragment $ basePage title (baseContact contactMd) footerMd sendLucidFragment $ basePage title (baseContact contactMd) footerMd
-- Respond with primary processed CSS -- Respond with primary processed CSS
theme :: ResponderM a theme :: ResponderM a
theme = send $ css $ TLE.encodeUtf8 $ S.cssRender S.composedStyles theme = send $ css $ TLE.encodeUtf8 $ S.cssRender S.composedStyles
-- Helper function for responding in ResponderM from Html -- Helper function for responding in ResponderM from Html
sendLucidFragment :: Html () -> ResponderM a sendLucidFragment :: Html () -> ResponderM a
sendLucidFragment x = send $ html $ lucidToTwain x sendLucidFragment x = send $ html $ lucidToTwain x

View File

@ -21,6 +21,6 @@ mdFileToLucid :: FilePath -> IO (LU.Html ())
mdFileToLucid path = fmap (mdToLucid . decodeUtf8) (B.readFile path) mdFileToLucid path = fmap (mdToLucid . decodeUtf8) (B.readFile path)
mdFileToText :: FilePath -> IO (Text) mdFileToText :: FilePath -> IO (Text)
mdFileToText path = do mdFileToText path = do
htmlContent <- mdFileToLucid path htmlContent <- mdFileToLucid path
return $ toStrict $ LU.renderText htmlContent return $ toStrict $ LU.renderText htmlContent

View File

@ -44,5 +44,10 @@ postIndex postNames = div_ [class_ "postList"] $ do
(\x -> li_ $ a_ [href_ (pack $ "/posts/" ++ x)] (fromString x)) (\x -> li_ $ a_ [href_ (pack $ "/posts/" ++ x)] (fromString x))
postNames postNames
pageNotFound :: Html ()
pageNotFound = baseDoc "404" baseNav <>
(div_ [class_ "notFound"] $ h1_ "404 NOT FOUND")
none :: Text none :: Text
none = mempty none = mempty

View File

@ -1,9 +0,0 @@
module Fragments.NotFound where
import Fragments.Base
import Lucid
pageNotFound :: Html ()
pageNotFound = baseDoc "404" baseNav <>
(div_ [class_ "notFound"] $ h1_ "404 NOT FOUND")

View File

@ -61,7 +61,7 @@ footer_ = do
margin auto (em 0) (em 0) (em 0) margin auto (em 0) (em 0) (em 0)
width $ pct 100 width $ pct 100
backgroundColor terColor backgroundColor terColor
textAlign center textAlign center
padding (em 1) (em 0) (em 1) (em 0) padding (em 1) (em 0) (em 1) (em 0)
boxSizing borderBox boxSizing borderBox
p ? do p ? do
@ -92,7 +92,7 @@ notFound_ :: Css
notFound_ = do notFound_ = do
".notFound" ? do ".notFound" ? do
margin (em 0) auto (em 0) auto margin (em 0) auto (em 0) auto
textAlign center textAlign center
h1 ? do h1 ? do
fontSize $ pct 500 fontSize $ pct 500
fontWeight $ weight 200 fontWeight $ weight 200

View File

@ -5,5 +5,5 @@ import qualified Core.Configuration as Conf
main :: IO () main :: IO ()
main = do main = do
Conf.main Conf.main
HTTP.main HTTP.main