urlpath 0.0.6 → 0.1
raw patch · 4 files changed
+235/−207 lines, 4 filesdep +QuickCheckdep +hspecdep +quickcheck-instancesdep ~base
Dependencies added: QuickCheck, hspec, quickcheck-instances
Dependency ranges changed: base
Files
- src/UrlPath.hs +67/−84
- src/UrlPath/Types.hs +120/−108
- test/Spec.hs +1/−0
- urlpath.cabal +47/−15
src/UrlPath.hs view
@@ -5,7 +5,7 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE TypeFamilies #-}-+{-# LANGUAGE RankNTypes #-} module UrlPath ( UrlReader (..)@@ -14,107 +14,90 @@ import UrlPath.Types -import qualified Data.Text as T--import Control.Monad.Trans-import Control.Monad.Reader.Class-+import Data.String import Data.Monoid import Control.Applicative import Control.Monad import Data.Functor.Identity+import Control.Monad.Trans+import Control.Monad.Reader.Class --- | Convenience typeclass for a uniform interface into pure @Reader@-like --- monads.-class MonadReader T.Text m => UrlReader (m :: * -> *) where- runUrlReader :: m a -- ^ Monadic reader-like computation- -> T.Text -- ^ @T.Text@ index- -> a -- ^ Result -instance MonadReader T.Text Identity where- ask = return ""--instance UrlReader Identity where- runUrlReader x _ = runIdentity x--instance UrlReader AbsoluteUrl where- runUrlReader = runAbsoluteUrl--instance UrlReader RelativeUrl where- runUrlReader = runRelativeUrl--instance UrlReader GroundedUrl where- runUrlReader = runGroundedUrl---- | @Url@ takes an input type @a@, and returns a modality @f@ around @T.Text@.-class MonadReader T.Text m => Url a m where- renderUrl :: a -- ^ Url-like type (@UrlString@ or @T.Text@).- -> m T.Text -- ^ Rendered Url in some context @f@--instance Url T.Text Identity where- renderUrl = Identity+-- | @Url@ is a relationship between an underlying (monomorphic) string type+-- @string@, and a deployment context @m@. We try to make the deployment and +-- implementation type coercible at the top level - coercing your final +-- expression to @String@ or @T.Text@ will have /all/ use-cases coerce to that +-- type, similarly with the deployment scheme.+--+-- We chose to not force @MonadReader@ as a superclass for @m@ due to the +-- monomorphic demand on functional dependencies.+class ( IsString string, Monoid string, MonadReader string m ) =>+ Url string (m :: * -> *) where+ url :: UrlString string -- ^ Url type+ -> m string -- ^ Rendered Url in some context.+ stringUrl :: string -- ^ raw string+ -> m string -- ^ Rendered string in some context. -instance Url UrlString Identity where- renderUrl = Identity . expandRelative+-- | Overload deployment schemes with this - then, all that's needed is a type +-- coercion to change deployment. This only works with flat (co)monads, so monad +-- transformers are out.+class Url string m => UrlReader string m where+ runUrlReader :: Url string m =>+ m string -- ^ Monad with result @string@+ -> string -- ^ Reader index+ -> string -- ^ Final result -instance Url UrlString RelativeUrl where- renderUrl x = RelativeUrl $ \_ -> expandRelative x+-- * Monads -instance Url UrlString GroundedUrl where- renderUrl x = GroundedUrl $ \_ -> expandGrounded x+instance ( Monoid a+ , IsString a ) => Url a (RelativeUrl a) where+ url = RelativeUrl . const . expandRelative+ stringUrl x = RelativeUrl $ const $ expandRelative $ UrlString x [] -instance Url UrlString AbsoluteUrl where- renderUrl = expandAbsolute+instance ( Monoid a+ , IsString a ) => UrlReader a (RelativeUrl a) where+ runUrlReader = runRelativeUrl -instance Monad m => Url UrlString (RelativeUrlT m) where- renderUrl x = RelativeUrlT $ \_ -> return $ expandRelative x+--- -instance Monad m => Url UrlString (GroundedUrlT m) where- renderUrl x = GroundedUrlT $ \_ -> return $ expandGrounded x+instance ( Monoid a+ , IsString a ) => Url a (GroundedUrl a) where+ url = GroundedUrl . const . expandGrounded+ stringUrl x = GroundedUrl $ const $ expandGrounded $ UrlString x [] -instance Monad m => Url UrlString (AbsoluteUrlT m) where- renderUrl = expandAbsolute+instance ( Monoid a+ , IsString a ) => UrlReader a (GroundedUrl a) where+ runUrlReader = runGroundedUrl -instance Url T.Text RelativeUrl where- renderUrl x = RelativeUrl $ \_ -> expandRelative $ UrlString x []+--- -instance Url T.Text GroundedUrl where- renderUrl x = GroundedUrl $ \_ -> expandGrounded $ UrlString x []+instance ( Monoid a+ , IsString a ) => Url a (AbsoluteUrl a) where+ url = expandAbsolute+ stringUrl x = expandAbsolute $ UrlString x [] -instance Url T.Text AbsoluteUrl where- renderUrl x = expandAbsolute $ UrlString x []+-- | Hand-off host prepending to the MonadReader instance+instance ( Monoid a+ , IsString a ) => UrlReader a (AbsoluteUrl a) where+ runUrlReader = runAbsoluteUrl -instance Monad m => Url T.Text (RelativeUrlT m) where- renderUrl x = RelativeUrlT $ \_ -> return $ expandRelative $ UrlString x []+-- * Transformers -instance Monad m => Url T.Text (GroundedUrlT m) where- renderUrl x = GroundedUrlT $ \_ -> return $ expandGrounded $ UrlString x []+instance ( Monad m+ , Monoid a+ , IsString a ) => Url a (RelativeUrlT a m) where+ url = RelativeUrlT . const . return . expandRelative+ stringUrl x = RelativeUrlT $ const $ return $ expandRelative $ UrlString x [] -instance Monad m => Url T.Text (AbsoluteUrlT m) where- renderUrl x = expandAbsolute $ UrlString x []+instance ( Monad m+ , Monoid a+ , IsString a ) => Url a (GroundedUrlT a m) where+ url = GroundedUrlT . const . return . expandGrounded+ stringUrl x = GroundedUrlT $ const $ return $ expandGrounded $ UrlString x [] --- * Example------ | Here is an example:------ > {-# LANGUAGE OverloadedStrings #-}--- > {-# LANGUAGE ExtendedDefaultRules #-}--- >--- > import Lucid--- > import Lucid.Path--- > import qualified Data.Text as T--- > import qualified Data.Text.Lazy as LT--- >--- > foo :: LT.Text--- > foo = (runUrlReader $ renderTextT bar) "example.com"--- >--- > bar :: HtmlT AbsoluteUrl ()--- > bar = do--- > url <- lift $ renderUrl $ "foo.php" <?> ("bar","baz")--- > script_ [src_ url] ""------ Where @foo@ is now------ > Lucid> foo--- > "<script src=\"example.com/foo.php?bar=baz\"></script>"+instance ( Monad m+ , Monoid a+ , IsString a ) => Url a (AbsoluteUrlT a m) where+ url = expandAbsolute+ stringUrl x = expandAbsolute $ UrlString x []
src/UrlPath/Types.hs view
@@ -1,88 +1,94 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DeriveFunctor #-} module UrlPath.Types where -import qualified Data.Text as T-+import Data.String 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)+-- | A Url string - a target page and GET parameters. We only require a +-- constraint of @IsString@ so that construction can be convenient, but +-- rendering will require a monomorphic type.+data UrlString a where+ UrlString :: ( IsString a+ , Monoid a ) =>+ a+ -> [(a, a)]+ -> UrlString a --- | 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)) =+-- | We choose to not provide a @Show@ instance for @UrlString@ to evade the +-- @String@ demand.+showUrlString :: UrlString a+ -> a+showUrlString (UrlString !t []) = t+showUrlString (UrlString !t ((!k,!v):xs)) = t <> "?" <> k <> "=" <> v <>- foldr (\x acc -> acc <> renderGETParam x) "" ps+ foldl (\acc (x,y) -> acc <> "&" <> x <> "=" <> y) "" xs --- | 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]+-- | Lifts a raw target path and a GET parameter pair into a @UrlString@.+(<?>) :: ( IsString a+ , Monoid a ) =>+ a -- ^ Target string+ -> (a, a) -- ^ GET Parameter+ -> UrlString a+(<?>) !t !kv = UrlString t [kv] 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]+-- | Adds another GET parameter pair to a @UrlString@.+(<&>) :: ( IsString a+ , Monoid a ) =>+ UrlString a -- ^ Old Url+ -> (a, a) -- ^ Additional GET Parameter+ -> UrlString a+(<&>) (UrlString !t !p) !kv = UrlString t $ p ++ [kv] infixl 8 <&> -- | Render the Url String as relative-expandRelative :: UrlString- -> T.Text-expandRelative = renderUrlString+expandRelative :: ( IsString a+ , Monoid a ) =>+ UrlString a+ -> a+expandRelative = showUrlString -- | Render the Url String as grounded-expandGrounded :: UrlString- -> T.Text-expandGrounded x = "/" <> renderUrlString x+expandGrounded :: ( IsString a+ , Monoid a ) =>+ UrlString a+ -> a+expandGrounded !x = "/" <> showUrlString 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 - getting the root from a @MonadReader@ +-- context. The @Monoid@ instance will be decided monomorphically, therefore a +-- type signature will be needed when ran.+expandAbsolute :: ( MonadReader a m+ , IsString a+ , Monoid a ) =>+ UrlString a+ -> m a+expandAbsolute !x = do+ host <- ask+ return $ host <> "/" <> showUrlString 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@@ -91,155 +97,161 @@ -- > 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+expandAbsoluteWith :: ( MonadReader a m+ , IsString a+ , Monoid a ) =>+ UrlString a+ -> (a -> a)+ -> m a+expandAbsoluteWith !x f = do root <- liftM f ask- return $ root <> "/" <> renderUrlString x+ return $ root <> "/" <> showUrlString 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] ""+-- > path <- lift $ url $ "foo.php" <?> ("bar","baz")+-- > script_ [src_ path] "" -- -- When rendering @foo@, simply use the Transformer's @run@ function to convert -- it to a reader: ----- > bar :: Monad m => m LT.Text+-- > bar :: ( Monad m, IsString a, Monoid a ) => m a -- > 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)+newtype RelativeUrlT h m a = RelativeUrlT { runRelativeUrlT :: h -> m a }+ deriving Functor -instance Applicative f => Applicative (RelativeUrlT f) where+instance Applicative f => Applicative (RelativeUrlT h f) where (<*>) f x = RelativeUrlT $ \a -> (<*>) (runRelativeUrlT f a) (runRelativeUrlT x a) -instance Monad m => Monad (RelativeUrlT m) where+instance Monad m => Monad (RelativeUrlT h m) where return x = RelativeUrlT $ \_ -> return x m >>= f = RelativeUrlT $ \a -> runRelativeUrlT m a >>= (\x -> runRelativeUrlT (f x) a) -instance MonadTrans RelativeUrlT where+instance MonadTrans (RelativeUrlT h) where lift m = RelativeUrlT (const m) -instance Monad m => MonadReader T.Text (RelativeUrlT m) where+instance ( Monad m+ , IsString a ) => MonadReader a (RelativeUrlT a m) where ask = return "" +instance MonadIO m => MonadIO (RelativeUrlT a m) where+ liftIO = lift . liftIO+ -- | 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] ""+-- > path <- lift $ url $ "foo.php" <?> ("bar","baz")+-- > script_ [src_ path] "" ----- when rendering these simple monads for automatic conversion via coercion, use --- the @runUrlReader@ member function of the @UrlReader@ typeclass:+-- when running the monad reader, use the @runUrlReader@ member function of the+-- @UrlReader@ typeclass: ----- > bar :: LT.Text+-- > bar :: ( IsString a, Monoid a ) => a -- > 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+-- To change the deployment sheme, simply coerce the environment monad in @foo@.+newtype RelativeUrl h a = RelativeUrl { runRelativeUrl :: h -> a }+ deriving Functor -instance Applicative RelativeUrl where+instance Applicative (RelativeUrl h) where (<*>) f x = RelativeUrl $ \a -> runRelativeUrl f a (runRelativeUrl x a) -instance Monad RelativeUrl where+instance Monad (RelativeUrl h) where return x = RelativeUrl $ const x m >>= f = RelativeUrl $ \a -> (\y -> runRelativeUrl (f y) a) (runRelativeUrl m a) -instance MonadReader T.Text RelativeUrl where+instance IsString a => MonadReader a (RelativeUrl a) where ask = return "" -newtype GroundedUrlT m a = GroundedUrlT { runGroundedUrlT :: T.Text -> m a }+newtype GroundedUrlT h m a = GroundedUrlT { runGroundedUrlT :: h -> m a } -instance Functor f => Functor (GroundedUrlT f) where+instance Functor f => Functor (GroundedUrlT h f) where fmap f x = GroundedUrlT $ \a -> fmap f (runGroundedUrlT x a) -instance Applicative f => Applicative (GroundedUrlT f) where+instance Applicative f => Applicative (GroundedUrlT h f) where (<*>) f x = GroundedUrlT $ \a -> (<*>) (runGroundedUrlT f a) (runGroundedUrlT x a) -instance Monad m => Monad (GroundedUrlT m) where+instance Monad m => Monad (GroundedUrlT h m) where return x = GroundedUrlT $ \_ -> return x m >>= f = GroundedUrlT $ \a -> runGroundedUrlT m a >>= (\x -> runGroundedUrlT (f x) a) -instance MonadTrans GroundedUrlT where+instance MonadTrans (GroundedUrlT h) where lift m = GroundedUrlT (const m) -instance Monad m => MonadReader T.Text (GroundedUrlT m) where+instance ( Monad m+ , IsString a ) => MonadReader a (GroundedUrlT a m) where ask = return "/" -newtype GroundedUrl a = GroundedUrl { runGroundedUrl :: T.Text -> a }+instance MonadIO m => MonadIO (GroundedUrlT a m) where+ liftIO = lift . liftIO -instance Functor GroundedUrl where+newtype GroundedUrl h a = GroundedUrl { runGroundedUrl :: h -> a }++instance Functor (GroundedUrl h) where fmap f x = GroundedUrl $ \a -> f $ runGroundedUrl x a -instance Applicative GroundedUrl where+instance Applicative (GroundedUrl h) where (<*>) f x = GroundedUrl $ \a -> runGroundedUrl f a (runGroundedUrl x a) -instance Monad GroundedUrl where+instance Monad (GroundedUrl h) where return x = GroundedUrl $ const x m >>= f = GroundedUrl $ \a -> (\y -> runGroundedUrl (f y) a) (runGroundedUrl m a) -instance MonadReader T.Text GroundedUrl where+instance IsString a => MonadReader a (GroundedUrl a) where ask = return "/" -newtype AbsoluteUrlT m a = AbsoluteUrlT { runAbsoluteUrlT :: T.Text -> m a }+newtype AbsoluteUrlT h m a = AbsoluteUrlT { runAbsoluteUrlT :: h -> m a } -instance Functor f => Functor (AbsoluteUrlT f) where+instance Functor f => Functor (AbsoluteUrlT h f) where fmap f x = AbsoluteUrlT $ \a -> fmap f (runAbsoluteUrlT x a) -instance Applicative f => Applicative (AbsoluteUrlT f) where+instance Applicative f => Applicative (AbsoluteUrlT h f) where (<*>) f x = AbsoluteUrlT $ \a -> (<*>) (runAbsoluteUrlT f a) (runAbsoluteUrlT x a) -instance Monad m => Monad (AbsoluteUrlT m) where+instance Monad m => Monad (AbsoluteUrlT h m) where return x = AbsoluteUrlT $ const $ return x m >>= f = AbsoluteUrlT $ \a -> runAbsoluteUrlT m a >>= (\x -> runAbsoluteUrlT (f x) a) -instance MonadTrans AbsoluteUrlT where+instance MonadTrans (AbsoluteUrlT h) where lift m = AbsoluteUrlT (const m) -instance Monad m => MonadReader T.Text (AbsoluteUrlT m) where+instance ( Monad m+ , IsString a ) => MonadReader a (AbsoluteUrlT a m) where ask = AbsoluteUrlT return -newtype AbsoluteUrl a = AbsoluteUrl { runAbsoluteUrl :: T.Text -> a }+instance MonadIO m => MonadIO (AbsoluteUrlT a m) where+ liftIO = lift . liftIO -instance Functor AbsoluteUrl where+newtype AbsoluteUrl h a = AbsoluteUrl { runAbsoluteUrl :: h -> a }++instance Functor (AbsoluteUrl h) where fmap f x = AbsoluteUrl $ \a -> f $ runAbsoluteUrl x a -instance Applicative AbsoluteUrl where+instance Applicative (AbsoluteUrl h) where (<*>) f x = AbsoluteUrl $ \a -> runAbsoluteUrl f a (runAbsoluteUrl x a) -instance Monad AbsoluteUrl where+instance Monad (AbsoluteUrl h) where return x = AbsoluteUrl $ const x m >>= f = AbsoluteUrl $ \a -> (\y -> runAbsoluteUrl (f y) a) (runAbsoluteUrl m a) -instance MonadReader T.Text AbsoluteUrl where+instance IsString a => MonadReader a (AbsoluteUrl a) where ask = AbsoluteUrl id
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
urlpath.cabal view
@@ -1,5 +1,5 @@ Name: urlpath-Version: 0.0.6+Version: 0.1 Author: Athan Clark <athan.clark@gmail.com> Maintainer: Athan Clark <athan.clark@gmail.com> License: MIT@@ -9,26 +9,33 @@ Description: Simple URL DSL for Haskell. .- Use raw combinators (kinda useless) ...+ This library tries to make it easier for people to write Url strings, + structurally. Packages like <https://hackage.haskell.org/package/yesod-routes Yesod Routes>+ do a wonderful job at implementing string-free routing and + references, but sometimes we have to compromise. This tries to make that + compromise less painful. .- > render $ "foo.php" <?> ("key1","bar") <&> ("key2","baz")+ Use bare combinators to render your strings (kinda useless):+ .+ > expandRelative $ "foo.php" <?> ("key1","bar") <&> ("key2","baz") > > ↪ "foo.php?key1=bar&key2=baz" .- ... or use the MonadReader instance for a configurable root ...+ ... or use the MonadReader instance for a configurable host: .- > let url = runReader $ expandAbsolute $ "foo.php" <?> ("key1","bar") <&> ("key2","baz")- > url "example.com"+ > let path = runAbsoluteUrl $ url $ "foo.php" <?> ("key1","bar") <&> ("key2","baz")+ > path "example.com" > > ↪ "example.com/foo.php?key1=bar&key2=baz" .- ... in Lucid ...+ @url@ puts the @UrlString@ in a MonadReader that we can use for applying our + host. We use different monads for different deployment schemes (currently we + have 3 - @RelativeUrl@, @GroundedUrl@, and @AbsoluteUrl@), which we can + integrate in different libraries, like Lucid: .- > (runReader $ renderTextT $- > (\a -> do- > foo <- lift $ expandAbsolute a+ > (runAbsoluteUrl $ renderTextT $ do+ > foo <- lift $ url $ "foo" <?> ("bar","baz") > script_ [src_ foo] "" )- > ("foo" <?> ("bar","baz")) > ) "example.com" > > ↪ "<script src=\"example.com/foo?bar=baz\"></script>"@@ -42,18 +49,27 @@ > run > > where- > rootConf = flip runReaderT "http://example.com"+ > rootConf = flip runAbsoluteT "http://example.com" > > run :: ( MonadIO m- > , MonadReader T.Text m ) =>+ > , MonadReader T.Text m+ > , Url T.Text m ) => > ScottyT LT.Text m () > run = get "/" $ do- > path <- lift $ expandAbsolute $ "foo" <?> ("bar","baz")+ > path <- lift $ url $ "foo" <?> ("bar","baz") > text $ LT.fromStrict path > > λ> curl localhost:3000/ > ↪ "http://example.com/foo?bar=baz"- + .+ Note that in the scotty example, we don't use one of our deployment schemes - + this is because the @scottyT@ function expects it's underlying monad to be an + instance of @MonadIO@, which we can only instantiate in our monad transformers.+ .+ Please take mind - the string type underlying the Url rendering is generalized + to @Data.String.IsString@ for convenient use with @-XOverloadedStrings@. However, + due to that generality, we need to specify the monomorphic type (like + @Data.Text.Text@ above). Cabal-Version: >= 1.10 Build-Type: Simple@@ -64,7 +80,23 @@ GHC-Options: -Wall Exposed-Modules: UrlPath UrlPath.Types+ -- UrlPath.Foo Build-Depends: base >= 4 && < 5+ , mtl+ , text+ , transformers++Test-Suite spec+ Type: exitcode-stdio-1.0+ Default-Language: Haskell2010+ Hs-Source-Dirs: src+ , test+ Ghc-Options: -Wall+ Main-Is: Spec.hs+ Build-Depends: base+ , hspec+ , QuickCheck+ , quickcheck-instances , mtl , text , transformers