{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} module UrlPath.Types where import qualified Data.Text as T import Data.Monoid import Control.Applicative import Control.Monad import Control.Monad.Trans import Control.Monad.Reader.Class -- | A GET parameter encoded in a Url. data GETParam = GETParam { key :: !T.Text -- ^ Key for a get parameter. , val :: !T.Text -- ^ Value for the key. } deriving (Show, Eq) -- | Render a GET parameter pair. renderGETParam :: GETParam -> T.Text renderGETParam (GETParam k v) = "&" <> k <> "=" <> v -- | A Url string - a target page and GET parameters. data UrlString = UrlString { target :: !T.Text -- ^ Relative base file - eg) @"foo.php"@ in @"foo.php?bar=baz"@. , params :: [GETParam] -- ^ GET Parameters. } deriving (Show, Eq) -- | Render a Url String /simply/ - this is equivalent to @expandRelative@. renderUrlString :: UrlString -> T.Text renderUrlString (UrlString t []) = t renderUrlString (UrlString t [GETParam k v]) = t <> "?" <> k <> "=" <> v renderUrlString (UrlString t (GETParam k v : ps)) = t <> "?" <> k <> "=" <> v <> foldr (\x acc -> acc <> renderGETParam x) "" ps -- | Lifts a target path with some GET parameter chunks into a @UrlString@. () :: T.Text -- ^ Target string -> (T.Text, T.Text) -- ^ GET Parameter -> UrlString t (k,v) = UrlString t [GETParam k v] infixl 9 -- | Adds more GET parameters to a @UrlString@. (<&>) :: UrlString -- ^ Old Url -> (T.Text, T.Text) -- ^ Additional GET Parameter -> UrlString old <&> (k,v) = UrlString (target old) $ params old ++ [GETParam k v] infixl 8 <&> -- | Render the Url String as relative expandRelative :: UrlString -> T.Text expandRelative = renderUrlString -- | Render the Url String as grounded expandGrounded :: UrlString -> T.Text expandGrounded x = "/" <> renderUrlString x -- | Render the Url String as absolute - getting the root from a MonadReader. expandAbsolute :: (MonadReader T.Text m) => UrlString -> m T.Text expandAbsolute x = do root <- ask return $ root <> "/" <> renderUrlString x -- | Render the Url String as absolute, but with your own configuration type. -- -- > data SiteConfig = SiteConfig { host :: T.Text -- > , cdnHost :: T.Text -- > } -- > deriving (Show, Eq) -- > -- > foo :: HtmlT (Reader SiteConfig) () -- > foo = do -- > url <- lift $ expandAbsoluteWith ("foo.php" ("bar","baz")) host -- > script_ [src_ url] "" -- > -- > bar :: LT.Text -- > bar = (runReader (runTextT foo)) $ -- > SiteConfig "example.com" "cdn.example.com" expandAbsoluteWith :: (MonadReader a m) => UrlString -> (a -> T.Text) -> m T.Text expandAbsoluteWith x f = do root <- liftM f ask return $ root <> "/" <> renderUrlString x -- | Rendering mode transformer. This isn't an instance of @UrlReader@ - to use, -- simple @lift@ as many levels as you need: -- -- > foo :: Monad m => HtmlT (RelativeUrlT m) () -- > foo = do -- > url <- lift $ renderUrl $ "foo.php" ("bar","baz") -- > script_ [src_ url] "" -- -- When rendering @foo@, simply use the Transformer's @run@ function to convert -- it to a reader: -- -- > bar :: Monad m => m LT.Text -- > bar = (runRelativeUrlT (renderTextT foo)) "example.com" -- -- It is generally simpler (but more restrictive) to use the non-transformer -- variety. newtype RelativeUrlT m a = RelativeUrlT { runRelativeUrlT :: T.Text -> m a } instance Functor f => Functor (RelativeUrlT f) where fmap f x = RelativeUrlT $ \a -> fmap f (runRelativeUrlT x a) instance Applicative f => Applicative (RelativeUrlT f) where (<*>) f x = RelativeUrlT $ \a -> (<*>) (runRelativeUrlT f a) (runRelativeUrlT x a) instance Monad m => Monad (RelativeUrlT m) where return x = RelativeUrlT $ \_ -> return x m >>= f = RelativeUrlT $ \a -> runRelativeUrlT m a >>= (\x -> runRelativeUrlT (f x) a) instance MonadTrans RelativeUrlT where lift m = RelativeUrlT (const m) instance Monad m => MonadReader T.Text (RelativeUrlT m) where ask = return "" -- | Concrete Monad for automatically coercing HtmlT's to use a mode of Url -- rendering (relative, grounded, or absolute). -- -- > foo :: HtmlT RelativeUrl () -- > foo = do -- > url <- lift $ renderUrl $ "foo.php" ("bar","baz") -- > script_ [src_ url] "" -- -- when rendering these simple monads for automatic conversion via coercion, use -- the @runUrlReader@ member function of the @UrlReader@ typeclass: -- -- > bar :: LT.Text -- > bar = (runUrlReader (renderTextT foo)) "example.com" -- -- To change the mode of rendering, simple change the coerced type of @foo@. newtype RelativeUrl a = RelativeUrl { runRelativeUrl :: T.Text -> a } instance Functor RelativeUrl where fmap f x = RelativeUrl $ \a -> f $ runRelativeUrl x a instance Applicative RelativeUrl where (<*>) f x = RelativeUrl $ \a -> runRelativeUrl f a (runRelativeUrl x a) instance Monad RelativeUrl where return x = RelativeUrl $ const x m >>= f = RelativeUrl $ \a -> (\y -> runRelativeUrl (f y) a) (runRelativeUrl m a) instance MonadReader T.Text RelativeUrl where ask = return "" newtype GroundedUrlT m a = GroundedUrlT { runGroundedUrlT :: T.Text -> m a } instance Functor f => Functor (GroundedUrlT f) where fmap f x = GroundedUrlT $ \a -> fmap f (runGroundedUrlT x a) instance Applicative f => Applicative (GroundedUrlT f) where (<*>) f x = GroundedUrlT $ \a -> (<*>) (runGroundedUrlT f a) (runGroundedUrlT x a) instance Monad m => Monad (GroundedUrlT m) where return x = GroundedUrlT $ \_ -> return x m >>= f = GroundedUrlT $ \a -> runGroundedUrlT m a >>= (\x -> runGroundedUrlT (f x) a) instance MonadTrans GroundedUrlT where lift m = GroundedUrlT (const m) instance Monad m => MonadReader T.Text (GroundedUrlT m) where ask = return "/" newtype GroundedUrl a = GroundedUrl { runGroundedUrl :: T.Text -> a } instance Functor GroundedUrl where fmap f x = GroundedUrl $ \a -> f $ runGroundedUrl x a instance Applicative GroundedUrl where (<*>) f x = GroundedUrl $ \a -> runGroundedUrl f a (runGroundedUrl x a) instance Monad GroundedUrl where return x = GroundedUrl $ const x m >>= f = GroundedUrl $ \a -> (\y -> runGroundedUrl (f y) a) (runGroundedUrl m a) instance MonadReader T.Text GroundedUrl where ask = return "/" newtype AbsoluteUrlT m a = AbsoluteUrlT { runAbsoluteUrlT :: T.Text -> m a } instance Functor f => Functor (AbsoluteUrlT f) where fmap f x = AbsoluteUrlT $ \a -> fmap f (runAbsoluteUrlT x a) instance Applicative f => Applicative (AbsoluteUrlT f) where (<*>) f x = AbsoluteUrlT $ \a -> (<*>) (runAbsoluteUrlT f a) (runAbsoluteUrlT x a) instance Monad m => Monad (AbsoluteUrlT m) where return x = AbsoluteUrlT $ const $ return x m >>= f = AbsoluteUrlT $ \a -> runAbsoluteUrlT m a >>= (\x -> runAbsoluteUrlT (f x) a) instance MonadTrans AbsoluteUrlT where lift m = AbsoluteUrlT (const m) instance Monad m => MonadReader T.Text (AbsoluteUrlT m) where ask = AbsoluteUrlT return newtype AbsoluteUrl a = AbsoluteUrl { runAbsoluteUrl :: T.Text -> a } instance Functor AbsoluteUrl where fmap f x = AbsoluteUrl $ \a -> f $ runAbsoluteUrl x a instance Applicative AbsoluteUrl where (<*>) f x = AbsoluteUrl $ \a -> runAbsoluteUrl f a (runAbsoluteUrl x a) instance Monad AbsoluteUrl where return x = AbsoluteUrl $ const x m >>= f = AbsoluteUrl $ \a -> (\y -> runAbsoluteUrl (f y) a) (runAbsoluteUrl m a) instance MonadReader T.Text AbsoluteUrl where ask = AbsoluteUrl id