Clarify IO usage via Applicative interface in Handlers, whitespace cleanup, remove dedicated source file for 404 view
This commit is contained in:
		@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
  ]  
 | 
					  ]
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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")
 | 
					 | 
				
			||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user