Scaffolding update

This commit is contained in:
Michael Snoyman 2015-01-15 08:38:34 +02:00
parent 9a3a2a5aa5
commit f9ee741b92
8 changed files with 747 additions and 226 deletions

View File

@ -1,3 +1,11 @@
## 1.4.3.4
Scaffolding updates:
* Improve `DevelMain` support
* Wipe out database during test runs
* Convenience `unsafeHandler` function
## 1.4.3.3 ## 1.4.3.3
More consistent whitespace in hamlet files in scaffolding [#50](https://github.com/yesodweb/yesod-scaffold/issues/50) More consistent whitespace in hamlet files in scaffolding [#50](https://github.com/yesodweb/yesod-scaffold/issues/50)

View File

@ -32,15 +32,22 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
, db
) where ) where
import Control.Monad.Logger (liftLoc) import Control.Monad.Logger (liftLoc)
import Database.Persist.MongoDB (MongoContext)
import Import import Import
import Language.Haskell.TH.Syntax (qLocation) import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -115,12 +122,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -145,16 +155,45 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
-- | Run DB queries
db :: ReaderT MongoContext (HandlerT App IO) a -> IO a
db = handler . runDB
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
import Database.Persist.MongoDB hiding (master) import Database.Persist.MongoDB hiding (master)
import Import.NoFoundation import Import.NoFoundation
import Text.Hamlet (hamletFile) import Text.Hamlet (hamletFile)
import Text.Jasmine (minifym) import Text.Jasmine (minifym)
import Yesod.Auth.BrowserId (authBrowserId) import Yesod.Auth.BrowserId (authBrowserId)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import qualified Yesod.Core.Unsafe as Unsafe
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application -- keep settings and values requiring initialization before your application
@ -291,6 +330,9 @@ instance YesodAuthPersist App
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -411,6 +453,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -446,7 +489,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-auth >= 1.4.0 && < 1.5 , yesod-auth >= 1.4.0 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
@ -683,40 +726,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -729,27 +783,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}

View File

@ -32,6 +32,12 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
, db
) where ) where
import Control.Monad.Logger (liftLoc, runLoggingT) import Control.Monad.Logger (liftLoc, runLoggingT)
@ -42,7 +48,7 @@ import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -131,12 +137,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -161,6 +170,34 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
-- | Run DB queries
db :: ReaderT SqlBackend (HandlerT App IO) a -> IO a
db = handler . runDB
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
@ -171,6 +208,7 @@ import Text.Jasmine (minifym)
import Yesod.Auth.BrowserId (authBrowserId) import Yesod.Auth.BrowserId (authBrowserId)
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import qualified Yesod.Core.Unsafe as Unsafe
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application -- keep settings and values requiring initialization before your application
@ -306,6 +344,9 @@ instance YesodAuthPersist App
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -423,6 +464,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -458,7 +500,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-auth >= 1.4.0 && < 1.5 , yesod-auth >= 1.4.0 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
@ -695,40 +737,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -741,27 +794,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}
@ -9032,7 +9106,7 @@ module TestImport
import Application (makeFoundation) import Application (makeFoundation)
import ClassyPrelude as X import ClassyPrelude as X
import Database.Persist as X hiding (get) import Database.Persist as X hiding (get)
import Database.Persist.Sql (SqlPersistM, runSqlPersistMPool) import Database.Persist.Sql (SqlPersistM, SqlBackend, runSqlPersistMPool, rawExecute, rawSql, unSingle, connEscapeName)
import Foundation as X import Foundation as X
import Model as X import Model as X
import Test.Hspec as X import Test.Hspec as X
@ -9041,8 +9115,11 @@ import Yesod.Test as X
runDB :: SqlPersistM a -> YesodExample App a runDB :: SqlPersistM a -> YesodExample App a
runDB query = do runDB query = do
pool <- fmap appConnPool getTestYesod app <- getTestYesod
liftIO $ runSqlPersistMPool query pool liftIO $ runDBWithApp app query
runDBWithApp :: App -> SqlPersistM a -> IO a
runDBWithApp app query = runSqlPersistMPool query (appConnPool app)
withApp :: SpecWith App -> Spec withApp :: SpecWith App -> Spec
withApp = before $ do withApp = before $ do
@ -9050,5 +9127,30 @@ withApp = before $ do
["config/test-settings.yml", "config/settings.yml"] ["config/test-settings.yml", "config/settings.yml"]
[] []
ignoreEnv ignoreEnv
makeFoundation settings foundation <- makeFoundation settings
wipeDB foundation
return foundation
-- This function will truncate all of the tables in your database.
-- 'withApp' calls it before each test, creating a clean environment for each
-- spec to run in.
wipeDB :: App -> IO ()
wipeDB app = do
runDBWithApp app $ do
tables <- getTables
sqlBackend <- ask
let queries = map (\t -> "TRUNCATE TABLE " ++ (connEscapeName sqlBackend $ DBName t)) tables
-- In MySQL, a table cannot be truncated if another table references it via foreign key.
-- Since we're wiping both the parent and child tables, though, it's safe
-- to temporarily disable this check.
rawExecute "SET foreign_key_checks = 0;" []
forM_ queries (\q -> rawExecute q [])
rawExecute "SET foreign_key_checks = 1;" []
return ()
getTables :: MonadIO m => ReaderT SqlBackend m [Text]
getTables = do
tables <- rawSql "SHOW TABLES;" []
return $ map unSingle tables

View File

@ -35,6 +35,12 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
, db
) where ) where
import Control.Monad.Logger (liftLoc, runLoggingT) import Control.Monad.Logger (liftLoc, runLoggingT)
@ -45,7 +51,7 @@ import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -137,12 +143,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -167,15 +176,44 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
-- | Run DB queries
db :: ReaderT SqlBackend (HandlerT App IO) a -> IO a
db = handler . runDB
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
import Database.Persist.Sql (ConnectionPool, runSqlPool) import Database.Persist.Sql (ConnectionPool, runSqlPool)
import Import.NoFoundation import Import.NoFoundation
import Text.Hamlet (hamletFile) import Text.Hamlet (hamletFile)
import Yesod.Auth.BrowserId (authBrowserId) import Yesod.Auth.BrowserId (authBrowserId)
import Yesod.Core.Types (Logger) import qualified Yesod.Core.Unsafe as Unsafe
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Core.Types (Logger)
import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Fay import Yesod.Fay
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
@ -322,6 +360,9 @@ instance YesodAuthPersist App
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -463,7 +504,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., fay-shared hs-source-dirs: ., fay-shared, app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -502,7 +543,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-auth >= 1.4.0 && < 1.5 , yesod-auth >= 1.4.0 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
@ -755,40 +796,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -801,27 +853,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}
@ -9153,7 +9226,7 @@ module TestImport
import Application (makeFoundation) import Application (makeFoundation)
import ClassyPrelude as X import ClassyPrelude as X
import Database.Persist as X hiding (get) import Database.Persist as X hiding (get)
import Database.Persist.Sql (SqlPersistM, runSqlPersistMPool) import Database.Persist.Sql (SqlPersistM, SqlBackend, runSqlPersistMPool, rawExecute, rawSql, unSingle, connEscapeName)
import Foundation as X import Foundation as X
import Model as X import Model as X
import Test.Hspec as X import Test.Hspec as X
@ -9162,8 +9235,12 @@ import Yesod.Test as X
runDB :: SqlPersistM a -> YesodExample App a runDB :: SqlPersistM a -> YesodExample App a
runDB query = do runDB query = do
pool <- fmap appConnPool getTestYesod app <- getTestYesod
liftIO $ runSqlPersistMPool query pool liftIO $ runDBWithApp app query
runDBWithApp :: App -> SqlPersistM a -> IO a
runDBWithApp app query = runSqlPersistMPool query (appConnPool app)
withApp :: SpecWith App -> Spec withApp :: SpecWith App -> Spec
withApp = before $ do withApp = before $ do
@ -9171,5 +9248,24 @@ withApp = before $ do
["config/test-settings.yml", "config/settings.yml"] ["config/test-settings.yml", "config/settings.yml"]
[] []
ignoreEnv ignoreEnv
makeFoundation settings foundation <- makeFoundation settings
wipeDB foundation
return foundation
-- This function will truncate all of the tables in your database.
-- 'withApp' calls it before each test, creating a clean environment for each
-- spec to run in.
wipeDB :: App -> IO ()
wipeDB app = do
runDBWithApp app $ do
tables <- getTables
sqlBackend <- ask
let escapedTables = map (connEscapeName sqlBackend . DBName) tables
query = "TRUNCATE TABLE " ++ (intercalate ", " escapedTables)
rawExecute query []
getTables :: MonadIO m => ReaderT SqlBackend m [Text]
getTables = do
tables <- rawSql "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';" []
return $ map unSingle tables

View File

@ -32,6 +32,12 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
, db
) where ) where
import Control.Monad.Logger (liftLoc, runLoggingT) import Control.Monad.Logger (liftLoc, runLoggingT)
@ -42,7 +48,7 @@ import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -131,12 +137,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -161,6 +170,34 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
-- | Run DB queries
db :: ReaderT SqlBackend (HandlerT App IO) a -> IO a
db = handler . runDB
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
@ -171,6 +208,7 @@ import Text.Jasmine (minifym)
import Yesod.Auth.BrowserId (authBrowserId) import Yesod.Auth.BrowserId (authBrowserId)
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import qualified Yesod.Core.Unsafe as Unsafe
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application -- keep settings and values requiring initialization before your application
@ -306,6 +344,9 @@ instance YesodAuthPersist App
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -423,6 +464,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -458,7 +500,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-auth >= 1.4.0 && < 1.5 , yesod-auth >= 1.4.0 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
@ -695,40 +737,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -741,27 +794,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}
@ -9032,7 +9106,7 @@ module TestImport
import Application (makeFoundation) import Application (makeFoundation)
import ClassyPrelude as X import ClassyPrelude as X
import Database.Persist as X hiding (get) import Database.Persist as X hiding (get)
import Database.Persist.Sql (SqlPersistM, runSqlPersistMPool) import Database.Persist.Sql (SqlPersistM, SqlBackend, runSqlPersistMPool, rawExecute, rawSql, unSingle, connEscapeName)
import Foundation as X import Foundation as X
import Model as X import Model as X
import Test.Hspec as X import Test.Hspec as X
@ -9041,8 +9115,12 @@ import Yesod.Test as X
runDB :: SqlPersistM a -> YesodExample App a runDB :: SqlPersistM a -> YesodExample App a
runDB query = do runDB query = do
pool <- fmap appConnPool getTestYesod app <- getTestYesod
liftIO $ runSqlPersistMPool query pool liftIO $ runDBWithApp app query
runDBWithApp :: App -> SqlPersistM a -> IO a
runDBWithApp app query = runSqlPersistMPool query (appConnPool app)
withApp :: SpecWith App -> Spec withApp :: SpecWith App -> Spec
withApp = before $ do withApp = before $ do
@ -9050,5 +9128,24 @@ withApp = before $ do
["config/test-settings.yml", "config/settings.yml"] ["config/test-settings.yml", "config/settings.yml"]
[] []
ignoreEnv ignoreEnv
makeFoundation settings foundation <- makeFoundation settings
wipeDB foundation
return foundation
-- This function will truncate all of the tables in your database.
-- 'withApp' calls it before each test, creating a clean environment for each
-- spec to run in.
wipeDB :: App -> IO ()
wipeDB app = do
runDBWithApp app $ do
tables <- getTables
sqlBackend <- ask
let escapedTables = map (connEscapeName sqlBackend . DBName) tables
query = "TRUNCATE TABLE " ++ (intercalate ", " escapedTables)
rawExecute query []
getTables :: MonadIO m => ReaderT SqlBackend m [Text]
getTables = do
tables <- rawSql "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';" []
return $ map unSingle tables

View File

@ -32,6 +32,11 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
) where ) where
import Control.Monad.Logger (liftLoc) import Control.Monad.Logger (liftLoc)
@ -40,7 +45,7 @@ import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -112,12 +117,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -142,14 +150,39 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
import Import.NoFoundation import Import.NoFoundation
import Text.Hamlet (hamletFile) import Text.Hamlet (hamletFile)
import Text.Jasmine (minifym) import Text.Jasmine (minifym)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import qualified Yesod.Core.Unsafe as Unsafe
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application -- keep settings and values requiring initialization before your application
@ -244,6 +277,9 @@ instance Yesod App where
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -346,6 +382,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -380,7 +417,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
, classy-prelude >= 0.10.2 , classy-prelude >= 0.10.2
@ -604,40 +641,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -650,27 +698,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}

View File

@ -32,6 +32,12 @@ module Application
, appMain , appMain
, develMain , develMain
, makeFoundation , makeFoundation
-- * for DevelMain
, getApplicationRepl
, shutdownApp
-- * for GHCI
, handler
, db
) where ) where
import Control.Monad.Logger (liftLoc, runLoggingT) import Control.Monad.Logger (liftLoc, runLoggingT)
@ -42,7 +48,7 @@ import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai.Handler.Warp (Settings, defaultSettings, import Network.Wai.Handler.Warp (Settings, defaultSettings,
defaultShouldDisplayException, defaultShouldDisplayException,
runSettings, setHost, runSettings, setHost,
setOnException, setPort) setOnException, setPort, getPort)
import Network.Wai.Middleware.RequestLogger (Destination (Logger), import Network.Wai.Middleware.RequestLogger (Destination (Logger),
IPAddrSource (..), IPAddrSource (..),
OutputFormat (..), destination, OutputFormat (..), destination,
@ -131,12 +137,15 @@ warpSettings foundation =
-- | 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)
getApplicationDev = do getApplicationDev = do
settings <- loadAppSettings [configSettingsYml] [] useEnv settings <- getAppSettings
foundation <- makeFoundation settings foundation <- makeFoundation settings
app <- makeApplication foundation
wsettings <- getDevSettings $ warpSettings foundation wsettings <- getDevSettings $ warpSettings foundation
app <- makeApplication foundation
return (wsettings, app) return (wsettings, app)
getAppSettings :: IO AppSettings
getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
-- | main function for use by yesod devel -- | main function for use by yesod devel
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
@ -161,6 +170,34 @@ appMain = do
-- 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)
--------------------------------------------------------------
getApplicationRepl :: IO (Int, App, Application)
getApplicationRepl = do
settings <- getAppSettings
foundation <- makeFoundation settings
wsettings <- getDevSettings $ warpSettings foundation
app1 <- makeApplication foundation
return (getPort wsettings, foundation, app1)
shutdownApp :: App -> IO ()
shutdownApp _ = return ()
---------------------------------------------
-- Functions for use in development with GHCi
---------------------------------------------
-- | Run a handler
handler :: Handler a -> IO a
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
-- | Run DB queries
db :: ReaderT SqlBackend (HandlerT App IO) a -> IO a
db = handler . runDB
{-# START_FILE Foundation.hs #-} {-# START_FILE Foundation.hs #-}
module Foundation where module Foundation where
@ -171,6 +208,7 @@ import Text.Jasmine (minifym)
import Yesod.Auth.BrowserId (authBrowserId) import Yesod.Auth.BrowserId (authBrowserId)
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import qualified Yesod.Core.Unsafe as Unsafe
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application -- keep settings and values requiring initialization before your application
@ -306,6 +344,9 @@ instance YesodAuthPersist App
instance RenderMessage App FormMessage where instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage renderMessage _ _ = defaultFormMessage
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- Note: Some functionality previously present in the scaffolding has been -- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful -- moved to documentation in the Wiki. Following are some hopefully helpful
-- links: -- links:
@ -423,6 +464,7 @@ Flag library-only
Default: False Default: False
library library
hs-source-dirs: ., app
exposed-modules: Application exposed-modules: Application
Foundation Foundation
Import Import
@ -458,7 +500,7 @@ library
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.4.1 && < 1.5 , yesod >= 1.4.1 && < 1.5
, yesod-core >= 1.4.0 && < 1.5 , yesod-core >= 1.4.6 && < 1.5
, yesod-auth >= 1.4.0 && < 1.5 , yesod-auth >= 1.4.0 && < 1.5
, yesod-static >= 1.4.0.3 && < 1.5 , yesod-static >= 1.4.0.3 && < 1.5
, yesod-form >= 1.4.0 && < 1.5 , yesod-form >= 1.4.0 && < 1.5
@ -695,40 +737,51 @@ import Yesod.Static (staticFiles)
staticFiles (appStaticDir compileTimeAppSettings) staticFiles (appStaticDir compileTimeAppSettings)
{-# START_FILE app/DevelMain.hs #-} {-# START_FILE app/DevelMain.hs #-}
-- | Development version to be run inside GHCi. -- | Running your app inside GHCi.
-- --
-- start this up with: -- To start up GHCi for usage with Yesod, first make sure you are in dev mode:
-- --
-- cabal repl --ghc-options="-O0 -fobject-code" -- > cabal configure -fdev
-- --
-- run with: -- Note that @yesod devel@ automatically sets the dev flag.
-- Now launch the repl:
-- --
-- :l DevelMain -- > cabal repl --ghc-options="-O0 -fobject-code"
-- DevelMain.update
-- --
-- You will need to add these packages to your .cabal file -- To start your app, run:
-- * foreign-store >= 0.1 (very light-weight) --
-- * warp (you already depend on this, it just isn't in your .cabal file) -- > :l DevelMain
-- > DevelMain.update
--
-- You can also call @DevelMain.shutdown@ to stop the app
--
-- You will need to add the foreign-store package to your .cabal file.
-- It is very light-weight.
-- --
-- If you don't use cabal repl, you will need -- If you don't use cabal repl, you will need
-- to add settings to your .ghci file. -- to run the following in GHCi or to add it to
-- your .ghci file.
-- --
-- :set -DDEVELOPMENT -- :set -DDEVELOPMENT
-- --
-- There is more information about using ghci -- There is more information about this approach,
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
module DevelMain where module DevelMain where
import Application (getApplicationDev) import Prelude
import Application (getApplicationRepl, shutdownApp)
import Control.Exception (finally) import Control.Exception (finally)
import Control.Monad ((>=>))
import Control.Concurrent import Control.Concurrent
import Data.IORef import Data.IORef
import Foreign.Store import Foreign.Store
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import GHC.Word
-- | Start or restart the server. -- | Start or restart the server.
-- newStore is from foreign-store.
-- A Store holds onto some data across ghci reloads -- A Store holds onto some data across ghci reloads
update :: IO () update :: IO ()
update = do update = do
@ -741,27 +794,48 @@ update = do
_ <- storeAction (Store tidStoreNum) (newIORef tid) _ <- storeAction (Store tidStoreNum) (newIORef tid)
return () return ()
-- server is already running -- server is already running
Just tidStore -> Just tidStore -> restartAppInNewThread tidStore
-- shut the server down with killThread and wait for the done signal
modifyStoredIORef tidStore $ \tid -> do
killThread tid
withStore doneStore takeMVar >> readStore doneStore >>= start
where where
doneStore :: Store (MVar ())
doneStore = Store 0 doneStore = Store 0
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO () -- shut the server down with killThread and wait for the done signal
modifyStoredIORef store f = withStore store $ \ref -> do restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
v <- readIORef ref restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
f v >>= writeIORef ref killThread tid
withStore doneStore takeMVar
readStore doneStore >>= start
-- | Start the server in a separate thread.
start :: MVar () -- ^ Written to when the thread is killed. -- | Start the server in a separate thread.
-> IO ThreadId start :: MVar () -- ^ Written to when the thread is killed.
start done = do -> IO ThreadId
(settings,app) <- getApplicationDev start done = do
forkIO (finally (runSettings settings app) (port, site, app) <- getApplicationRepl
(putMVar done ())) forkIO (finally (runSettings (setPort port defaultSettings) app)
-- Note that this implies concurrency
-- between shutdownApp and the next app that is starting.
-- Normally this should be fine
(putMVar done () >> shutdownApp site))
-- | kill the server
shutdown :: IO ()
shutdown = do
mtidStore <- lookupStore tidStoreNum
case mtidStore of
-- no server running
Nothing -> putStrLn "no Yesod app running"
Just tidStore -> do
withStore tidStore $ readIORef >=> killThread
putStrLn "Yesod app is shutdown"
tidStoreNum :: Word32
tidStoreNum = 1
modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
modifyStoredIORef store f = withStore store $ \ref -> do
v <- readIORef ref
f v >>= writeIORef ref
{-# START_FILE app/devel.hs #-} {-# START_FILE app/devel.hs #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}

View File

@ -1,5 +1,5 @@
name: yesod-bin name: yesod-bin
version: 1.4.3.3 version: 1.4.3.4
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com> author: Michael Snoyman <michael@snoyman.com>