Skip to content

Commit

Permalink
Flatten the main function
Browse files Browse the repository at this point in the history
This should make it a bit easier to read while hopefully making it
easier to change in the future.

Use pattern matching to see if the user wants to the see the usage
instructions, this allows us to make use of one `case` expression
instead of a `case` expression and an `if` expression.

Do configuration reification pointfree style while using lenses.
  • Loading branch information
Rembane committed Sep 27, 2023
1 parent ddb9040 commit 7351c1a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 41 deletions.
81 changes: 40 additions & 41 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import Control.Monad.Log ( defaultBatchingOptio
import Control.Monad.Reader ( runReaderT )
import Control.Monad.Trans ( liftIO )
import Data.FileEmbed ( embedDir )
import Data.Foldable ( traverse_ )
import Data.IORef ( IORef
, readIORef
)
import Data.Time.Format ( defaultTimeLocale
, formatTime
, iso8601DateFormat
)
import Lens.Micro.Platform ( (<&>)
, set
import Lens.Micro.Platform ( set
, view
)
import Network.HTTP.Client.TLS ( newTlsManager )
Expand Down Expand Up @@ -64,53 +64,52 @@ opts =
"Update interval"
]

usage :: IO ()
usage = putStrLn $ usageInfo "mat-chalmers [OPTION...]" opts

main :: IO ()
main =
getArgs
<&> getOpt Permute opts
>>= \case
(_ , _ , _ : _) -> usage
(_ , _ : _, _ ) -> usage
(!confs, _ , _ ) -> do
let config = foldl (flip id) defaultConfig confs
if view cHelp config
then usage
else do
upd <- newMVar () -- putMVar when to update
mgr <- newTlsManager
(viewRef, refreshAction) <- runLoggingT
(runReaderT refresh (ClientContext config mgr))
print
(reifyConfig . getOpt Permute opts <$> getArgs)
>>= \case
(_ , _ , _ : _) -> usage
(_ , _ : _, _ ) -> usage
(Config{_cHelp=True}, _ , _ ) -> usage
(config, _ , _ ) -> do
upd <- newMVar () -- putMVar when to update
mgr <- newTlsManager
(viewRef, refreshAction) <- runLoggingT
(runReaderT refresh (ClientContext config mgr))
print

-- In the list there are three items running concurrently:
-- 1. Timer that sends a signal to the updater when it's time to update
-- 2. Webserver that serves the menus to the user
-- 3. Updater that fetches new data from the restaurants
Async.runConcurrently $ traverse_ Async.Concurrently
[ timer
, webserver config viewRef upd
, updater
]
where
timer = forever $ tryPutMVar upd () >> threadDelay (view cInterval config)

Async.concurrently_
(Async.concurrently_
-- timer
(forever $ tryPutMVar upd () >> threadDelay (view cInterval config))
-- webserver
(serve config viewRef upd))
-- updater
(forever
$ withFDHandler defaultBatchingOptions stdout 1.0 80
$ \logCallback ->
runReaderT (refreshAction upd) (ClientContext config mgr)
`runLoggingT` ( logCallback
. renderWithTimestamp
(formatTime
defaultTimeLocale
(iso8601DateFormat
(Just "%H:%M:%S")
)
)
id
))
where usage = putStrLn $ usageInfo "mat-chalmers [OPTION...]" opts
updater =
forever
$ withFDHandler defaultBatchingOptions stdout 1.0 80
$ \logCallback ->
runLoggingT
(runReaderT (refreshAction upd) (ClientContext config mgr))
(logCallback . renderWithTimestamp (
formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")))
id
)

serve
webserver
:: Config
-> IORef View -- ^ View model
-> MVar () -- ^ Update signal
-> IO ()
serve conf viewRef upd = scotty (view cPort conf) $ do
webserver conf viewRef upd = scotty (view cPort conf) $ do
middleware logStdout
middleware (static $(embedDir "static"))
get "/" ((html . render) =<< liftIO (readIORef viewRef))
Expand Down
14 changes: 14 additions & 0 deletions src/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

module Config where

import Data.Foldable ( foldl' )
import Lens.Micro.Platform

-- | Configuration record
Expand All @@ -18,3 +19,16 @@ makeLenses ''Config

defaultConfig :: Config
defaultConfig = Config False 14 (1000000 * 60 * 30) 5007

-- | Create a Config we can touch
--
-- Yes, this function implementation is overly cute. It uses a lens to update
-- the first field of the tuple by using defaultConfig as first argument to
-- every function in the list. The brain needs to turn inside out a couple of
-- times before it makes sense, so if you're in a hurry, look at the types.
--
-- TODO: Feel free to bikeshed the function name.
reifyConfig
:: ([Config -> Config], [String], [String])
-> (Config, [String], [String])
reifyConfig = (& _1 %~ foldl' (flip id) defaultConfig)

0 comments on commit 7351c1a

Please sign in to comment.