This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "snap".

The branch, master has been updated
       via  25b4ea0d61e60b2fb5ba80c388f88441de5f0bac (commit)
      from  71266cfb01d06ee21eeb896763166de1ef3f27b9 (commit)


Summary of changes:
 project_template/default/foo.cabal             |    5 +-
 project_template/default/src/Main.hs           |   86 +++++++++++-----------
 project_template/default/src/Site.hs           |    2 +-
 snap.cabal                                     |    3 +-
 src/Snap/Extension/Loader/Devel.hs             |   92 +++++++++++++-----------
 src/Snap/Extension/Loader/Devel/Evaluator.hs   |    2 +-
 src/Snap/Extension/Loader/Devel/TreeWatcher.hs |   46 ++++++++++++
 7 files changed, 145 insertions(+), 91 deletions(-)
 create mode 100644 src/Snap/Extension/Loader/Devel/TreeWatcher.hs

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 25b4ea0d61e60b2fb5ba80c388f88441de5f0bac
Author: Carl Howells <[email protected]>
Date:   Sat Dec 25 16:27:11 2010 -0800

    Explicitly watch for changes before recompiling

diff --git a/project_template/default/foo.cabal 
b/project_template/default/foo.cabal
index 778f768..e4ecb0b 100644
--- a/project_template/default/foo.cabal
+++ b/project_template/default/foo.cabal
@@ -18,9 +18,8 @@ Executable projname
   hs-source-dirs: src
   main-is: Main.hs
 
-  if !flag(development)
-    cpp-options: -DPRODUCTION
-  else
+  if flag(development)
+    cpp-options: -DDEVELOPMENT
     build-depends: hint >= 0.3.2 && < 0.4
 
   Build-depends:
diff --git a/project_template/default/src/Main.hs 
b/project_template/default/src/Main.hs
index c1fe8dc..332ccc8 100644
--- a/project_template/default/src/Main.hs
+++ b/project_template/default/src/Main.hs
@@ -3,61 +3,63 @@
 
 {-|
 
-This is the entry point for this web server application.  It supports easily
-switching between interpreting source and running statically compiled code.
-
-In either mode, the generated program should be run from the root of the
-project tree.  It locates its templates, static content, and source files in 
-development mode, relative to the current working directory when it is run.
-
-When compiled without the production flag, only changes to the libraries, your
-cabal file, or this file should require a recompile to be picked up.
-Everything else is interpreted at runtime.  There are a few consequences of
-this.
-
-First, this is much slower.  Running the interpreter seems to take about
-300ms, regardless of the simplicity of the loaded code.  The results of the
-interpreter process are cached for a few seconds, to hopefully ensure that
-the the interpreter is only invoked once for each load of a page and the
-resources it depends on.
-
-Second, the generated server binary is MUCH larger, since it links in the GHC
-API (via the hint library).
-
-Third, it results in initialization\/cleanup code defined by the @Initializer@
-being called for each request.  This is to ensure that the current state is
-compatible with the running action.  If your application state takes a long
-time to load or clean up, the penalty will be visible.
-
-Fourth, and the reason you would ever want to actually compile without
-production mode, is that it enables a *much* faster development cycle. You can
-simply edit a file, save your changes, and hit reload to see your changes
-reflected immediately.
-
-When this is compiled with the production flag, all the actions are statically
-compiled in.  This results in much faster execution, a smaller binary size,
-only running initialization and cleanup once per application run, and having
-to recompile the server for any code change.
+This is the entry point for this web server application.  It supports
+easily switching between interpreting source and running statically
+compiled code.
+
+In either mode, the generated program should be run from the root of
+the project tree.  When it is run, it locates its templates, static
+content, and source files in development mode, relative to the current
+working directory.
+
+When compiled with the development flag, only changes to the
+libraries, your cabal file, or this file should require a recompile to
+be picked up.  Everything else is interpreted at runtime.  There are a
+few consequences of this.
+
+First, this is much slower.  Running the interpreter takes a
+significant chunk of time (a couple tenths of a second on the author's
+machine, at this time), regardless of the simplicity of the loaded
+code.  In order to recompile and re-load server state as infrequently
+as possible, the source directories are watched for updates, as are
+any extra directories specified below.
+
+Second, the generated server binary is MUCH larger, since it links in
+the GHC API (via the hint library).
+
+Third, and the reason you would ever want to actually compile with
+development mode, is that it enables a faster development cycle. You
+can simply edit a file, save your changes, and hit reload to see your
+changes reflected immediately.
+
+When this is compiled without the development flag, all the actions
+are statically compiled in.  This results in faster execution, a
+smaller binary size, and having to recompile the server for any code
+change.
 
 -}
 
 module Main where
 
-#ifdef PRODUCTION
-import           Snap.Extension.Server
-#else
+#ifdef DEVELOPMENT
 import           Snap.Extension.Loader.Devel
 import           Snap.Http.Server (quickHttpServe)
+#else
+import           Snap.Extension.Server
 #endif
 
 import           Application
 import           Site
 
 main :: IO ()
-#ifdef PRODUCTION
-main = quickHttpServe applicationInitializer site
-#else
+#ifdef DEVELOPMENT
 main = do
-    snap <- $(loadSnapTH 'applicationInitializer 'site)
+    -- All source directories will be watched for updates
+    -- automatically.  If any extra directories should be watched for
+    -- updates, include them here.
+    snap <- $(let extraWatcheDirs = ["resources/templates"]
+              in loadSnapTH 'applicationInitializer 'site extraWatcheDirs)
     quickHttpServe snap
+#else
+main = quickHttpServe applicationInitializer site
 #endif
diff --git a/project_template/default/src/Site.hs 
b/project_template/default/src/Site.hs
index e60c894..b59f647 100644
--- a/project_template/default/src/Site.hs
+++ b/project_template/default/src/Site.hs
@@ -31,7 +31,7 @@ import           Application
 index :: Application ()
 index = ifTop $ heistLocal (bindSplices indexSplices) $ render "index"
   where
-    indexSplices = 
+    indexSplices =
         [ ("start-time",   startTimeSplice)
         , ("current-time", currentTimeSplice)
         ]
diff --git a/snap.cabal b/snap.cabal
index 3826833..f9916e9 100644
--- a/snap.cabal
+++ b/snap.cabal
@@ -43,7 +43,8 @@ Library
 
   other-modules:
     Snap.Extension.Loader.Devel.Evaluator,
-    Snap.Extension.Loader.Devel.Signal
+    Snap.Extension.Loader.Devel.Signal,
+    Snap.Extension.Loader.Devel.TreeWatcher
 
   build-depends:
     base >= 4 && < 5,
diff --git a/src/Snap/Extension/Loader/Devel.hs 
b/src/Snap/Extension/Loader/Devel.hs
index 7111da9..1afd29b 100644
--- a/src/Snap/Extension/Loader/Devel.hs
+++ b/src/Snap/Extension/Loader/Devel.hs
@@ -9,6 +9,8 @@ module Snap.Extension.Loader.Devel
   ( loadSnapTH
   ) where
 
+import           Control.Monad (liftM2)
+
 import           Data.List (groupBy, intercalate, isPrefixOf, nub)
 import           Data.Maybe (catMaybes)
 import           Data.Time.Clock (diffUTCTime, getCurrentTime)
@@ -25,6 +27,7 @@ import           Snap.Types
 import           Snap.Extension (runInitializerWithoutReloadAction)
 import           Snap.Extension.Loader.Devel.Signal
 import           Snap.Extension.Loader.Devel.Evaluator
+import           Snap.Extension.Loader.Devel.TreeWatcher
 
 ------------------------------------------------------------------------------
 -- | This function derives all the information necessary to use the
@@ -33,20 +36,15 @@ import           Snap.Extension.Loader.Devel.Evaluator
 --
 -- This could be considered a TH wrapper around a function
 --
--- > loadSnap :: Initializer s -> SnapExtend s () -> IO (Snap ())
+-- > loadSnap :: Initializer s -> SnapExtend s () -> [String] -> IO (Snap ())
 --
 -- with a magical implementation.
 --
--- The returned Snap action runs the 'Initializer', runs the 'Snap' handler,
--- and does the cleanup.  This means that the whole application state will be
--- loaded and unloaded for each request.  To make this worthwhile, those steps
--- should be made quite fast.
---
 -- The upshot is that you shouldn't need to recompile your server
 -- during development unless your .cabal file changes, or the code
 -- that uses this splice changes.
-loadSnapTH :: Name -> Name -> Q Exp
-loadSnapTH initializer action = do
+loadSnapTH :: Name -> Name -> [String] -> Q Exp
+loadSnapTH initializer action additionalWatchDirs = do
     args <- runIO getArgs
 
     let initMod = nameModule initializer
@@ -54,14 +52,15 @@ loadSnapTH initializer action = do
         actMod = nameModule action
         actBase = nameBase action
 
-        modules = catMaybes [initMod, actMod]
         opts = getHintOpts args
+        modules = catMaybes [initMod, actMod]
+        srcPaths = additionalWatchDirs ++ getSrcPaths args
 
     -- The let in this block causes an extra static type check that the
     -- types of the names passed in were correct at compile time.
     [| let _ = runInitializerWithoutReloadAction $(varE initializer)
                                                  $(varE action)
-       in hintSnap opts modules initBase actBase |]
+       in hintSnap opts modules srcPaths initBase actBase |]
 
 
 ------------------------------------------------------------------------------
@@ -90,6 +89,14 @@ getHintOpts args = removeBad opts
 
 
 ------------------------------------------------------------------------------
+-- | This function extracts the source paths from the compilation args
+getSrcPaths :: [String] -> [String]
+getSrcPaths = filter (not . null) . map (drop 2) . filter srcArg
+  where
+    srcArg x = "-i" `isPrefixOf` x && not ("-idist" `isPrefixOf` x)
+
+
+------------------------------------------------------------------------------
 -- | This function creates the Snap handler that actually is
 -- responsible for doing the dynamic loading of actions via hint,
 -- given all of the configuration information that the interpreter
@@ -97,12 +104,6 @@ getHintOpts args = removeBad opts
 -- and caches the interpreter results for a short time before allowing
 -- it to run again.
 --
--- This constructs an expression of type Snap (), that is essentially
---
--- > bracketSnap initialization cleanup handler
---
--- for the values of initialization, cleanup, and handler passed in.
---
 -- Generally, this won't be called manually.  Instead, loadSnapTH will
 -- generate a call to it at compile-time, calculating all the
 -- arguments from its environment.
@@ -112,36 +113,41 @@ hintSnap :: [String] -- ^ A list of command-line options 
for the interpreter
                      -- modules which contain the initialization,
                      -- cleanup, and handler actions.  Everything else
                      -- they require will be loaded transitively.
+         -> [String] -- ^ A list of paths to watch for updates
          -> String   -- ^ The name of the initializer action
          -> String   -- ^ The name of the SnapExtend action
          -> IO (Snap ())
-hintSnap opts modules initialization handler = do
-    let action = intercalate " " [ "runInitializerWithoutReloadAction"
-                                 , initialization
-                                 , handler
-                                 ]
-        interpreter = do
-            loadModules . nub $ modules
-            let imports = "Prelude" :
-                          "Snap.Extension" :
-                          "Snap.Types" :
-                          modules
-            setImports . nub $ imports
-
-            interpret action (as :: HintLoadable)
-
-        loadInterpreter = unsafeRunInterpreterWithArgs opts interpreter
-
-        formatOnError (Left err) = error $ format err
-        formatOnError (Right a) = a
-
-        loader = formatOnError `fmap` protectHandlers loadInterpreter
-
-        test prevTime = do
-            now <- getCurrentTime
-            return $ diffUTCTime now prevTime < 4
-
-    protectedHintEvaluator getCurrentTime test loader
+hintSnap opts modules srcPaths initialization handler =
+    protectedHintEvaluator initialize test loader
+  where
+    action = intercalate " " [ "runInitializerWithoutReloadAction"
+                             , initialization
+                             , handler
+                             ]
+    interpreter = do
+        loadModules . nub $ modules
+        let imports = "Prelude" :
+                      "Snap.Extension" :
+                      "Snap.Types" :
+                      modules
+        setImports . nub $ imports
+
+        interpret action (as :: HintLoadable)
+
+    loadInterpreter = unsafeRunInterpreterWithArgs opts interpreter
+
+    formatOnError (Left err) = error $ format err
+    formatOnError (Right a) = a
+
+    loader = formatOnError `fmap` protectHandlers loadInterpreter
+
+    initialize = liftM2 (,) getCurrentTime $ getTreeStatus srcPaths
+
+    test (prevTime, ts) = do
+        now <- getCurrentTime
+        if diffUTCTime now prevTime < 3
+            then return True
+            else checkTreeStatus ts
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Snap/Extension/Loader/Devel/Evaluator.hs 
b/src/Snap/Extension/Loader/Devel/Evaluator.hs
index b62762c..01b1c16 100644
--- a/src/Snap/Extension/Loader/Devel/Evaluator.hs
+++ b/src/Snap/Extension/Loader/Devel/Evaluator.hs
@@ -38,7 +38,7 @@ type HintLoadable = IO (Snap (), IO ())
 --
 -- If an exception is raised during the processing of the action, it
 -- will be thrown to all waiting threads, and for all requests made
--- before the delay time has expired after the exception was raised.
+-- before the recompile condition is reached.
 protectedHintEvaluator :: forall a.
                           IO a
                        -> (a -> IO Bool)
diff --git a/src/Snap/Extension/Loader/Devel/TreeWatcher.hs 
b/src/Snap/Extension/Loader/Devel/TreeWatcher.hs
new file mode 100644
index 0000000..afdcaaf
--- /dev/null
+++ b/src/Snap/Extension/Loader/Devel/TreeWatcher.hs
@@ -0,0 +1,46 @@
+module Snap.Extension.Loader.Devel.TreeWatcher
+    ( TreeStatus
+    , getTreeStatus
+    , checkTreeStatus
+    ) where
+
+import Control.Applicative
+
+import System.Directory
+import System.Directory.Tree
+
+import System.Time
+
+
+------------------------------------------------------------------------------
+-- | An opaque representation of the contents and last modification
+-- times of a forest of directory trees.
+data TreeStatus = TS [FilePath] [AnchoredDirTree ClockTime]
+
+
+------------------------------------------------------------------------------
+-- | Create a 'TreeStatus' for later checking with 'checkTreeStatus'
+getTreeStatus :: [FilePath] -> IO TreeStatus
+getTreeStatus = liftA2 (<$>) TS readModificationTimes
+
+
+------------------------------------------------------------------------------
+-- | Checks that all the files present in the initial set of paths are
+-- the exact set of files currently present, with unchanged modifcations times
+checkTreeStatus :: TreeStatus -> IO Bool
+checkTreeStatus (TS paths entries) = check <$> readModificationTimes paths
+  where
+    check = and . zipWith adtEq entries
+    adtEq (n1 :/ dt1) (n2 :/ dt2) = n1 == n2 && dtEq dt1 dt2
+
+    dtEq (Dir n1 d1) (Dir n2 d2) = n1 == n2 && and (zipWith dtEq d1 d2)
+    dtEq (File n1 t1) (File n2 t2) = n1 == n2 && t1 == t2
+    dtEq _ _ = False
+
+
+------------------------------------------------------------------------------
+-- | This is the core of the functions in this module.  It converts a
+-- list of filepaths into a list of 'AnchoredDirTree' annotated with
+-- the modification times of the files located in those paths.
+readModificationTimes :: [FilePath] -> IO [AnchoredDirTree ClockTime]
+readModificationTimes = mapM $ readDirectoryWith getModificationTime
-----------------------------------------------------------------------


hooks/post-receive
-- 
snap
_______________________________________________
Snap mailing list
[email protected]
http://mailman-mail5.webfaction.com/listinfo/snap

Reply via email to