hakyll/src/Hakyll/Core/Routes.hs
Jasper Van der Jeugt 758e0beaaa Type-safe identifiers
2011-05-24 11:58:13 +02:00

137 lines
3.8 KiB
Haskell

-- | Once a target is compiled, the user usually wants to save it to the disk.
-- This is where the 'Routes' type comes in; it determines where a certain
-- target should be written.
--
-- Suppose we have an item @foo\/bar.markdown@. We can render this to
-- @foo\/bar.html@ using:
--
-- > route "foo/bar.markdown" (setExtension ".html")
--
-- If we do not want to change the extension, we can use 'idRoute', the simplest
-- route available:
--
-- > route "foo/bar.markdown" idRoute
--
-- That will route @foo\/bar.markdown@ to @foo\/bar.markdown@.
--
-- Note that the extension says nothing about the content! If you set the
-- extension to @.html@, it is your own responsibility to ensure that the
-- content is indeed HTML.
--
-- Finally, some special cases:
--
-- * If there is no route for an item, this item will not be routed, so it will
-- not appear in your site directory.
--
-- * If an item matches multiple routes, the first rule will be chosen.
--
{-# LANGUAGE Rank2Types #-}
module Hakyll.Core.Routes
( Routes
, runRoutes
, idRoute
, setExtension
, matchRoute
, customRoute
, gsubRoute
, composeRoutes
) where
import Data.Monoid (Monoid, mempty, mappend)
import Control.Monad (mplus)
import System.FilePath (replaceExtension)
import Hakyll.Core.Identifier
import Hakyll.Core.Identifier.Pattern
import Hakyll.Core.Util.String
-- | Type used for a route
--
newtype Routes = Routes {unRoutes :: forall a. Identifier a -> Maybe FilePath}
instance Monoid Routes where
mempty = Routes $ const Nothing
mappend (Routes f) (Routes g) = Routes $ \id' -> f id' `mplus` g id'
-- | Apply a route to an identifier
--
runRoutes :: Routes -> Identifier a -> Maybe FilePath
runRoutes = unRoutes
-- | A route that uses the identifier as filepath. For example, the target with
-- ID @foo\/bar@ will be written to the file @foo\/bar@.
--
idRoute :: Routes
idRoute = Routes $ Just . toFilePath
-- | Set (or replace) the extension of a route.
--
-- Example:
--
-- > runRoute (setExtension "html") "foo/bar"
--
-- Result:
--
-- > Just "foo/bar.html"
--
-- Example:
--
-- > runRoute (setExtension "html") "posts/the-art-of-trolling.markdown"
--
-- Result:
--
-- > Just "posts/the-art-of-trolling.html"
--
setExtension :: String -> Routes
setExtension extension = Routes $ fmap (`replaceExtension` extension)
. unRoutes idRoute
-- | Apply the route if the identifier matches the given pattern, fail
-- otherwise
--
matchRoute :: Pattern a -> Routes -> Routes
matchRoute pattern (Routes route) = Routes $ \id' ->
if matches pattern (castIdentifier id') then route id' else Nothing
-- | Create a custom route. This should almost always be used with
-- 'matchRoute'
--
customRoute :: (Identifier a -> FilePath) -> Routes
customRoute f = Routes $ Just . f . castIdentifier
-- | Create a gsub route
--
-- Example:
--
-- > runRoutes (gsubRoute "rss/" (const "")) "tags/rss/bar.xml"
--
-- Result:
--
-- > Just "tags/bar.xml"
--
gsubRoute :: String -- ^ Pattern
-> (String -> String) -- ^ Replacement
-> Routes -- ^ Resulting route
gsubRoute pattern replacement = customRoute $
replaceAll pattern replacement . toFilePath
-- | Compose routes so that @f `composeRoutes` g@ is more or less equivalent
-- with @f >>> g@.
--
-- Example:
--
-- > let routes = gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml"
-- > in runRoutes routes "tags/rss/bar"
--
-- Result:
--
-- > Just "tags/bar.xml"
--
-- If the first route given fails, Hakyll will not apply the second route.
--
composeRoutes :: Routes -- ^ First route to apply
-> Routes -- ^ Second route to apply
-> Routes -- ^ Resulting route
composeRoutes (Routes f) (Routes g) = Routes $ \i -> do
p <- f i
g $ parseIdentifier p