Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatten the main function #116

Merged
merged 1 commit into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 47 additions & 48 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

module Main
( main
)
where
) where

import Control.Concurrent ( MVar
, newMVar
, threadDelay
, tryPutMVar
)
import qualified Control.Concurrent.Async as Async
import qualified Control.Concurrent.Async as Async
import Control.Monad ( forever )
import Control.Monad.Log ( defaultBatchingOptions
, renderWithTimestamp
Expand All @@ -20,15 +19,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 @@ -56,63 +55,63 @@ import View ( render )

opts :: [OptDescr (Config -> Config)]
opts =
[ Option [] ["help"] (NoArg (set cHelp True)) "Show usage info"
, Option [] ["port"] (ReqArg (set cPort . read) "PORT") "Port to run on"
[ Option [] ["help"] (NoArg (set cHelp True)) "Show usage info"
, Option [] ["port"] (ReqArg (set cPort . read) "PORT") "Port to run on"
, Option []
["interval"]
(ReqArg (set cInterval . (1000000 *) . read) "INTERVAL (s)")
"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
main = (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 upd config
, webserver config viewRef upd
, updater mgr upd refreshAction config
]
where
timer upd cfg =
forever $ tryPutMVar upd () >> threadDelay (view cInterval cfg)

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 mgr upd refreshAction cfg =
forever
$ withFDHandler defaultBatchingOptions stdout 1.0 80
$ \logCallback -> runLoggingT
(runReaderT (refreshAction upd) (ClientContext cfg 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))
get "/r" (liftIO (tryPutMVar upd ()) >> redirect "/") -- force update

16 changes: 15 additions & 1 deletion 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 @@ -12,9 +13,22 @@ data Config = Config
, _cNextDayHour :: !Int
, _cInterval :: !Int
, _cPort :: !Int
} deriving (Eq, Show)
}
deriving (Eq, Show)

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)
Loading