packages feed

wai-handler-devel 0.1.1.2 → 0.2.0

raw patch · 5 files changed

+148/−136 lines, 5 filesdep +enumeratordep +old-timedep +textdep −utf8-stringdep −wai-extradep ~basedep ~wai

Dependencies added: enumerator, old-time, text, transformers, warp

Dependencies removed: utf8-string, wai-extra

Dependency ranges changed: base, wai

Files

+ Network/Wai/Application/Devel.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE OverloadedStrings #-}+module Network.Wai.Application.Devel+    ( -- * Types+      AppHolder+    , AppRunner+    , WithAppRunner+      -- * Functions+    , initAppHolder+    , swapApp+    , swapAppSimple+    , toApp+    ) where++import Control.Concurrent (forkIO)+import Control.Concurrent.MVar+    ( MVar, newEmptyMVar, newMVar+    , takeMVar, putMVar, readMVar+    )+import Network.Wai (Application, responseLBS, status500)+import Data.ByteString.Lazy.Char8 ()+import Control.Monad.IO.Class (liftIO)++type AppHolder = MVar (Application, MVar ())+type AppRunner = Application -> IO ()+type WithAppRunner = AppRunner -> IO ()++initAppHolder :: IO AppHolder+initAppHolder = do+    flag <- newEmptyMVar+    newMVar (initApp, flag)+  where+    initApp _ = return+              $ responseLBS status500 [("Content-Type", "text/plain")]+              $ "No app has yet been loaded"++swapAppSimple :: Application -> AppHolder -> IO ()+swapAppSimple app =+    swapApp war+  where+    war f = f app++swapApp :: WithAppRunner -> AppHolder -> IO ()+swapApp war ah = void $ forkIO $ war $ \app -> do+    (_, oldFlag) <- takeMVar ah+    -- allow the old app to cleanup+    putMVar oldFlag ()+    -- now place the new app into the AppHolder, waiting for a termination+    -- signal+    flag <- newEmptyMVar+    putMVar ah (app, flag)+    takeMVar flag -- this causes execution to hang until we are terminated+  where+    void x = x >> return ()++toApp :: AppHolder -> Application+toApp ah req = do+    (app, _) <- liftIO $ readMVar ah+    app req
Network/Wai/Handler/DevelServer.hs view
@@ -1,53 +1,60 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}-module Network.Wai.Handler.DevelServer (run, runNoWatch) where+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE StandaloneDeriving #-}+module Network.Wai.Handler.DevelServer+    ( run+    , runQuit+    , runNoWatch+    ) where -import Language.Haskell.Interpreter+import Language.Haskell.Interpreter hiding (typeOf) import Network.Wai -import qualified Data.ByteString.Lazy.UTF8 as U+import Data.Text.Lazy (pack)+import Data.Text.Lazy.Encoding (encodeUtf8) import qualified Data.ByteString.Lazy.Char8 as L8-import Network-    ( listenOn, accept, sClose, PortID(PortNumber), Socket-    , withSocketsDo)-import Control.Exception (bracket, finally, Exception,-                          SomeException, toException)+import Control.Exception (Exception, SomeException, toException) import qualified Control.Exception as E-import System.IO (Handle, hClose) import Control.Concurrent (forkIO, threadDelay) -import qualified Control.Concurrent.MVar as M-import qualified Control.Concurrent.Chan as C import System.Directory (getModificationTime)-import Network.Wai.Handler.SimpleServer (parseRequest, sendResponse)-import Control.Monad (filterM)--import Control.Arrow ((&&&))-import System.Directory (doesDirectoryExist,-                         doesFileExist, getDirectoryContents)-import Control.Applicative ((<$>))+import qualified Network.Wai.Handler.Warp as Warp+import Network.Wai.Application.Devel -import Data.List (nub)+import Data.List (nub, group, sort)+import System.Time (ClockTime)  type FunctionName = String -runNoWatch :: Port -> ModuleName -> FunctionName -> [FilePath] -> IO ()-runNoWatch port modu func dirs = do-    queue <- C.newChan-    mqueue <- M.newMVar queue-    startApp queue $ loadingApp Nothing-    reload modu func mqueue dirs Nothing-    run' port mqueue+runNoWatch :: Int -> ModuleName -> FunctionName+           -> (FilePath -> IO [FilePath]) -> IO ()+runNoWatch port modu func extras = do+    ah <- initAppHolder+    _ <- reload modu func extras Nothing ah+    Warp.run port $ toApp ah -run :: Port -> ModuleName -> FunctionName -> [FilePath] -> IO ()-run port modu func dirs = do-    queue <- C.newChan-    mqueue <- M.newMVar queue-    startApp queue $ loadingApp Nothing-    _ <- forkIO $ fillApp modu func mqueue dirs-    run' port mqueue+runQuit :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath])+        -> IO ()+runQuit port modu func extras = do+    _ <- forkIO $ run port modu func extras+    go+  where+    go = do+        x <- getLine+        case x of+            'q':_ -> putStrLn "Quitting, goodbye!"+            _ -> go +run :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath])+    -> IO ()+run port modu func extras = do+    ah <- initAppHolder+    _ <- forkIO $ fillApp modu func extras ah+    Warp.run port $ toApp ah++{- startApp :: Queue -> Handler -> IO () startApp queue withApp = do     forkIO (withApp go) >> return ()@@ -61,17 +68,23 @@                 go app     onErr :: SomeException -> IO Response     onErr e = return-            $ Response status500 [("Content-Type", "text/plain; charset=utf-8")]-            $ ResponseLBS $ U.fromString+            $ responseLBS+                status500+                [("Content-Type", "text/plain; charset=utf-8")]+            $ charsToLBS             $ "Exception thrown while running application\n\n" ++ show e     void x = x >> return ()+-} +getTimes :: [FilePath] -> IO [ClockTime] getTimes = E.handle (constSE $ return []) . mapM getModificationTime+ constSE :: x -> SomeException -> x constSE = const -fillApp :: String -> String -> M.MVar Queue -> [FilePath] -> IO ()-fillApp modu func mqueue dirs =+fillApp :: String -> String+        -> (FilePath -> IO [FilePath]) -> AppHolder -> IO ()+fillApp modu func dirs ah =     go Nothing []   where     go prevError prevFiles = do@@ -83,37 +96,41 @@                     return $ times /= map snd prevFiles         (newError, newFiles) <-             if toReload-                then reload modu func mqueue dirs prevError+                then reload modu func dirs prevError ah                 else return (prevError, prevFiles)         threadDelay 1000000         go newError newFiles --- reload :: String -> String -> M.MVar Queue -> [FilePath] -> Maybe SomeException -> IO (Maybe SomeException, [ClockTime])-reload modu func mqueue dirs prevError = do+reload :: String -> String+       -> (FilePath -> IO [FilePath])+       -> Maybe SomeException+       -> AppHolder+       -> IO (Maybe SomeException, [(FilePath, ClockTime)])+reload modu func extras prevError ah = do     case prevError of          Nothing -> putStrLn "Attempting to interpret your app..."          _       -> return ()-    loadingApp' prevError mqueue+    loadingApp' prevError ah     res <- theapp modu func     case res of         Left err -> do             if show (Just err) /= show prevError                then putStrLn $ "Compile failed: " ++ showInterpError err                else return ()-            loadingApp' (Just $ toException err) mqueue+            loadingApp' (Just $ toException err) ah             return (Just $ toException err, [])         Right (app, files') -> E.handle onInitErr $ do-            files'' <- mapM fileList dirs-            let files = concat $ files' : files''+            files'' <- mapM extras files'+            let files = map head $ group $ sort $ concat $ files' : files''             putStrLn "Interpreting success, new app loaded"             E.handle onInitErr $ do-                swapApp app mqueue+                swapApp app ah                 times <- getTimes files                 return (Nothing, zip files times)     where         onInitErr e = do             putStrLn $ "Error initializing application: " ++ show e-            loadingApp' (Just e) mqueue+            loadingApp' (Just e) ah             return (Just e, [])  showInterpError :: InterpreterError -> String@@ -121,49 +138,24 @@     concat . nub $ map (\(GhcError msg) -> '\n':'\n':msg) errs showInterpError err = show err -fileList :: FilePath -> IO [FilePath]-fileList top = do-    ex <- doesDirectoryExist top-    if ex then fileList' top "" else return []--fileList' :: FilePath -> FilePath -> IO [FilePath]-fileList' realTop top = do-    let prefix1 = top ++ "/"-        prefix2 = realTop ++ prefix1-    allContents <- filter notHidden <$> getDirectoryContents prefix2-    let all' = map ((++) prefix1 &&& (++) prefix2) allContents-    files <- map snd <$> filterM (doesFileExist . snd) all'-    dirs <- filterM (doesDirectoryExist . snd) all' >>=-            mapM (fileList' realTop . fst)-    return $ concat $ files : dirs--notHidden :: FilePath -> Bool-notHidden ('.':_) = False-notHidden _ = True--loadingApp' :: Maybe SomeException -> M.MVar Queue -> IO ()-loadingApp' err mqueue = swapApp (loadingApp err) mqueue--swapApp :: Handler -> M.MVar Queue -> IO ()-swapApp app mqueue = do-    oldqueue <- M.takeMVar mqueue-    C.writeChan oldqueue Nothing-    queue <- C.newChan-    M.putMVar mqueue queue-    startApp queue app+loadingApp' :: Maybe SomeException -> AppHolder -> IO ()+loadingApp' err = swapApp (loadingApp err)  loadingApp :: Maybe SomeException -> Handler loadingApp err f =-    f $ const $ return $ Response status200+    f $ const $ return $ responseLBS status200         ( ("Content-Type", "text/plain")         : case err of             Nothing -> [("Refresh", "1")]             Just _ -> []-        ) $ ResponseLBS $ L8.pack $ toMessage err+        ) $ toMessage err   where     toMessage Nothing = "Loading code changes, please wait"-    toMessage (Just err') = "Error loading code: " ++ show err'+    toMessage (Just err') = charsToLBS $ "Error loading code: " ++ show err' +charsToLBS :: String -> L8.ByteString+charsToLBS = encodeUtf8 . pack+ type Handler = (Application -> IO ()) -> IO ()  theapp :: String -> String -> IO (Either InterpreterError (Handler, [FilePath]))@@ -171,36 +163,10 @@     runInterpreter $ do         loadModules [modu]         mods <- getLoadedModules-        setImports ["Prelude", "Network.Wai", modu]+        setImports ["Prelude", "Network.Wai", "Data.Enumerator", "Data.ByteString.Internal", modu]         app <- interpret func infer         return (app, map toFile mods)   where     toFile s = map toSlash s ++ ".hs"     toSlash '.' = '/'     toSlash c   = c--run' :: Port -> M.MVar Queue -> IO ()-run' port = withSocketsDo .-    bracket-        (listenOn $ PortNumber $ fromIntegral port)-        sClose .-        serveConnections port-type Port = Int--serveConnections :: Port -> M.MVar Queue -> Socket -> IO ()-serveConnections port mqueue socket = do-    (conn, remoteHost', _) <- accept socket-    _ <- forkIO $ serveConnection port mqueue conn remoteHost'-    serveConnections port mqueue socket--type Queue = C.Chan (Maybe (Request, Response -> IO ()))--serveConnection :: Port -> M.MVar Queue -> Handle -> String -> IO ()-serveConnection port mqueue conn remoteHost' = do-    env <- parseRequest port conn remoteHost'-    let onRes res =-            finally-                (sendResponse (httpVersion env) conn res)-                (hClose conn)-    queue <- M.readMVar mqueue-    C.writeChan queue $ Just (env, onRes)
wai-handler-devel-unwatched.hs view
@@ -7,23 +7,19 @@     { port :: Int     , moduleName :: String     , function :: String-    , yesod :: Bool     }     deriving (Show, Data, Typeable)  main :: IO () main = do-    Devel p m f y <- cmdArgs Devel+    Devel p m f <- cmdArgs Devel         { port = 3000 &= argPos 0 &= typ "PORT"         , moduleName = "" &= argPos 1 &= typ "MODULE"         , function = "" &= argPos 2 &= typ "FUNCTION"-        , yesod = False &= help "Monitor typical Yesod folders (hamlet, etc)"         } &= summary "WAI development web server"-    _ <- forkIO $ runNoWatch p m f $ folders y+    _ <- forkIO $ runNoWatch p m f $ const $ return []     go   where-    folders False = []-    folders True = ["hamlet", "cassius", "julius"]     go = do         x <- getLine         case x of
wai-handler-devel.cabal view
@@ -1,5 +1,5 @@ Name:                wai-handler-devel-Version:             0.1.1.2+Version:             0.2.0 Synopsis:            WAI server that automatically reloads code after modification. Description:         This handler automatically reloads your source code upon any changes. It works by using the hint package, essentially embedding GHC inside the handler. The handler (both the executable and library) takes three arguments: the port to listen on, the module name containing the application function, and the name of the function.                      .@@ -21,15 +21,19 @@     location:        git://github.com/snoyberg/wai-handler-devel.git  Library-  Build-Depends:     base >= 3 && < 5-                   , wai >= 0.2.1 && < 0.3-                   , wai-extra >= 0.2.3 && < 0.3-                   , directory >= 1.0 && < 1.2-                   , network >= 2.2 && < 2.4-                   , bytestring >= 0.9 && < 0.10-                   , hint >= 0.3.2.3 && < 0.4-                   , utf8-string >= 0.3 && < 0.4+  Build-Depends:     base                  >= 4             && < 5+                   , wai                   >= 0.3           && < 0.4+                   , warp                  >= 0.3           && < 0.4+                   , directory             >= 1.0           && < 1.2+                   , network               >= 2.2           && < 2.4+                   , bytestring            >= 0.9           && < 0.10+                   , hint                  >= 0.3.2.3       && < 0.4+                   , text                  >= 0.7           && < 0.12+                   , old-time              >= 1.0           && < 1.1+                   , enumerator            >= 0.4.6         && < 0.5+                   , transformers          >= 0.2.2         && < 0.3   Exposed-modules:   Network.Wai.Handler.DevelServer+                     Network.Wai.Application.Devel   ghc-options:       -Wall  Executable           wai-handler-devel
wai-handler-devel.hs view
@@ -1,31 +1,19 @@ {-# LANGUAGE DeriveDataTypeable #-}-import Network.Wai.Handler.DevelServer (run)+import Network.Wai.Handler.DevelServer (runQuit) import System.Console.CmdArgs-import Control.Concurrent (forkIO)  data Devel = Devel     { port :: Int     , moduleName :: String     , function :: String-    , yesod :: Bool     }     deriving (Show, Data, Typeable)  main :: IO () main = do-    Devel p m f y <- cmdArgs Devel+    Devel p m f <- cmdArgs Devel         { port = 3000 &= argPos 0 &= typ "PORT"         , moduleName = "" &= argPos 1 &= typ "MODULE"         , function = "" &= argPos 2 &= typ "FUNCTION"-        , yesod = False &= help "Monitor typical Yesod folders (hamlet, etc)"         } &= summary "WAI development web server"-    _ <- forkIO $ run p m f $ folders y-    go-  where-    folders False = []-    folders True = ["hamlet", "cassius", "julius"]-    go = do-        x <- getLine-        case x of-            'q':_ -> putStrLn "Quitting, goodbye!"-            _ -> go+    runQuit p m f $ const $ return []