2010-12-23 18:20:05 +00:00
|
|
|
-- | Once a target is compiled, the user usually wants to save it to the disk.
|
2011-02-03 15:07:49 +00:00
|
|
|
-- This is where the 'Routes' type comes in; it determines where a certain
|
|
|
|
-- target should be written.
|
2010-12-23 18:20:05 +00:00
|
|
|
--
|
2011-02-11 07:48:18 +00:00
|
|
|
-- 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.
|
2010-12-23 18:20:05 +00:00
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
module Hakyll.Core.Routes
|
|
|
|
( Routes
|
|
|
|
, runRoutes
|
2010-12-23 18:20:05 +00:00
|
|
|
, idRoute
|
|
|
|
, setExtension
|
|
|
|
, ifMatch
|
2011-02-11 07:20:35 +00:00
|
|
|
, customRoute
|
2011-02-28 21:40:23 +00:00
|
|
|
, gsubRoute
|
2011-03-01 08:40:07 +00:00
|
|
|
, composeRoutes
|
2010-12-23 18:20:05 +00:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Data.Monoid (Monoid, mempty, mappend)
|
|
|
|
import Control.Monad (mplus)
|
|
|
|
import System.FilePath (replaceExtension)
|
|
|
|
|
|
|
|
import Hakyll.Core.Identifier
|
|
|
|
import Hakyll.Core.Identifier.Pattern
|
2011-02-28 21:40:23 +00:00
|
|
|
import Hakyll.Core.Util.String
|
2010-12-23 18:20:05 +00:00
|
|
|
|
|
|
|
-- | Type used for a route
|
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
newtype Routes = Routes {unRoutes :: Identifier -> Maybe FilePath}
|
2010-12-23 18:20:05 +00:00
|
|
|
|
2011-02-03 15:07:49 +00:00
|
|
|
instance Monoid Routes where
|
|
|
|
mempty = Routes $ const Nothing
|
|
|
|
mappend (Routes f) (Routes g) = Routes $ \id' -> f id' `mplus` g id'
|
2010-12-23 18:20:05 +00:00
|
|
|
|
|
|
|
-- | Apply a route to an identifier
|
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
runRoutes :: Routes -> Identifier -> Maybe FilePath
|
|
|
|
runRoutes = unRoutes
|
2010-12-23 18:20:05 +00:00
|
|
|
|
|
|
|
-- | A route that uses the identifier as filepath. For example, the target with
|
|
|
|
-- ID @foo\/bar@ will be written to the file @foo\/bar@.
|
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
idRoute :: Routes
|
|
|
|
idRoute = Routes $ Just . toFilePath
|
2010-12-23 18:20:05 +00:00
|
|
|
|
|
|
|
-- | 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"
|
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
setExtension :: String -> Routes
|
|
|
|
setExtension extension = Routes $ fmap (`replaceExtension` extension)
|
|
|
|
. unRoutes idRoute
|
2010-12-23 18:20:05 +00:00
|
|
|
|
|
|
|
-- | Modify a route: apply the route if the identifier matches the given
|
|
|
|
-- pattern, fail otherwise.
|
|
|
|
--
|
2011-02-03 15:07:49 +00:00
|
|
|
ifMatch :: Pattern -> Routes -> Routes
|
|
|
|
ifMatch pattern (Routes route) = Routes $ \id' ->
|
2010-12-23 18:20:05 +00:00
|
|
|
if doesMatch pattern id' then route id'
|
|
|
|
else Nothing
|
2011-02-11 07:20:35 +00:00
|
|
|
|
|
|
|
-- | Create a custom route. This should almost always be used with 'ifMatch'.
|
|
|
|
--
|
|
|
|
customRoute :: (Identifier -> FilePath) -> Routes
|
|
|
|
customRoute f = Routes $ Just . f
|
2011-02-28 21:40:23 +00:00
|
|
|
|
|
|
|
-- | 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
|
2011-03-01 08:40:07 +00:00
|
|
|
|
|
|
|
-- | 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
|