{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- | CLI interface for Rib. -- -- Typically you would call `Rib.App.run` passing your Shake build action. module Rib.App ( App(..) , run , runWith ) where import Control.Concurrent (threadDelay) import Control.Concurrent.Async (concurrently_) import Control.Monad import Data.Bool (bool) import Development.Shake import Development.Shake.Forward (shakeForward) import System.Console.CmdArgs import System.FSNotify (watchTree, withManager) import qualified Rib.Server as Server import Rib.Shake (Dirs (..)) -- | Application modes -- -- The mode in which to run the Rib CLI data App = Generate { force :: Bool -- ^ Force generation of /all/ files } -- ^ Generate static files once. | WatchAndGenerate -- ^ Watch for changes in the input directory and run `Generate` | Serve { port :: Int -- ^ Port to bind the server , dontWatch :: Bool -- ^ Unless set run `WatchAndGenerate` automatically } -- ^ Run a HTTP server serving content from the output directory deriving (Data,Typeable,Show,Eq) -- | Run Rib using arguments passed in the command line. run :: FilePath -- ^ Directory from which source content will be read. -- -- NOTE: This should ideally *not* be `"."` as our use of watchTree (of -- `runWith`) can interfere with Shake's file scaning. -> FilePath -- ^ The path where static files will be generated. Rib's server uses this -- directory when serving files. -> Action () -- ^ Shake build rules for building the static site -> IO () run src dst buildAction = runWith src dst buildAction =<< cmdArgs ribCli where ribCli = modes [ Serve { port = 8080 &= help "Port to bind to" , dontWatch = False &= help "Do not watch in addition to serving generated files" } &= help "Serve the generated site" &= auto , WatchAndGenerate &= help "Watch for changes and generate" , Generate { force = False &= help "Force generation of all files" } &= help "Generate the site" ] -- | Like `run` but with an explicitly passed `App` mode runWith :: FilePath -> FilePath -> Action () -> App -> IO () runWith src dst buildAction = \case WatchAndGenerate -> withManager $ \mgr -> do -- Begin with a *full* generation as the HTML layout may have been changed. runWith src dst buildAction $ Generate True -- And then every time a file changes under the current directory putStrLn $ "[Rib] Watching " <> src void $ watchTree mgr src (const True) $ const $ runWith src dst buildAction $ Generate False -- Wait forever, effectively. forever $ threadDelay maxBound Serve p dw -> concurrently_ (unless dw $ runWith src dst buildAction WatchAndGenerate) (Server.serve p dst) Generate forceGen -> let opts = shakeOptions { shakeVerbosity = Chatty , shakeRebuild = bool [] [(RebuildNow, "**")] forceGen , shakeExtra = addShakeExtra (Dirs (src, dst)) (shakeExtra shakeOptions) } in shakeForward opts buildAction