Created
April 11, 2016 18:26
-
-
Save tolgap/a08fb76984c14a74155eb9c9cc060e65 to your computer and use it in GitHub Desktop.
A file watcher and compiler for Elm written in Haskell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | Running your app inside GHCi. | |
-- | |
-- To start up GHCi for usage with Yesod, first make sure you are in dev mode: | |
-- | |
-- > cabal configure -fdev | |
-- | |
-- Note that @yesod devel@ automatically sets the dev flag. | |
-- Now launch the repl: | |
-- | |
-- > cabal repl --ghc-options="-O0 -fobject-code" | |
-- | |
-- To start your app, run: | |
-- | |
-- > :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 | |
-- to run the following in GHCi or to add it to | |
-- your .ghci file. | |
-- | |
-- :set -DDEVELOPMENT | |
-- | |
-- There is more information about this approach, | |
-- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci | |
-- | |
-- The Watcher repo can be found here: https://github.com/paramanders/elm-watcher | |
module DevelMain where | |
import Prelude | |
import Application (getApplicationRepl, shutdownApp) | |
import qualified Watcher as EW | |
import Control.Concurrent | |
import Control.Exception (finally) | |
import Control.Monad ((>=>)) | |
import Data.IORef | |
import Data.Monoid ((<>)) | |
import Foreign.Store | |
import GHC.Word | |
import Network.Wai.Handler.Warp | |
-- | Start both Haskell Wai app and Elm watchers | |
start :: IO () | |
start = do | |
EW.update EW.WatchConfig { watchDir = "src/frontend", compileFile = "Public.elm", outputDir = "src/static/app.js" } 30 -- Start Elm public dir watcher | |
update -- Start Haskell | |
-- | Start or restart the server. | |
-- newStore is from foreign-store. | |
-- A Store holds onto some data across ghci reloads | |
update :: IO () | |
update = do | |
mtidStore <- lookupStore tidStoreNum | |
case mtidStore of | |
-- no server running | |
Nothing -> do | |
done <- storeAction doneStore newEmptyMVar | |
tid <- start done | |
_ <- storeAction (Store tidStoreNum) (newIORef tid) | |
return () | |
-- server is already running | |
Just tidStore -> restartAppInNewThread tidStore | |
where | |
doneStore :: Store (MVar ()) | |
doneStore = Store 0 | |
-- shut the server down with killThread and wait for the done signal | |
restartAppInNewThread :: Store (IORef ThreadId) -> IO () | |
restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do | |
killThread tid | |
withStore doneStore takeMVar | |
readStore doneStore >>= start | |
-- | Start the server in a separate thread. | |
start :: MVar () -- ^ Written to when the thread is killed. | |
-> IO ThreadId | |
start done = do | |
(port, site, app) <- getApplicationRepl | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment