Commit d55a1be9 authored by Christopher League's avatar Christopher League 🖥

ormolu formatting

parent 1b3f7ba0
Pipeline #831 passed with stage
in 2 minutes and 12 seconds
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Application module Application
( getApplicationDev ( getApplicationDev,
, appMain appMain,
, develMain develMain,
, makeFoundation makeFoundation,
, makeLogWare makeLogWare,
-- * for DevelMain -- * for DevelMain
, getApplicationRepl getApplicationRepl,
, shutdownApp shutdownApp,
-- * for GHCI -- * for GHCI
, handler handler
) where )
where
import qualified Calendar as Cal import qualified Calendar as Cal
import Control.Monad.Logger (liftLoc, logInfoN) import Control.Monad.Logger (liftLoc, logInfoN)
import qualified FreshCache as FC import qualified FreshCache as FC
import Handlers import Handlers
import Import import Import
import Language.Haskell.TH.Syntax (qLocation) import Language.Haskell.TH.Syntax (qLocation)
import Network.HTTP.Client.TLS (getGlobalManager) import Network.HTTP.Client.TLS (getGlobalManager)
import Network.Wai (Middleware) import Network.Wai (Middleware)
import Network.Wai.Handler.Warp (Settings, import Network.Wai.Handler.Warp
defaultSettings, ( Settings,
defaultShouldDisplayException, defaultSettings,
getPort, runSettings, defaultShouldDisplayException,
setHost, setOnException, getPort,
setPort) runSettings,
import Network.Wai.Middleware.RequestLogger (Destination (Logger), setHost,
IPAddrSource (..), setOnException,
OutputFormat (..), setPort
destination, )
mkRequestLogger, import Network.Wai.Middleware.RequestLogger
outputFormat) ( Destination (Logger),
import System.Log.FastLogger (defaultBufSize, IPAddrSource (..),
newStdoutLoggerSet, OutputFormat (..),
toLogStr) destination,
mkRequestLogger,
outputFormat
)
import System.Log.FastLogger
( defaultBufSize,
newStdoutLoggerSet,
toLogStr
)
-- This line actually creates our YesodDispatch instance. It is the second half -- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the -- of the call to mkYesodData which occurs in Foundation.hs. Please see the
...@@ -57,24 +66,26 @@ makeFoundation appSettings@AppSettings {..} = do ...@@ -57,24 +66,26 @@ makeFoundation appSettings@AppSettings {..} = do
appHttpManager <- getGlobalManager appHttpManager <- getGlobalManager
appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger
appStatic <- appStatic <-
(if appMutableStatic ( if appMutableStatic
then staticDevel then staticDevel
else static) else static
)
appStaticDir appStaticDir
let partialApp = App {..} let partialApp = App {..}
where where
appCalendarCxt = error "partialApp loop: Accessing appCalendarCxt" appCalendarCxt = error "partialApp loop: Accessing appCalendarCxt"
appCalendarCache = error "partialApp loop: Accessing appCalendarCache" appCalendarCache = error "partialApp loop: Accessing appCalendarCache"
logFunc loc src lv = logFunc loc src lv =
whenM (shouldLogIO partialApp "" lv) . whenM (shouldLogIO partialApp "" lv)
messageLoggerSource partialApp appLogger loc src lv . messageLoggerSource partialApp appLogger loc src lv
appCalendarCxt <- Cal.initialize logFunc appHttpManager appCredentials appCalendarCxt <- Cal.initialize logFunc appHttpManager appCredentials
appCalendarCache <- appCalendarCache <-
unsafeHandler partialApp $ unsafeHandler partialApp
FC.newCache appCacheExpiry $ do $ FC.newCache appCacheExpiry
logInfoN "Refreshing calendar cache" $ do
Cal.listAvailMinusBusy appCalendarCxt appFreeCalendarId appBusyCalendarId $ logInfoN "Refreshing calendar cache"
fromWeeks appLookaheadWeeks Cal.listAvailMinusBusy appCalendarCxt appFreeCalendarId appBusyCalendarId
$ fromWeeks appLookaheadWeeks
return App {..} return App {..}
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and -- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
...@@ -82,7 +93,7 @@ makeFoundation appSettings@AppSettings {..} = do ...@@ -82,7 +93,7 @@ makeFoundation appSettings@AppSettings {..} = do
makeApplication :: App -> IO Application makeApplication :: App -> IO Application
makeApplication foundation = do makeApplication foundation = do
logWare <- makeLogWare foundation logWare <- makeLogWare foundation
-- Create the WAI application and apply middlewares -- Create the WAI application and apply middlewares
appPlain <- toWaiAppPlain foundation appPlain <- toWaiAppPlain foundation
return $ logWare $ defaultMiddlewaresNoLogging appPlain return $ logWare $ defaultMiddlewaresNoLogging appPlain
...@@ -93,29 +104,32 @@ makeLogWare foundation = ...@@ -93,29 +104,32 @@ makeLogWare foundation =
{ outputFormat = { outputFormat =
if appDetailedRequestLogging $ appSettings foundation if appDetailedRequestLogging $ appSettings foundation
then Detailed True then Detailed True
else Apache else
(if appIpFromHeader $ appSettings foundation Apache
then FromFallback ( if appIpFromHeader $ appSettings foundation
else FromSocket) then FromFallback
, destination = Logger $ loggerSet $ appLogger foundation else FromSocket
} ),
destination = Logger $ loggerSet $ appLogger foundation
}
-- | Warp settings for the given foundation value. -- | Warp settings for the given foundation value.
warpSettings :: App -> Settings warpSettings :: App -> Settings
warpSettings foundation = warpSettings foundation =
setPort (appPort $ appSettings foundation) $ setPort (appPort $ appSettings foundation)
setHost (appHost $ appSettings foundation) $ $ setHost (appHost $ appSettings foundation)
setOnException $ setOnException
(\_req e -> ( \_req e ->
when (defaultShouldDisplayException e) $ when (defaultShouldDisplayException e)
messageLoggerSource $ messageLoggerSource
foundation foundation
(appLogger foundation) (appLogger foundation)
$(qLocation >>= liftLoc) $(qLocation >>= liftLoc)
"yesod" "yesod"
LevelError LevelError
(toLogStr $ "Exception from Warp: " ++ show e)) (toLogStr $ "Exception from Warp: " ++ show e)
defaultSettings )
defaultSettings
-- | For yesod devel, return the Warp settings and WAI Application. -- | For yesod devel, return the Warp settings and WAI Application.
getApplicationDev :: IO (Settings, Application) getApplicationDev :: IO (Settings, Application)
...@@ -135,21 +149,21 @@ develMain = develMainHelper getApplicationDev ...@@ -135,21 +149,21 @@ develMain = develMainHelper getApplicationDev
-- | The @main@ function for an executable running this site. -- | The @main@ function for an executable running this site.
appMain :: IO () appMain :: IO ()
appMain appMain =
-- Get the settings from all relevant sources -- Get the settings from all relevant sources
= do do
settings <- settings <-
loadYamlSettingsArgs loadYamlSettingsArgs
-- fall back to compile-time values, set to [] to require values at runtime -- fall back to compile-time values, set to [] to require values at runtime
[configSettingsYmlValue] [configSettingsYmlValue]
-- allow environment variables to override -- allow environment variables to override
useEnv useEnv
-- Generate the foundation from the settings -- Generate the foundation from the settings
foundation <- makeFoundation settings foundation <- makeFoundation settings
-- Generate a WAI Application from the foundation -- Generate a WAI Application from the foundation
app <- makeApplication foundation app <- makeApplication foundation
-- Run the application with Warp -- Run the application with Warp
runSettings (warpSettings foundation) app runSettings (warpSettings foundation) app
-------------------------------------------------------------- --------------------------------------------------------------
-- Functions for DevelMain.hs (a way to run the app from GHCi) -- Functions for DevelMain.hs (a way to run the app from GHCi)
......
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-| {-|
Module: BookingForm Module: BookingForm
...@@ -11,35 +11,38 @@ Description: TODO ...@@ -11,35 +11,38 @@ Description: TODO
TODO TODO
-} -}
module BookingForm module BookingForm
( Booking(..) ( Booking (..),
, bookingMForm bookingMForm,
, eventFromBooking eventFromBooking,
, toSession toSession,
, fromSessionMaybe fromSessionMaybe,
, fromSession fromSession,
, b3Class b3Class,
, inputSize inputSize,
, labelSize labelSize
) where )
where
import Calendar
import Control.Monad.Trans.Maybe import Calendar
import Data.Aeson ((.!=), (.:?)) import Control.Monad.Trans.Maybe
import qualified Data.Aeson as Js import Data.Aeson ((.!=), (.:?))
import Data.Function ((&)) import qualified Data.Aeson as Js
import Data.Time.Clock (addUTCTime) import Data.Function ((&))
import Data.Time.Zones import Data.Time.Clock (addUTCTime)
import Import import Data.Time.Zones
import qualified Network.HTTP.Simple as H import Import
import qualified QueryForm as QF import qualified Network.HTTP.Simple as H
import qualified Yesod.Form.Bootstrap3 as B3 import qualified QueryForm as QF
import qualified Yesod.Form.Bootstrap3 as B3
data Booking = Booking
{ bookName :: Text data Booking
, bookEmail :: Text = Booking
, bookSubject :: Text { bookName :: Text,
, bookContact :: Maybe Text bookEmail :: Text,
} deriving (Show) bookSubject :: Text,
bookContact :: Maybe Text
}
deriving (Show)
toSession :: MonadHandler m => Booking -> m () toSession :: MonadHandler m => Booking -> m ()
toSession Booking {..} = do toSession Booking {..} = do
...@@ -77,12 +80,12 @@ submit = do ...@@ -77,12 +80,12 @@ submit = do
bookingAForm :: Maybe Booking -> AForm Handler Booking bookingAForm :: Maybe Booking -> AForm Handler Booking
bookingAForm bOpt = bookingAForm bOpt =
Booking <$> areq textField name (bookName <$> bOpt) <*> Booking <$> areq textField name (bookName <$> bOpt)
areq emailField email (bookEmail <$> bOpt) <*> <*> areq emailField email (bookEmail <$> bOpt)
areq textField subject (bookSubject <$> bOpt) <*> <*> areq textField subject (bookSubject <$> bOpt)
aopt textField contact (bookContact <$> bOpt) <* <*> aopt textField contact (bookContact <$> bOpt)
formToAForm recaptcha <* <* formToAForm recaptcha
formToAForm submit <* formToAForm submit
where where
name = bfs "*Name" "Who are you?" name = bfs "*Name" "Who are you?"
email = bfs "*Email" "You must use a valid email address" email = bfs "*Email" "You must use a valid email address"
...@@ -90,12 +93,15 @@ bookingAForm bOpt = ...@@ -90,12 +93,15 @@ bookingAForm bOpt =
bfs "*Subject" "What course are you in? What do you want to talk about?" bfs "*Subject" "What course are you in? What do you want to talk about?"
contact = bfs "Contact" "For online meetings, how do I reach you?" contact = bfs "Contact" "For online meetings, how do I reach you?"
data RecaptchaResponse = RecaptchaResponse data RecaptchaResponse
{ rrSuccess :: Bool = RecaptchaResponse
, rrErrors :: [Text] { rrSuccess :: Bool,
} deriving (Show) rrErrors :: [Text]
}
deriving (Show)
instance Js.FromJSON RecaptchaResponse where instance Js.FromJSON RecaptchaResponse where
parseJSON = parseJSON =
Js.withObject "RecaptchaResponse" $ \o -> do Js.withObject "RecaptchaResponse" $ \o -> do
rrSuccess <- o .: "success" rrSuccess <- o .: "success"
...@@ -115,11 +121,11 @@ recaptcha = ...@@ -115,11 +121,11 @@ recaptcha =
H.parseRequest H.parseRequest
"POST https://www.google.com/recaptcha/api/siteverify" "POST https://www.google.com/recaptcha/api/siteverify"
let req = let req =
req' & H.setRequestManager mgr & req' & H.setRequestManager mgr
H.setRequestBodyURLEncoded & H.setRequestBodyURLEncoded
[ ("secret", encodeUtf8 secret) [ ("secret", encodeUtf8 secret),
, ("response", encodeUtf8 response) ("response", encodeUtf8 response)
] ]
resp <- H.getResponseBody <$> H.httpJSON req resp <- H.getResponseBody <$> H.httpJSON req
if rrSuccess resp if rrSuccess resp
then return (FormSuccess (), []) then return (FormSuccess (), [])
...@@ -164,8 +170,8 @@ b3Class g = ...@@ -164,8 +170,8 @@ b3Class g =
B3.ColMd n -> "col-md-" <> tshow n B3.ColMd n -> "col-md-" <> tshow n
B3.ColLg n -> "col-lg-" <> tshow n B3.ColLg n -> "col-lg-" <> tshow n
bookingMForm :: bookingMForm
Maybe Booking -> Html -> MForm Handler (FormResult Booking, Widget) :: Maybe Booking -> Html -> MForm Handler (FormResult Booking, Widget)
bookingMForm = B3.renderBootstrap3 horiz . bookingAForm bookingMForm = B3.renderBootstrap3 horiz . bookingAForm
where where
horiz = horiz =
......
This diff is collapsed.
This diff is collapsed.
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-| {-|
...@@ -13,32 +13,35 @@ existing one is used. If it is requested after the time is expired, we ...@@ -13,32 +13,35 @@ existing one is used. If it is requested after the time is expired, we
automatically refresh it. Should be thread-safe. automatically refresh it. Should be thread-safe.
-} -}
module FreshCache module FreshCache
( Cache ( Cache,
, newCache newCache,
, readCache readCache,
, invalidateCache invalidateCache,
, cacheDemo cacheDemo
) where )
where
import ClassyPrelude.Yesod import ClassyPrelude.Yesod
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Data.Time.Clock (NominalDiffTime, diffUTCTime) import Data.Time.Clock (NominalDiffTime, diffUTCTime)
data CacheData a = CacheData data CacheData a
{ value :: a = CacheData
, retrievedAt :: UTCTime { value :: a,
} retrievedAt :: UTCTime
}
data CacheMeta m a = CacheMeta data CacheMeta m a
{ content :: Maybe (CacheData a) = CacheMeta
, maxAge :: NominalDiffTime { content :: Maybe (CacheData a),
, refresh :: m a maxAge :: NominalDiffTime,
} refresh :: m a
}
-- | The cache structure. Saves the value 'a' which can be refreshed or -- | The cache structure. Saves the value 'a' which can be refreshed or
-- accessed within the monad 'm'. -- accessed within the monad 'm'.
newtype Cache m a = newtype Cache m a
Cache (MVar (CacheMeta m a)) = Cache (MVar (CacheMeta m a))
-- | Create a new cache by running the action and saving it with a -- | Create a new cache by running the action and saving it with a
-- timestamp. -- timestamp.
......
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-| {-|
Module: Handlers Module: Handlers
...@@ -12,53 +12,54 @@ Description: TODO ...@@ -12,53 +12,54 @@ Description: TODO
TODO TODO
-} -}
module Handlers module Handlers
( getFaviconR ( getFaviconR,
, getRobotsR getRobotsR,
, getHomeR getHomeR,
, getAvailR getAvailR,
, postHomeR postHomeR,
, getBookR getBookR,
, postBookR postBookR,
, getFinalR getFinalR,
, getClearR getClearR,
, getVersionR getVersionR
) where )
where
import BookingForm (b3Class, inputSize, labelSize) import BookingForm (b3Class, inputSize, labelSize)
import qualified BookingForm as BF import qualified BookingForm as BF
import Calendar import Calendar
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Data.FileEmbed (embedFile) import Data.FileEmbed (embedFile)
import qualified Data.SortedList as SL import qualified Data.SortedList as SL
import Data.Time.Clock (addUTCTime, getCurrentTime) import Data.Time.Clock (addUTCTime, getCurrentTime)
import Data.Time.Zones (localTimeToUTCTZ, utcToLocalTimeTZ) import Data.Time.Zones (localTimeToUTCTZ, utcToLocalTimeTZ)
import qualified FreshCache as FC import qualified FreshCache as FC
import Import import Import
import qualified QueryForm as QF import qualified QueryForm as QF
import Text.Hamlet (shamletFile) import Text.Hamlet (shamletFile)
import Text.Julius (RawJS (..)) import Text.Julius (RawJS (..))
-- | Home page, which shows query settings. Also has an AJAX facility -- | Home page, which shows query settings. Also has an AJAX facility
-- to get the available slots and show them as buttons. -- to get the available slots and show them as buttons.
getHomeR :: Handler Html getHomeR :: Handler Html
getHomeR getHomeR =
-- Start refresh of calendar, but don't wait for result. -- Start refresh of calendar, but don't wait for result.
= do do
App {appCalendarCache} <- getYesod App {appCalendarCache} <- getYesod
void $ async $ FC.readCache appCalendarCache void $ async $ FC.readCache appCalendarCache
-- Produce form for query and display parameters: appointment length, -- Produce form for query and display parameters: appointment length,
-- 12/24-hour time, time zone, location. -- 12/24-hour time, time zone, location.
qOpt <- runMaybeT QF.fromSession qOpt <- runMaybeT QF.fromSession
bOpt <- runMaybeT BF.fromSessionMaybe bOpt <- runMaybeT BF.fromSessionMaybe
(idReset, idSpinner, idAvail, idAlert) <- newIdent4 (idReset, idSpinner, idAvail, idAlert) <- newIdent4
(queryWidget, enctype) <- generateFormPost $ QF.queryForm idReset qOpt (queryWidget, enctype) <- generateFormPost $ QF.queryForm idReset qOpt
defaultLayout $(widgetFile "homepage") defaultLayout $(widgetFile "homepage")
-- | Ensure a successful form submission, or else throw a 400. -- | Ensure a successful form submission, or else throw a 400.
formSuccess :: MonadHandler m => ((FormResult a, b), c) -> m a formSuccess :: MonadHandler m => ((FormResult a, b), c) -> m a
formSuccess ((formResult, _), _) = formSuccess ((formResult, _), _) =
case formResult of case formResult of
FormMissing -> invalidArgs ["Missing form data!"] FormMissing -> invalidArgs ["Missing form data!"]
FormFailure errors -> invalidArgs errors FormFailure errors -> invalidArgs errors
FormSuccess result -> return result FormSuccess result -> return result
...@@ -70,12 +71,12 @@ getAvailR = do ...@@ -70,12 +71,12 @@ getAvailR = do
App {appSettings = AppSettings {..}, appCalendarCache} <- getYesod App {appSettings = AppSettings {..}, appCalendarCache} <- getYesod
earliest <- addUTCTime appLeadTime <$> liftIO getCurrentTime earliest <- addUTCTime appLeadTime <$> liftIO getCurrentTime
daysWithSlots <- daysWithSlots <-
groupByDay . groupByDay
SL.map (applyTz (tzByLabel queryTzLabel)) . . SL.map (applyTz (tzByLabel queryTzLabel))
SL.dropWhile ((< earliest) . seStart) . . SL.dropWhile ((< earliest) . seStart)
partitionSlots (headMay appApptLengthsMinutes) queryApptLength . . partitionSlots (headMay appApptLengthsMinutes) queryApptLength
SL.filter (summaryMatches (locSearch queryLocation)) <$> . SL.filter (summaryMatches (locSearch queryLocation))
FC.readCache appCalendarCache <$> FC.readCache appCalendarCache
return $(shamletFile "templates/avail.hamlet") return $(shamletFile "templates/avail.hamlet")
postHomeR :: Handler Html postHomeR :: Handler Html
...@@ -100,9 +101,10 @@ showWhenWhere = do ...@@ -100,9 +101,10 @@ showWhenWhere = do
let inOtherZone = let inOtherZone =
if queryTzLabel == appDefaultTimeZone if queryTzLabel == appDefaultTimeZone
then Nothing then Nothing
else Just $ else
utcToLocalTimeTZ (tzByLabel appDefaultTimeZone) $ Just
localTimeToUTCTZ (tzByLabel queryTzLabel) slotLocal $ utcToLocalTimeTZ (tzByLabel appDefaultTimeZone)
$ localTimeToUTCTZ (tzByLabel queryTzLabel) slotLocal
return (q, s, $(widgetFile "when-where")) return (q, s, $(widgetFile "when-where"))
postBookR :: Handler Html postBookR :: Handler Html
...@@ -138,8 +140,9 @@ getClearR = clearSession >> redirect HomeR ...@@ -138,8 +140,9 @@ getClearR = clearSession >> redirect HomeR
getFaviconR :: Handler TypedContent getFaviconR :: Handler TypedContent
getFaviconR = do getFaviconR = do
cacheSeconds $ 60 * 60 * 24 * 30 -- cache for a month cacheSeconds $ 60 * 60 * 24 * 30 -- cache for a month
return $ return
TypedContent "image/x-icon" $ toContent $(embedFile "config/favicon.ico") $ TypedContent "image/x-icon"
$ toContent $(embedFile "config/favicon.ico")
getRobotsR :: Handler TypedContent getRobotsR :: Handler TypedContent
getRobotsR = getRobotsR =
......
module Import module Import
( module Import ( module Import
) where )
where
import Foundation as Import import Foundation as Import
import Import.NoFoundation as Import import Import.NoFoundation as Import
newIdent2 :: MonadHandler m => m (Text, Text) newIdent2 :: MonadHandler m => m (Text, Text)
newIdent2 = (,) <$> newIdent <*> newIdent newIdent2 = (,) <$> newIdent <*> newIdent
......
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Import.NoFoundation module Import.NoFoundation
( module Import ( module Import,
, pluralN pluralN,
, fromWeeks fromWeeks,
, fromMinutes fromMinutes
) where )
where
import Calendar as Import (CalendarContext, import Calendar as Import
SimpleEventUTC) ( CalendarContext,
import ClassyPrelude.Yesod as Import SimpleEventUTC
import Data.Time.Clock (NominalDiffTime) )
import Settings as Import import ClassyPrelude.Yesod as Import
import Settings.StaticFiles as Import import Data.Time.Clock (NominalDiffTime)
import Yesod.Core.Types as Import (loggerSet) import Settings as Import
import Yesod.Default.Config2 as Import import Settings.StaticFiles as Import
import Yesod.Core.Types as Import (loggerSet)
import Yesod.Default.Config2 as Import
pluralN :: Int -> Text -> Text -> Text pluralN :: Int -> Text -> Text -> Text
pluralN 1 x _ = "1 " <> x pluralN 1 x _ = "1 " <> x
......
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}