on master help sign in
Add 'wai-handler-devel/' from commit 'eb5fe4b63c3ef0e2baecd5c15f0a45f9de3e6796'

git-subtree-dir: wai-handler-devel git-subtree-mainline: 879d4a23047c3585e1cba4cdd7c3e8fc13e17592 git-subtree-split: eb5fe4b63c3ef0e2baecd5c15f0a45f9de3e6796

commit44589e3be4fc284043f4a8c31f9f5d9e57784556 authorMichael Snoyman <michael@snoyman.com> dateFri 22 Jul 2011, 16:49 (15 years ago) parent879d4a2eb5fe4b
15 files changed+486
wai-handler-devel/.gitignore+2 wai-handler-devel/Helper.hs+3 wai-handler-devel/LICENSE+25 wai-handler-devel/Network/Wai/Application/Devel.hs+59 wai-handler-devel/Network/Wai/Handler/DevelServer.hs+185 wai-handler-devel/README wai-handler-devel/Setup.lhs+11 wai-handler-devel/TestApp.hs+40 wai-handler-devel/Testapp.hs+9 wai-handler-devel/hamlet/testapp.hamlet+4 wai-handler-devel/run-testapp.hs+5 wai-handler-devel/testapp.hs+3 wai-handler-devel/wai-handler-devel-unwatched.hs+27 wai-handler-devel/wai-handler-devel.cabal+50 wai-handler-devel/wai-handler-devel.hs+63
wai-handler-devel/.gitignorenew file+2
@@ -0,0 +1,2 @@
1+*.swp
2+/dist/
wai-handler-devel/Helper.hsnew file+3
@@ -0,0 +1,3 @@
1+module Helper where
2+
3+db = "test.db3"
wai-handler-devel/LICENSEnew file+25
@@ -0,0 +1,25 @@
1+The following license covers this documentation, and the source code, except
2+where otherwise indicated.
3+
4+Copyright 2010, Michael Snoyman. All rights reserved.
5+
6+Redistribution and use in source and binary forms, with or without
7+modification, are permitted provided that the following conditions are met:
8+
9+* Redistributions of source code must retain the above copyright notice, this
10+ list of conditions and the following disclaimer.
11+
12+* Redistributions in binary form must reproduce the above copyright notice,
13+ this list of conditions and the following disclaimer in the documentation
14+ and/or other materials provided with the distribution.
15+
16+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
17+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
20+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
22+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
wai-handler-devel/Network/Wai/Application/Devel.hsnew file+59
@@ -0,0 +1,59 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Network.Wai.Application.Devel
3+ ( -- * Types
4+ AppHolder
5+ , AppRunner
6+ , WithAppRunner
7+ -- * Functions
8+ , initAppHolder
9+ , swapApp
10+ , swapAppSimple
11+ , toApp
12+ ) where
13+
14+import Control.Concurrent (forkIO)
15+import Control.Concurrent.MVar
16+ ( MVar, newEmptyMVar, newMVar
17+ , takeMVar, putMVar, readMVar
18+ )
19+import Network.Wai (Application, responseLBS)
20+import Network.HTTP.Types (status500)
21+import Data.ByteString.Lazy.Char8 ()
22+import Control.Monad.IO.Class (liftIO)
23+
24+type AppHolder = MVar (Application, MVar ())
25+type AppRunner = Application -> IO ()
26+type WithAppRunner = AppRunner -> IO ()
27+
28+initAppHolder :: IO AppHolder
29+initAppHolder = do
30+ flag <- newEmptyMVar
31+ newMVar (initApp, flag)
32+ where
33+ initApp _ = return
34+ $ responseLBS status500 [("Content-Type", "text/plain")]
35+ $ "No app has yet been loaded"
36+
37+swapAppSimple :: Application -> AppHolder -> IO ()
38+swapAppSimple app =
39+ swapApp war
40+ where
41+ war f = f app
42+
43+swapApp :: WithAppRunner -> AppHolder -> IO ()
44+swapApp war ah = void $ forkIO $ war $ \app -> do
45+ (_, oldFlag) <- takeMVar ah
46+ -- allow the old app to cleanup
47+ putMVar oldFlag ()
48+ -- now place the new app into the AppHolder, waiting for a termination
49+ -- signal
50+ flag <- newEmptyMVar
51+ putMVar ah (app, flag)
52+ takeMVar flag -- this causes execution to hang until we are terminated
53+ where
54+ void x = x >> return ()
55+
56+toApp :: AppHolder -> Application
57+toApp ah req = do
58+ (app, _) <- liftIO $ readMVar ah
59+ app req
wai-handler-devel/Network/Wai/Handler/DevelServer.hsnew file+185
@@ -0,0 +1,185 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+module Network.Wai.Handler.DevelServer
6+ ( run
7+ , runQuit
8+ , runNoWatch
9+ ) where
10+
11+import Language.Haskell.Interpreter hiding (typeOf)
12+import Network.Wai
13+import Network.HTTP.Types (status200)
14+
15+import Data.Text.Lazy (pack)
16+import Data.Text.Lazy.Encoding (encodeUtf8)
17+import qualified Data.ByteString.Lazy.Char8 as L8
18+import Control.Exception (Exception, SomeException, toException, fromException)
19+import qualified Control.Exception as E
20+import Control.Concurrent (forkIO, threadDelay)
21+
22+import Data.Maybe
23+import Control.Monad
24+import Control.Concurrent.MVar
25+
26+import System.Directory (getModificationTime)
27+import qualified Network.Wai.Handler.Warp as Warp
28+import Network.Wai.Application.Devel
29+import Network.Wai.Middleware.Debug (debug)
30+
31+import Data.List (nub, group, sort)
32+import System.Time (ClockTime)
33+
34+type FunctionName = String
35+
36+runNoWatch :: Int -> ModuleName -> FunctionName
37+ -> (FilePath -> IO [FilePath]) -> IO ()
38+runNoWatch port modu func extras = do
39+ ah <- initAppHolder
40+ _ <- reload modu func extras Nothing ah
41+ Warp.run port $ toApp ah
42+
43+runQuit :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath])
44+ -> IO ()
45+runQuit port modu func extras = do
46+ sig <- newEmptyMVar
47+ _ <- forkIO $ run port modu func extras (Just sig)
48+ go sig
49+ where
50+ go sig = do
51+ x <- getLine
52+ case x of
53+ 'q':_ -> putStrLn "Quitting, goodbye!"
54+ 'r':_ -> do
55+ putStrLn "Forcing reinterpretation"
56+ _ <- tryPutMVar sig ()
57+ go sig
58+ _ -> go sig
59+
60+run :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath]) -> Maybe (MVar ())
61+ -> IO ()
62+run port modu func extras msig = do
63+ ah <- initAppHolder
64+ _ <- forkIO $ fillApp modu func extras ah msig
65+ Warp.run port $ toApp ah
66+
67+{-
68+startApp :: Queue -> Handler -> IO ()
69+startApp queue withApp = do
70+ forkIO (withApp go) >> return ()
71+ where
72+ go app = do
73+ msession <- C.readChan queue
74+ case msession of
75+ Nothing -> return ()
76+ Just (req, onRes) -> do
77+ void $ forkIO $ (E.handle onErr $ app req) >>= onRes
78+ go app
79+ onErr :: SomeException -> IO Response
80+ onErr e = return
81+ $ responseLBS
82+ status500
83+ [("Content-Type", "text/plain; charset=utf-8")]
84+ $ charsToLBS
85+ $ "Exception thrown while running application\n\n" ++ show e
86+ void x = x >> return ()
87+-}
88+
89+getTimes :: [FilePath] -> IO [ClockTime]
90+getTimes = E.handle (constSE $ return []) . mapM getModificationTime
91+
92+constSE :: x -> SomeException -> x
93+constSE = const
94+
95+fillApp :: String -> String
96+ -> (FilePath -> IO [FilePath]) -> AppHolder -> Maybe (MVar ()) -> IO ()
97+fillApp modu func dirs ah msig =
98+ go Nothing []
99+ where
100+ go prevError prevFiles = do
101+ forceReload <- maybe (return False) (fmap isJust . tryTakeMVar) msig
102+ toReload <-
103+ if forceReload || null prevFiles
104+ then return True
105+ else do
106+ times <- getTimes $ map fst prevFiles
107+ return $ times /= map snd prevFiles
108+ (newError, newFiles) <-
109+ if toReload
110+ then reload modu func dirs prevError ah
111+ else return (prevError, prevFiles)
112+ threadDelay 1000000
113+ go newError newFiles
114+
115+reload :: String -> String
116+ -> (FilePath -> IO [FilePath])
117+ -> Maybe SomeException
118+ -> AppHolder
119+ -> IO (Maybe SomeException, [(FilePath, ClockTime)])
120+reload modu func extras prevError ah = do
121+ case prevError of
122+ Nothing -> putStrLn "Attempting to interpret your app..."
123+ _ -> return ()
124+ loadingApp' prevError ah
125+ res <- theapp modu func
126+ case res of
127+ Left err -> do
128+ when (show (Just err) /= show prevError) $
129+ putStrLn $ "Compile failed: " ++ showInterpError err
130+ loadingApp' (Just $ toException err) ah
131+ return (Just $ toException err, [])
132+ Right (app, files') -> E.handle onInitErr $ do
133+ files'' <- mapM extras files'
134+ let files = map head $ group $ sort $ concat $ files' : files''
135+ putStrLn "Interpreting success, new app loaded"
136+ E.handle onInitErr $ do
137+ swapApp (\f -> app $ f . debug) ah
138+ times <- getTimes files
139+ return (Nothing, zip files times)
140+ where
141+ onInitErr e = do
142+ putStrLn $ "Error initializing application: " ++ show e
143+ loadingApp' (Just e) ah
144+ return (Just e, [])
145+
146+showInterpError :: InterpreterError -> String
147+showInterpError (WontCompile errs) =
148+ concat . nub $ map (\(GhcError msg) -> '\n':'\n':msg) errs
149+showInterpError err = show err
150+
151+loadingApp' :: Maybe SomeException -> AppHolder -> IO ()
152+loadingApp' err = swapApp (loadingApp err)
153+
154+loadingApp :: Maybe SomeException -> Handler
155+loadingApp err f =
156+ f $ const $ return $ responseLBS status200
157+ ( ("Content-Type", "text/plain")
158+ : case err of
159+ Nothing -> [("Refresh", "1")]
160+ Just _ -> []
161+ ) $ toMessage err
162+ where
163+ toMessage Nothing = "Loading code changes, please wait"
164+ toMessage (Just err') = charsToLBS $ "Error loading code: " ++
165+ (case fromException err' of
166+ Just e -> showInterpError e
167+ Nothing -> show err')
168+
169+charsToLBS :: String -> L8.ByteString
170+charsToLBS = encodeUtf8 . pack
171+
172+type Handler = (Application -> IO ()) -> IO ()
173+
174+theapp :: String -> String -> IO (Either InterpreterError (Handler, [FilePath]))
175+theapp modu func =
176+ runInterpreter $ do
177+ loadModules [modu]
178+ mods <- getLoadedModules
179+ setImports ["Prelude", "Network.Wai", "Data.Enumerator", "Data.ByteString.Internal", modu]
180+ app <- interpret func infer
181+ return (app, map toFile mods)
182+ where
183+ toFile s = map toSlash s ++ ".hs"
184+ toSlash '.' = '/'
185+ toSlash c = c
wai-handler-devel/READMEnew file

No changes to the content.

wai-handler-devel/Setup.lhsnew file+11
@@ -0,0 +1,11 @@
1+#!/usr/bin/env runhaskell
2+
3+> module Main where
4+> import Distribution.Simple
5+> import System.Cmd (system)
6+
7+> main :: IO ()
8+> main = defaultMainWithHooks (simpleUserHooks { runTests = runTests' })
9+
10+> runTests' :: a -> b -> c -> d -> IO ()
11+> runTests' _ _ _ _ = system "runhaskell -DTEST runtests.hs" >> return ()
wai-handler-devel/TestApp.hsnew file+40
@@ -0,0 +1,40 @@
1+{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+module TestApp (testApp) where
5+
6+import Network.Wai
7+import Data.ByteString.Lazy.Char8 (pack)
8+import Database.Persist.Sqlite
9+import System.Directory
10+import Control.Monad (when)
11+import Helper
12+import Text.Hamlet
13+
14+mkPersist [$persist|
15+Dummy
16+ dummy String
17+|]
18+
19+testApp handler = do
20+ putStrLn "testApp called, this should happen only once per reload"
21+ -- Swap between the following two lines as necessary to generate errors
22+ exi <- doesFileExist db
23+ --let exi = True
24+ when exi $ removeFile db
25+ withSqlitePool db 10 $ \pool -> do
26+ flip runSqlPool pool $ runMigration $ migrate $ Dummy ""
27+ handler $ \req -> do
28+ if pathInfo req == "/favicon.ico"
29+ then return $ responseLBS status301 [("Location", "http://docs.yesodweb.com/favicon.ico")]
30+ $ pack ""
31+ else do
32+ print $ pathInfo req
33+ x <- flip runSqlPool pool $ do
34+ insert $ Dummy ""
35+ count ([] :: [Filter Dummy])
36+ return $ responseLBS status200
37+ [("Content-Type", "text/html; charset=utf-8")]
38+ $ renderHamlet id
39+ $(hamletFile "hamlet/testapp.hamlet")
40+ putStrLn "handler completed, this should only happen at the beginning of a reload"
wai-handler-devel/Testapp.hsnew file+9
@@ -0,0 +1,9 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Testapp where
3+
4+import Network.Wai
5+import Data.ByteString.Lazy.Char8 ()
6+
7+testapp' _ = return $ responseLBS status200 [("Content-Type", "text/html")] "test"
8+
9+testapp f = f testapp'
wai-handler-devel/hamlet/testapp.hamletnew file+4
@@ -0,0 +1,4 @@
1+%h1 Hello World
2+%p Counter: $show x$
3+%p The database file is $db$
4+%p שלום
wai-handler-devel/run-testapp.hsnew file+5
@@ -0,0 +1,5 @@
1+import Network.Wai.Handler.DevelServer (runQuit)
2+import Data.Enumerator (Iteratee)
3+import Data.ByteString (ByteString)
4+
5+main = runQuit 3000 "TestApp2" "testapp" (const $ return ["hamlet/testapp.hamlet"])
wai-handler-devel/testapp.hsnew file+3
@@ -0,0 +1,3 @@
1+import Network.Wai.Handler.DevelServer (runQuit)
2+
3+main = runQuit 3000 "Testapp" "testapp" (const $ return [])
wai-handler-devel/wai-handler-devel-unwatched.hsnew file+27
@@ -0,0 +1,27 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+import Network.Wai.Handler.DevelServer (runNoWatch)
3+import System.Console.CmdArgs
4+import Control.Concurrent (forkIO)
5+
6+data Devel = Devel
7+ { port :: Int
8+ , moduleName :: String
9+ , function :: String
10+ }
11+ deriving (Show, Data, Typeable)
12+
13+main :: IO ()
14+main = do
15+ Devel p m f <- cmdArgs Devel
16+ { port = 3000 &= argPos 0 &= typ "PORT"
17+ , moduleName = "" &= argPos 1 &= typ "MODULE"
18+ , function = "" &= argPos 2 &= typ "FUNCTION"
19+ } &= summary "WAI development web server"
20+ _ <- forkIO $ runNoWatch p m f $ const $ return []
21+ go
22+ where
23+ go = do
24+ x <- getLine
25+ case x of
26+ 'q':_ -> putStrLn "Quitting, goodbye!"
27+ _ -> go
wai-handler-devel/wai-handler-devel.cabalnew file+50
@@ -0,0 +1,50 @@
1+Name: wai-handler-devel
2+Version: 0.4.3
3+Synopsis: WAI server that automatically reloads code after modification.
4+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.
5+ .
6+ One major note: the type of the application is most likely not what you expect. A common case for WAI applications is having a withApplication function, and this library assumes this is the case. Therefore, the type signature of your application function must be:
7+ .
8+ > withYourApp :: (Application -> IO ()) -> IO ()
9+License: BSD3
10+License-file: LICENSE
11+Author: Michael Snoyman
12+Maintainer: michael@snoyman.com
13+Homepage: http://github.com/snoyberg/wai-handler-devel
14+Category: Web
15+Build-Type: Simple
16+Cabal-Version: >=1.6
17+Stability: Stable
18+
19+Source-repository head
20+ type: git
21+ location: git://github.com/snoyberg/wai-handler-devel.git
22+
23+Library
24+ Build-Depends: base >= 4 && < 5
25+ , wai >= 0.4 && < 0.5
26+ , wai-extra >= 0.4 && < 0.5
27+ , http-types >= 0.6 && < 0.7
28+ , warp >= 0.4 && < 0.5
29+ , directory >= 1.0 && < 1.2
30+ , network >= 2.2 && < 2.4
31+ , bytestring >= 0.9 && < 0.10
32+ , hint >= 0.3.2.3 && < 0.4
33+ , text >= 0.7 && < 0.12
34+ , old-time >= 1.0 && < 1.1
35+ , enumerator >= 0.4.6 && < 0.5
36+ , transformers >= 0.2.2 && < 0.3
37+ , attoparsec-text >= 0.8.2 && < 0.9
38+ Exposed-modules: Network.Wai.Handler.DevelServer
39+ Network.Wai.Application.Devel
40+ ghc-options: -Wall
41+
42+Executable wai-handler-devel
43+ Build-Depends: cmdargs >= 0.4 && < 0.8
44+ ghc-options: -Wall -O2 -threaded
45+ main-is: wai-handler-devel.hs
46+
47+Executable wai-handler-devel-unwatched
48+ Build-Depends: cmdargs >= 0.4 && < 0.8
49+ ghc-options: -Wall -O2 -threaded
50+ main-is: wai-handler-devel-unwatched.hs
wai-handler-devel/wai-handler-devel.hsnew file+63
@@ -0,0 +1,63 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+import Network.Wai.Handler.DevelServer (runQuit)
4+import System.Console.CmdArgs
5+import qualified Data.Attoparsec.Text.Lazy as A
6+import qualified Data.Text.Lazy.IO as TIO
7+import Control.Applicative ((<|>))
8+import Data.Char (isSpace)
9+import Data.Maybe (mapMaybe)
10+
11+data Devel = Devel
12+ { port :: Int
13+ , moduleName :: String
14+ , function :: String
15+ , yesod :: Bool
16+ }
17+ deriving (Show, Data, Typeable)
18+
19+main :: IO ()
20+main = do
21+ Devel p m f y <- cmdArgs Devel
22+ { port = 3000 &= argPos 0 &= typ "PORT"
23+ , moduleName = "" &= argPos 1 &= typ "MODULE"
24+ , function = "" &= argPos 2 &= typ "FUNCTION"
25+ , yesod = False
26+ } &= summary "WAI development web server"
27+ runQuit p m f $ if y then determineHamletDeps else (const $ return [])
28+
29+data TempType = Hamlet | Cassius | Lucius | Julius | Widget | Verbatim
30+ deriving Show
31+
32+determineHamletDeps :: FilePath -> IO [FilePath]
33+determineHamletDeps x = do
34+ y <- TIO.readFile x
35+ let z = A.parse (A.many $ (parser <|> (A.anyChar >> return Nothing))) y
36+ case z of
37+ A.Fail{} -> return []
38+ A.Done _ r -> return $ mapMaybe go r
39+ where
40+ go (Just (Hamlet, f)) = Just $ "hamlet/" ++ f ++ ".hamlet"
41+ go (Just (Widget, f)) = Just $ "hamlet/" ++ f ++ ".hamlet"
42+ go (Just (Verbatim, f)) = Just f
43+ go _ = Nothing
44+ parser = do
45+ ty <- (A.string "$(hamletFile " >> return Hamlet)
46+ <|> (A.string "$(cassiusFile " >> return Cassius)
47+ <|> (A.string "$(luciusFile " >> return Lucius)
48+ <|> (A.string "$(juliusFile " >> return Julius)
49+ <|> (A.string "$(widgetFile " >> return Widget)
50+ <|> (A.string "$(Settings.hamletFile " >> return Hamlet)
51+ <|> (A.string "$(Settings.cassiusFile " >> return Cassius)
52+ <|> (A.string "$(Settings.luciusFile " >> return Lucius)
53+ <|> (A.string "$(Settings.juliusFile " >> return Julius)
54+ <|> (A.string "$(Settings.widgetFile " >> return Widget)
55+ <|> (A.string "$(persistFile " >> return Verbatim)
56+ <|> (A.string "$(parseRoutesFile " >> return Verbatim)
57+ A.skipWhile isSpace
58+ _ <- A.char '"'
59+ y <- A.many1 $ A.satisfy (/= '"')
60+ _ <- A.char '"'
61+ A.skipWhile isSpace
62+ _ <- A.char ')'
63+ return $ Just (ty, y)