diff --git a/hakyll.cabal b/hakyll.cabal index d4034fc..bea4077 100644 --- a/hakyll.cabal +++ b/hakyll.cabal @@ -93,7 +93,7 @@ Library Hakyll Hakyll.Core.Compiler Hakyll.Core.Configuration - Hakyll.Core.DependencyAnalyzer + Hakyll.Core.Dependencies Hakyll.Core.Identifier Hakyll.Core.Identifier.Pattern Hakyll.Core.Logger diff --git a/src/Hakyll/Core/Run.hs b/src/Hakyll/Core/Run.hs deleted file mode 100644 index adbdb60..0000000 --- a/src/Hakyll/Core/Run.hs +++ /dev/null @@ -1,189 +0,0 @@ --------------------------------------------------------------------------------- --- | This is the module which binds it all together -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE OverloadedStrings #-} -module Hakyll.Core.Run - ( run - ) where - - --------------------------------------------------------------------------------- -import Control.Applicative (Applicative, (<$>)) -import Control.DeepSeq (deepseq) -import Control.Monad (filterM, forM_) -import Control.Monad.Error (ErrorT, runErrorT, throwError) -import Control.Monad.Reader (ReaderT, ask, runReaderT) -import Control.Monad.Trans (liftIO) -import Data.Map (Map) -import qualified Data.Map as M -import Data.Monoid (mempty) -import qualified Data.Set as S -import Prelude hiding (reverse) -import System.FilePath (()) - - --------------------------------------------------------------------------------- -import Hakyll.Core.CompiledItem -import Hakyll.Core.Compiler -import Hakyll.Core.Compiler.Internal -import Hakyll.Core.Configuration -import Hakyll.Core.DependencyAnalyzer -import qualified Hakyll.Core.DirectedGraph as DG -import Hakyll.Core.Identifier -import Hakyll.Core.Logger -import Hakyll.Core.ResourceProvider -import Hakyll.Core.Routes -import Hakyll.Core.Rules.Internal -import Hakyll.Core.Store (Store) -import qualified Hakyll.Core.Store as Store -import Hakyll.Core.Util.File -import Hakyll.Core.Writable - - --------------------------------------------------------------------------------- --- | Run all rules needed, return the rule set used -run :: HakyllConfiguration -> RulesM a -> IO RuleSet -run configuration rules = do - logger <- makeLogger putStrLn - - section logger "Initialising" - store <- timed logger "Creating store" $ - Store.new (inMemoryCache configuration) $ storeDirectory configuration - provider <- timed logger "Creating provider" $ newResourceProvider - store (ignoreFile configuration) "." - - ruleSet <- timed logger "Running rules" $ runRules rules provider - let compilers = rulesCompilers ruleSet - - -- Extract the reader/state - reader = unRuntime analyzeAndBuild - errorT = runReaderT reader $ RuntimeEnvironment - { runtimeLogger = logger - , runtimeConfiguration = configuration - , runtimeRoutes = rulesRoutes ruleSet - , runtimeProvider = provider - , runtimeStore = store - , runtimeCompilers = M.fromList compilers - } - - -- Run the program and fetch the resulting state - result <- runErrorT errorT - case result of - Left e -> thrown logger e - _ -> return () - - -- Flush and return - flushLogger logger - return ruleSet - - --------------------------------------------------------------------------------- -data RuntimeEnvironment = RuntimeEnvironment - { runtimeLogger :: Logger - , runtimeConfiguration :: HakyllConfiguration - , runtimeRoutes :: Routes - , runtimeProvider :: ResourceProvider - , runtimeStore :: Store - , runtimeCompilers :: Map (Identifier ()) (Compiler () CompiledItem) - } - - --------------------------------------------------------------------------------- -newtype Runtime a = Runtime - { unRuntime :: ReaderT RuntimeEnvironment (ErrorT String IO) a - } deriving (Functor, Applicative, Monad) - - --------------------------------------------------------------------------------- -analyzeAndBuild :: Runtime () -analyzeAndBuild = Runtime $ do - -- Get some stuff - logger <- runtimeLogger <$> ask - provider <- runtimeProvider <$> ask - store <- runtimeStore <$> ask - compilers <- runtimeCompilers <$> ask - - -- Checking which items have been modified - let universe = M.keys compilers - modified <- timed logger "Checking for modified items" $ - fmap S.fromList $ flip filterM universe $ - liftIO . resourceModified provider - - -- Fetch the old graph from the store. If we don't find it, we consider this - -- to be the first run - mOldGraph <- liftIO $ Store.get store graphKey - let (firstRun, oldGraph) = case mOldGraph of Store.Found g -> (False, g) - _ -> (True, mempty) - - -- Create a new dependency graph - graph = DG.fromList $ - flip map (M.toList compilers) $ \(id', compiler) -> - let deps = runCompilerDependencies compiler id' universe - in (id', S.toList deps) - - ood | firstRun = const True - | otherwise = (`S.member` modified) - - -- Check for cycles and analyze the graph - analysis = analyze oldGraph graph ood - - -- Make sure this stuff is evaluated - () <- timed logger "Analyzing dependency graph" $ - oldGraph `deepseq` analysis `deepseq` return () - - -- We want to save the new dependency graph for the next run - liftIO $ Store.set store graphKey graph - - case analysis of - Cycle c -> unRuntime $ dumpCycle c - Order o -> mapM_ (unRuntime . build) o - where - graphKey = ["Hakyll.Core.Run.run", "dependencies"] - - --------------------------------------------------------------------------------- --- | Dump cyclic error and quit -dumpCycle :: [Identifier ()] -> Runtime () -dumpCycle cycle' = Runtime $ do - logger <- runtimeLogger <$> ask - section logger "Dependency cycle detected! Conflict:" - forM_ (zip cycle' $ drop 1 cycle') $ \(x, y) -> - report logger $ show x ++ " -> " ++ show y - - --------------------------------------------------------------------------------- -build :: Identifier () -> Runtime () -build id' = Runtime $ do - logger <- runtimeLogger <$> ask - routes <- runtimeRoutes <$> ask - provider <- runtimeProvider <$> ask - store <- runtimeStore <$> ask - compilers <- runtimeCompilers <$> ask - - section logger $ "Compiling " ++ show id' - - -- Fetch the right compiler from the map - let compiler = compilers M.! id' - - -- Check if the resource was modified - isModified <- liftIO $ resourceModified provider id' - - -- Run the compiler - result <- timed logger "Total compile time" $ liftIO $ - runCompiler compiler id' provider (M.keys compilers) routes - store isModified logger - - case result of - -- Success - Right compiled -> do - case runRoutes routes id' of - Nothing -> return () - Just url -> timed logger ("Routing to " ++ url) $ do - destination <- - destinationDirectory . runtimeConfiguration <$> ask - let path = destination url - liftIO $ makeDirectories path - liftIO $ write path compiled - - -- Some error happened, rethrow in Runtime monad - Left err -> throwError err diff --git a/src/Hakyll/Core/Runtime.hs b/src/Hakyll/Core/Runtime.hs index 7354119..2ed3d2c 100644 --- a/src/Hakyll/Core/Runtime.hs +++ b/src/Hakyll/Core/Runtime.hs @@ -132,7 +132,9 @@ scheduleOutOfDate = do -- Update facts and todo items modify $ \s -> s - { runtimeTodo = todo `M.union` todo' + { runtimeDone = runtimeDone s `S.union` + (S.fromList identifiers `S.difference` ood) + , runtimeTodo = todo `M.union` todo' , runtimeFacts = facts' } @@ -143,7 +145,9 @@ pickAndChase = do todo <- runtimeTodo <$> get case M.minViewWithKey todo of Nothing -> return () - Just ((id', _), _) -> chase [] id' + Just ((id', _), _) -> do + chase [] id' + pickAndChase -------------------------------------------------------------------------------- diff --git a/src/Hakyll/Main.hs b/src/Hakyll/Main.hs index 6c9103f..e0f5b93 100644 --- a/src/Hakyll/Main.hs +++ b/src/Hakyll/Main.hs @@ -13,7 +13,7 @@ import System.Process (system) import Hakyll.Core.Configuration import Hakyll.Core.Identifier -import Hakyll.Core.Run +import Hakyll.Core.Runtime import Hakyll.Core.Rules #ifdef PREVIEW_SERVER @@ -28,13 +28,13 @@ import Hakyll.Web.Preview.Server -- | This usualy is the function with which the user runs the hakyll compiler -- -hakyll :: RulesM a -> IO () -hakyll = hakyllWith defaultHakyllConfiguration +hakyll :: Rules a -> IO () +hakyll = hakyllWith defaultConfiguration -- | A variant of 'hakyll' which allows the user to specify a custom -- configuration -- -hakyllWith :: HakyllConfiguration -> RulesM a -> IO () +hakyllWith :: Configuration -> Rules a -> IO () hakyllWith conf rules = do args <- getArgs case args of @@ -51,14 +51,14 @@ hakyllWith conf rules = do -- | Build the site -- -build :: HakyllConfiguration -> RulesM a -> IO () +build :: Configuration -> Rules a -> IO () build conf rules = do _ <- run conf rules return () -- | Remove the output directories -- -clean :: HakyllConfiguration -> IO () +clean :: Configuration -> IO () clean conf = do remove $ destinationDirectory conf remove $ storeDirectory conf @@ -97,12 +97,12 @@ help = do -- | Preview the site -- -preview :: HakyllConfiguration -> RulesM a -> Int -> IO () +preview :: Configuration -> Rules a -> Int -> IO () #ifdef PREVIEW_SERVER preview conf rules port = do -- Fork a thread polling for changes _ <- forkIO $ previewPoll conf update - + -- Run the server in the main thread server conf port where @@ -113,14 +113,14 @@ preview _ _ _ = previewServerDisabled -- | Rebuild the site -- -rebuild :: HakyllConfiguration -> RulesM a -> IO () +rebuild :: Configuration -> Rules a -> IO () rebuild conf rules = do clean conf build conf rules -- | Start a server -- -server :: HakyllConfiguration -> Int -> IO () +server :: Configuration -> Int -> IO () #ifdef PREVIEW_SERVER server conf port = do let destination = destinationDirectory conf @@ -133,7 +133,7 @@ server _ _ = previewServerDisabled -- | Upload the site -- -deploy :: HakyllConfiguration -> IO () +deploy :: Configuration -> IO () deploy conf = do _ <- system $ deployCommand conf return () diff --git a/src/Hakyll/Web/Template.hs b/src/Hakyll/Web/Template.hs index e23b532..6d9060f 100644 --- a/src/Hakyll/Web/Template.hs +++ b/src/Hakyll/Web/Template.hs @@ -58,8 +58,6 @@ -- > About -- > Code -- > #{body} -{-# LANGUAGE Arrows #-} -{-# LANGUAGE ScopedTypeVariables #-} module Hakyll.Web.Template ( Template , applyTemplate @@ -70,11 +68,7 @@ module Hakyll.Web.Template -------------------------------------------------------------------------------- -import Control.Arrow -import Control.Category (id) -import qualified Data.Map as M -import Data.Maybe (fromMaybe) -import Data.Tuple (swap) +import Control.Monad (forM, liftM) import Prelude hiding (id) import System.FilePath (takeExtension) import Text.Hamlet (HamletSettings, @@ -84,7 +78,6 @@ import Text.Hamlet (HamletSettings, -------------------------------------------------------------------------------- import Hakyll.Core.Compiler import Hakyll.Core.Identifier -import Hakyll.Core.Util.Arrow import Hakyll.Web.Page.Internal import Hakyll.Web.Template.Context import Hakyll.Web.Template.Internal @@ -92,53 +85,44 @@ import Hakyll.Web.Template.Read -------------------------------------------------------------------------------- -applyTemplate :: forall a b. (ArrowChoice a, ArrowMap a) - => a (String, b) String - -> a (Template, b) String -applyTemplate context = - arr (\(tpl, x) -> [(e, x) | e <- unTemplate tpl]) >>> - mapA applyElement >>^ concat - where - applyElement :: a (TemplateElement, b) String - applyElement = unElement >>> (id ||| context) - - unElement :: a (TemplateElement, b) (Either String (String, b)) - unElement = arr $ \(e, x) -> case e of - Chunk c -> Left c - Escaped -> Left "$" - Key k -> Right (k, x) +applyTemplate :: Monad m + => (String -> a -> m String) + -> Template -> a -> m String +applyTemplate context tpl x = liftM concat $ + forM (unTemplate tpl) $ \e -> case e of + Chunk c -> return c + Escaped -> return "$" + Key k -> context k x -------------------------------------------------------------------------------- -- | Read a template. If the extension of the file we're compiling is -- @.hml@ or @.hamlet@, it will be considered as a Hamlet template, and parsed -- as such. -templateCompiler :: Compiler () Template +templateCompiler :: Compiler Template templateCompiler = templateCompilerWith defaultHamletSettings -------------------------------------------------------------------------------- -- | Version of 'templateCompiler' that enables custom settings. -templateCompilerWith :: HamletSettings -> Compiler () Template +templateCompilerWith :: HamletSettings -> Compiler Template templateCompilerWith settings = - cached "Hakyll.Web.Template.templateCompilerWith" $ - getIdentifier &&& getResourceString >>^ uncurry read' - where - read' identifier string = + cached "Hakyll.Web.Template.templateCompilerWith" $ do + identifier <- getIdentifier + string <- getResourceString if takeExtension (toFilePath identifier) `elem` [".hml", ".hamlet"] -- Hamlet template - then readHamletTemplateWith settings string + then return $ readHamletTemplateWith settings string -- Hakyll template - else readTemplate string + else return $ readTemplate string -------------------------------------------------------------------------------- -applyTemplateCompiler :: Identifier Template -- ^ Template - -> Context Page -- ^ Context - -> Compiler Page Page -- ^ Compiler -applyTemplateCompiler identifier context = requireA identifier $ - arr swap >>> applyTemplate context' - where - context' = proc (k, x) -> do - id' <- getIdentifier -< () - context -< (k, (id', x)) +applyTemplateCompiler :: Template -- ^ Template + -> Context Page -- ^ Context + -> Page -- ^ Page + -> Compiler Page -- ^ Compiler +applyTemplateCompiler tpl context page = do + identifier <- getIdentifier + let context' k x = unContext context k identifier x + applyTemplate context' tpl page diff --git a/src/Hakyll/Web/Template/Context.hs b/src/Hakyll/Web/Template/Context.hs index 6261a09..9c3e412 100644 --- a/src/Hakyll/Web/Template/Context.hs +++ b/src/Hakyll/Web/Template/Context.hs @@ -1,6 +1,7 @@ -------------------------------------------------------------------------------- module Hakyll.Web.Template.Context - ( Context + ( Context (..) + , mapContext , field , defaultContext @@ -13,8 +14,8 @@ module Hakyll.Web.Template.Context -------------------------------------------------------------------------------- -import Control.Applicative (empty, (<|>)) -import Control.Arrow +import Control.Applicative (Alternative (..), (<$>)) +import Data.Monoid (Monoid (..)) import System.FilePath (takeBaseName, takeDirectory) @@ -26,24 +27,35 @@ import Hakyll.Web.Urls -------------------------------------------------------------------------------- -type Context a = String -> Identifier -> a -> Compiler String +newtype Context a = Context + { unContext :: String -> Identifier -> a -> Compiler String + } + + +-------------------------------------------------------------------------------- +instance Monoid (Context a) where + mempty = Context $ \_ _ _ -> empty + mappend (Context f) (Context g) = Context $ \k i x -> f k i x <|> g k i x + + +-------------------------------------------------------------------------------- +mapContext :: (String -> String) -> Context a -> Context a +mapContext f (Context g) = Context $ \k i x -> f <$> g k i x -------------------------------------------------------------------------------- field :: String -> (Identifier -> a -> Compiler String) -> Context a -field key value k' id' x - | k' == key = value id' x - | otherwise = empty +field key value = Context $ \k i x -> if k == key then value i x else empty -------------------------------------------------------------------------------- defaultContext :: Context Page defaultContext = - bodyField "body" <|> - urlField "url" <|> - pathField "path" <|> - categoryField "category" <|> - titleField "title" <|> + bodyField "body" `mappend` + urlField "url" `mappend` + pathField "path" `mappend` + categoryField "category" `mappend` + titleField "title" `mappend` missingField @@ -54,24 +66,24 @@ bodyField key = field key $ \_ x -> return x -------------------------------------------------------------------------------- urlField :: String -> Context a -urlField key = field key $ fst ^>> getRouteFor >>^ maybe empty toUrl +urlField key = field key $ \i _ -> maybe empty toUrl <$> getRouteFor i -------------------------------------------------------------------------------- pathField :: String -> Context a -pathField key = field key $ arr $ toFilePath . fst +pathField key = field key $ \i _ -> return $ toFilePath i -------------------------------------------------------------------------------- categoryField :: String -> Context a -categoryField key = pathField key >>^ (takeBaseName . takeDirectory) +categoryField key = mapContext (takeBaseName . takeDirectory) $ pathField key -------------------------------------------------------------------------------- titleField :: String -> Context a -titleField key = pathField key >>^ takeBaseName +titleField key = mapContext takeBaseName $ pathField key -------------------------------------------------------------------------------- missingField :: Context a -missingField = arr $ \(k, _) -> "$" ++ k ++ "$" +missingField = Context $ \k _ _ -> return $ "$" ++ k ++ "$"