packages feed

web-routes 0.25.3 → 0.26.2

raw patch · 6 files changed

+146/−84 lines, 6 filesdep +blaze-builderdep +http-typesdep +textPVP ok

version bump matches the API change (PVP)

Dependencies added: blaze-builder, http-types, text

API changes (from Hackage documentation)

- Web.Routes.RouteT: class ShowURL m where { type family URL m; }
- Web.Routes.RouteT: crossURL :: Monad m => (url2 -> url1) -> [(String, String)] -> RouteT url1 m (url2 -> Link)
- Web.Routes.RouteT: instance Monad m => ShowURL (RouteT url m)
- Web.Routes.RouteT: type Link = String
+ Web.Routes.PathInfo: instance PathInfo Text
+ Web.Routes.PathInfo: instance PathInfo [Text]
+ Web.Routes.PathInfo: stripOverlapBS :: ByteString -> ByteString -> ByteString
+ Web.Routes.PathInfo: stripOverlapText :: Text -> Text -> Text
+ Web.Routes.RouteT: askRouteFn :: MonadRoute m => m (URL m -> [(Text, Maybe Text)] -> Text)
+ Web.Routes.RouteT: class Monad m => MonadRoute m where { type family URL m; }
+ Web.Routes.RouteT: instance Monad m => MonadRoute (RouteT url m)
- Web.Routes.Base: decodePathInfo :: String -> [String]
+ Web.Routes.Base: decodePathInfo :: ByteString -> [Text]
- Web.Routes.Base: encodePathInfo :: [String] -> [(String, String)] -> String
+ Web.Routes.Base: encodePathInfo :: [Text] -> [(Text, Maybe Text)] -> Text
- Web.Routes.PathInfo: anySegment :: URLParser String
+ Web.Routes.PathInfo: anySegment :: URLParser Text
- Web.Routes.PathInfo: fromPathInfo :: PathInfo url => String -> Either String url
+ Web.Routes.PathInfo: fromPathInfo :: PathInfo url => ByteString -> Either String url
- Web.Routes.PathInfo: mkSitePI :: PathInfo url => ((url -> [(String, String)] -> String) -> url -> a) -> Site url a
+ Web.Routes.PathInfo: mkSitePI :: PathInfo url => ((url -> [(Text, Maybe Text)] -> Text) -> url -> a) -> Site url a
- Web.Routes.PathInfo: pToken :: tok -> (String -> Maybe a) -> URLParser a
+ Web.Routes.PathInfo: pToken :: tok -> (Text -> Maybe a) -> URLParser a
- Web.Routes.PathInfo: parseSegments :: URLParser a -> [String] -> Either String a
+ Web.Routes.PathInfo: parseSegments :: URLParser a -> [Text] -> Either String a
- Web.Routes.PathInfo: patternParse :: ([String] -> Either String a) -> URLParser a
+ Web.Routes.PathInfo: patternParse :: ([Text] -> Either String a) -> URLParser a
- Web.Routes.PathInfo: segment :: String -> URLParser String
+ Web.Routes.PathInfo: segment :: Text -> URLParser Text
- Web.Routes.PathInfo: toPathInfo :: PathInfo url => url -> String
+ Web.Routes.PathInfo: toPathInfo :: PathInfo url => url -> Text
- Web.Routes.PathInfo: toPathInfoParams :: PathInfo url => url -> [(String, String)] -> String
+ Web.Routes.PathInfo: toPathInfoParams :: PathInfo url => url -> [(Text, Maybe Text)] -> Text
- Web.Routes.PathInfo: toPathSegments :: PathInfo url => url -> [String]
+ Web.Routes.PathInfo: toPathSegments :: PathInfo url => url -> [Text]
- Web.Routes.PathInfo: type URLParser a = GenParser String () a
+ Web.Routes.PathInfo: type URLParser a = GenParser Text () a
- Web.Routes.RouteT: RouteT :: ((url -> [(String, String)] -> Link) -> m a) -> RouteT url m a
+ Web.Routes.RouteT: RouteT :: ((url -> [(Text, Maybe Text)] -> Text) -> m a) -> RouteT url m a
- Web.Routes.RouteT: askRouteT :: Monad m => RouteT url m (url -> [(String, String)] -> String)
+ Web.Routes.RouteT: askRouteT :: Monad m => RouteT url m (url -> [(Text, Maybe Text)] -> Text)
- Web.Routes.RouteT: nestURL :: Monad m => (url2 -> url1) -> RouteT url2 m a -> RouteT url1 m a
+ Web.Routes.RouteT: nestURL :: (url1 -> url2) -> RouteT url1 m a -> RouteT url2 m a
- Web.Routes.RouteT: runRouteT :: (url -> RouteT url m a) -> ((url -> [(String, String)] -> String) -> url -> m a)
+ Web.Routes.RouteT: runRouteT :: (url -> RouteT url m a) -> ((url -> [(Text, Maybe Text)] -> Text) -> url -> m a)
- Web.Routes.RouteT: showURL :: ShowURL m => URL m -> m Link
+ Web.Routes.RouteT: showURL :: MonadRoute m => URL m -> m Text
- Web.Routes.RouteT: showURLParams :: ShowURL m => (URL m) -> [(String, String)] -> m Link
+ Web.Routes.RouteT: showURLParams :: MonadRoute m => URL m -> [(Text, Maybe Text)] -> m Text
- Web.Routes.RouteT: unRouteT :: RouteT url m a -> (url -> [(String, String)] -> Link) -> m a
+ Web.Routes.RouteT: unRouteT :: RouteT url m a -> (url -> [(Text, Maybe Text)] -> Text) -> m a
- Web.Routes.RouteT: withRouteT :: ((url' -> [(String, String)] -> Link) -> (url -> [(String, String)] -> Link)) -> RouteT url m a -> RouteT url' m a
+ Web.Routes.RouteT: withRouteT :: ((url' -> [(Text, Maybe Text)] -> Text) -> (url -> [(Text, Maybe Text)] -> Text)) -> RouteT url m a -> RouteT url' m a
- Web.Routes.Site: Site :: ((url -> [(String, String)] -> String) -> url -> a) -> (url -> ([String], [(String, String)])) -> ([String] -> Either String url) -> Site url a
+ Web.Routes.Site: Site :: ((url -> [(Text, Maybe Text)] -> Text) -> url -> a) -> (url -> ([Text], [(Text, Maybe Text)])) -> ([Text] -> Either String url) -> Site url a
- Web.Routes.Site: formatPathSegments :: Site url a -> url -> ([String], [(String, String)])
+ Web.Routes.Site: formatPathSegments :: Site url a -> url -> ([Text], [(Text, Maybe Text)])
- Web.Routes.Site: handleSite :: Site url a -> (url -> [(String, String)] -> String) -> url -> a
+ Web.Routes.Site: handleSite :: Site url a -> (url -> [(Text, Maybe Text)] -> Text) -> url -> a
- Web.Routes.Site: parsePathSegments :: Site url a -> [String] -> Either String url
+ Web.Routes.Site: parsePathSegments :: Site url a -> [Text] -> Either String url
- Web.Routes.Site: runSite :: String -> Site url a -> String -> (Either String a)
+ Web.Routes.Site: runSite :: Text -> Site url a -> ByteString -> (Either String a)

Files

Web/Routes/Base.hs view
@@ -16,9 +16,14 @@        , decodePathInfo        ) where +import Blaze.ByteString.Builder (Builder, toByteString) import Codec.Binary.UTF8.String (encodeString, decodeString)+import Data.ByteString (ByteString) import Data.List (intercalate, intersperse)+import Data.Text          (Text)+import Data.Text.Encoding as Text (encodeUtf8, decodeUtf8) import Network.URI+import Network.HTTP.Types (encodePath, decodePathSegments, queryTextToQuery)  {- @@ -240,8 +245,15 @@ \"%D7%A9%D7%9C%D7%95%D7%9D\"  -}-encodePathInfo :: [String] -> [(String, String)] -> String-encodePathInfo pieces qs = +encodePathInfo :: [Text] -> [(Text, Maybe Text)] -> Text+encodePathInfo segments qs =+    Text.decodeUtf8 $ toByteString $ encodePathInfoUtf8 segments qs++encodePathInfoUtf8 :: [Text] -> [(Text, Maybe Text)] -> Builder+encodePathInfoUtf8 segments qs = encodePath segments (queryTextToQuery qs)++encodePathInfoString :: [String] -> [(String, String)] -> String+encodePathInfoString pieces qs =    let x = map encodeString  `o` -- utf-8 encode the data characters in path components (we have not added any delimiters yet)           map (escapeURIString (\c -> isUnreserved c || c `elem` ":@&=+$,"))   `o` -- percent encode the characters           map (\str -> case str of "." -> "%2E" ; ".." -> "%2E%2E" ; _ -> str) `o` -- encode . and ..@@ -278,9 +290,13 @@  [\"\"] +Note that while function accepts a 'Text' value, it is expected that 'Text' will only contain the subset of characters which are allowed to appear in a URL. -}-decodePathInfo :: String -> [String]-decodePathInfo =+decodePathInfo :: ByteString -> [Text]+decodePathInfo = decodePathSegments++decodePathInfoString :: String -> [String]+decodePathInfoString =   splitPaths         `o` -- split path on delimiters   map unEscapeString `o` -- decode any percent encoded characters   map decodeString       -- decode octets
Web/Routes/PathInfo.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-} module Web.Routes.PathInfo     ( stripOverlap+    , stripOverlapBS+    , stripOverlapText     , URLParser     , pToken     , segment@@ -15,10 +17,17 @@     , showParseError     ) where -import Control.Applicative ((<*))+import Blaze.ByteString.Builder (Builder, toByteString)+import Control.Applicative ((<$>), (<*)) import Control.Monad (msum)-import Data.List (stripPrefix, tails)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B+import Data.List as List (stripPrefix, tails)+import Data.Text as Text (Text, pack, unpack, null, tails, stripPrefix)+import Data.Text.Encoding (decodeUtf8)+import Data.Text.Read (decimal) import Data.Maybe (fromJust)+import Network.HTTP.Types  import Text.ParserCombinators.Parsec.Combinator (notFollowedBy) import Text.ParserCombinators.Parsec.Error (ParseError, errorPos, errorMessages, showErrorMessages) import Text.ParserCombinators.Parsec.Pos   (incSourceLine, sourceName, sourceLine, sourceColumn)@@ -28,20 +37,32 @@  -- this is not very efficient. Among other things, we need only consider the last 'n' characters of x where n == length y. stripOverlap :: (Eq a) => [a] -> [a] -> [a]-stripOverlap x y = fromJust $ msum $ [ stripPrefix p y | p <- tails x]+stripOverlap x y = fromJust $ msum $ [ List.stripPrefix p y | p <- List.tails x] -type URLParser a = GenParser String () a+stripOverlapText :: Text -> Text -> Text+stripOverlapText x y = fromJust $ msum $ [ Text.stripPrefix p y | p <- Text.tails x ] -pToken :: tok -> (String -> Maybe a) -> URLParser a+stripOverlapBS :: B.ByteString -> B.ByteString -> B.ByteString+stripOverlapBS x y = fromJust $ msum $ [ stripPrefix p y | p <- B.tails x ] -- fromJust will never fail+    where+      stripPrefix :: B.ByteString -> B.ByteString -> Maybe B.ByteString+      stripPrefix x y+          | x `B.isPrefixOf` y = Just $ B.drop (B.length x) y+          | otherwise        = Nothing+++type URLParser a = GenParser Text () a++pToken :: tok -> (Text -> Maybe a) -> URLParser a pToken msg f = do pos <- getPosition-                  token id (const $ incSourceLine pos 1) f+                  token unpack (const $ incSourceLine pos 1) f  -- | match on a specific string-segment :: String -> URLParser String-segment x = (pToken (const x) (\y -> if x == y then Just x else Nothing)) <?> x+segment :: Text -> URLParser Text+segment x = (pToken (const x) (\y -> if x == y then Just x else Nothing)) <?> unpack x  -- | match on any string-anySegment :: URLParser String+anySegment :: URLParser Text anySegment = pToken (const "any string") Just  -- | Only matches if all segments have been consumed@@ -57,7 +78,7 @@ -- > foo _              = Left "parse error" --  -- > patternParse foo-patternParse :: ([String] -> Either String a) -> URLParser a+patternParse :: ([Text] -> Either String a) -> URLParser a patternParse p =   do segs <- getInput      case p segs of@@ -65,13 +86,21 @@          do setInput []             return r        (Left err) -> fail err-       ++-- | show Parsec 'ParseError' using terms that relevant to parsing a url+showParseError :: ParseError -> String+showParseError pErr =+  let pos    = errorPos pErr+      posMsg = sourceName pos ++ " (segment " ++ show (sourceLine pos) ++ " character " ++ show (sourceColumn pos) ++ "): "+      msgs   = errorMessages pErr+  in posMsg ++ showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" msgs+ -- | run a 'URLParser' on a list of path segments -- -- returns @Left "parse error"@ on failure. -- -- returns @Right a@ on success-parseSegments :: URLParser a -> [String] -> Either String a+parseSegments :: URLParser a -> [Text] -> Either String a parseSegments p segments =   case parse (p <* eof) (show segments) segments of     (Left e)  -> Left (showParseError e)@@ -121,19 +150,23 @@ -}  class PathInfo url where-  toPathSegments :: url -> [String]+  toPathSegments :: url -> [Text]   fromPathSegments :: URLParser url  -- |convert url into the path info portion of a URL-toPathInfo :: (PathInfo url) => url -> String-toPathInfo = ('/' :) . flip encodePathInfo [] . toPathSegments+toPathInfo :: (PathInfo url) => url -> Text+toPathInfo =  decodeUtf8 . toByteString . toPathInfoUtf8 +-- |convert url into the path info portion of a URL+toPathInfoUtf8 :: (PathInfo url) => url -> Builder+toPathInfoUtf8 =  flip encodePath [] . toPathSegments+ -- |convert url + params into the path info portion of a URL + a query string toPathInfoParams :: (PathInfo url) =>                     url -- ^ url-                 -> [(String, String)] -- ^ query string parameter-                 -> String-toPathInfoParams url params = ('/' :) . flip encodePathInfo params . toPathSegments $ url+                 -> [(Text, Maybe Text)] -- ^ query string parameter+                 -> Text+toPathInfoParams url params = encodePathInfo (toPathSegments url) params  -- should this fail if not all the input was consumed?   --@@ -154,16 +187,19 @@ -- returns @Left "parse error"@ on failure -- -- returns @Right url@ on success-fromPathInfo :: (PathInfo url) => String -> Either String url++fromPathInfo :: (PathInfo url) => ByteString -> Either String url fromPathInfo pi =   parseSegments fromPathSegments (decodePathInfo $ dropSlash pi)   where-    dropSlash ('/':rs) = rs-    dropSlash x        = x+    dropSlash s =+        if ((B.pack "/") `B.isPrefixOf` s) +        then B.tail s+        else s  -- | turn a routing function into a 'Site' value using the 'PathInfo' class mkSitePI :: (PathInfo url) =>-            ((url -> [(String, String)] -> String) -> url -> a) -- ^ a routing function+            ((url -> [(Text, Maybe Text)] -> Text) -> url -> a) -- ^ a routing function          -> Site url a mkSitePI handler =   Site { handleSite         = handler@@ -171,36 +207,41 @@        , parsePathSegments  = parseSegments fromPathSegments        } --- | show Parsec 'ParseError' using terms that relevant to parsing a url-showParseError :: ParseError -> String-showParseError pErr =-  let pos    = errorPos pErr-      posMsg = sourceName pos ++ " (segment " ++ show (sourceLine pos) ++ " character " ++ show (sourceColumn pos) ++ "): "-      msgs   = errorMessages pErr-  in posMsg ++ showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" msgs- -- it's instances all the way down -instance PathInfo [String] where+instance PathInfo Text where+  toPathSegments = (:[])+  fromPathSegments = anySegment++instance PathInfo [Text] where   toPathSegments = id   fromPathSegments = many anySegment  instance PathInfo String where-  toPathSegments = (:[])-  fromPathSegments = anySegment+  toPathSegments = (:[]) . pack+  fromPathSegments = unpack <$> anySegment +instance PathInfo [String] where+  toPathSegments = id . map pack+  fromPathSegments = many (unpack <$> anySegment)+ instance PathInfo Int where-  toPathSegments i = [show i]-  fromPathSegments = pToken (const "int") checkInt-   where checkInt str =-           case reads str of-             [(n,[])] -> Just n-             _ ->        Nothing+  toPathSegments i = [pack $ show i]+  fromPathSegments = pToken (const "Int") checkInt+   where checkInt txt =+           case decimal txt of+             (Left e) -> Nothing+             (Right (n, r))+                 | Text.null r -> Just n+                 | otherwise -> Nothing  instance PathInfo Integer where-  toPathSegments i = [show i]-  fromPathSegments = pToken (const "integer") checkInteger-   where checkInteger str =-           case reads str of-             [(n,[])] -> Just n-             _ ->        Nothing+  toPathSegments i = [pack $ show i]+  fromPathSegments = pToken (const "Integer") checkInt+   where checkInt txt =+           case decimal txt of+             (Left e) -> Nothing+             (Right (n, r))+                 | Text.null r -> Just n+                 | otherwise -> Nothing+
Web/Routes/QuickCheck.hs view
@@ -1,9 +1,10 @@ module Web.Routes.QuickCheck where +import qualified Data.Text.Encoding as Text import Web.Routes.PathInfo (PathInfo, toPathInfo, fromPathInfo)  pathInfoInverse_prop :: (Eq url, PathInfo url) => url -> Bool pathInfoInverse_prop url =-    case (fromPathInfo $ toPathInfo url) of+    case (fromPathInfo $ Text.encodeUtf8 $ toPathInfo url) of       Right url' -> url == url'       _ -> False
Web/Routes/RouteT.hs view
@@ -23,20 +23,23 @@ import Control.Monad.State(MonadState(get,put)) import Control.Monad.Trans (MonadTrans(lift), MonadIO(liftIO)) import Control.Monad.Writer(MonadWriter(listen, tell, pass))+import Data.Text (Text)   -- * RouteT Monad Transformer -type Link = String- -- |monad transformer for generating URLs-newtype RouteT url m a = RouteT { unRouteT :: (url -> [(String, String)] -> Link) -> m a }+newtype RouteT url m a = RouteT { unRouteT :: (url -> [(Text, Maybe Text)] -> Text) -> m a } +class (Monad m) => MonadRoute m where+    type URL m+    askRouteFn :: m (URL m -> [(Text, Maybe Text)] -> Text)+ -- | convert a 'RouteT' based route handler to a handler that can be used with the 'Site' type -- -- NOTE: this function used to be the same as 'unRouteT'. If you want the old behavior, just call 'unRouteT'. runRouteT :: (url -> RouteT url m a) -          -> ((url -> [(String, String)] -> String) -> url -> m a)+          -> ((url -> [(Text, Maybe Text)] -> Text) -> url -> m a) runRouteT r = \f u -> (unRouteT (r u)) f  -- | Transform the computation inside a @RouteT@.@@ -44,13 +47,13 @@ mapRouteT f (RouteT m) = RouteT $ f . m  -- | Execute a computation in a modified environment-withRouteT :: ((url' -> [(String, String)] -> Link) -> (url -> [(String, String)] -> Link)) -> RouteT url m a -> RouteT url' m a+withRouteT :: ((url' -> [(Text, Maybe Text)] -> Text) -> (url -> [(Text, Maybe Text)] -> Text)) -> RouteT url m a -> RouteT url' m a withRouteT f (RouteT m) = RouteT $ m . f  liftRouteT :: m a -> RouteT url m a liftRouteT m = RouteT (const m) -askRouteT :: (Monad m) => RouteT url m (url -> [(String, String)] -> String)+askRouteT :: (Monad m) => RouteT url m (url -> [(Text, Maybe Text)] -> Text) askRouteT = RouteT return  instance (Functor m) => Functor (RouteT url m) where@@ -108,26 +111,21 @@   listen m = mapRouteT listen m   pass   m = mapRouteT pass   m --class ShowURL m where-    type URL m-    showURLParams :: (URL m) -> [(String, String)] -> m Link -- ^ convert a URL value and a parameter list into a Link (aka, a String)--instance (Monad m) => ShowURL (RouteT url m) where+instance (Monad m) => MonadRoute (RouteT url m) where     type URL (RouteT url m) = url-    showURLParams url params =-        do showF <- askRouteT-           return (showF url params)+    askRouteFn = askRouteT --- | convert a URL value into a Link (aka, a String) using a null parameter list.-showURL :: ShowURL m => URL m -> m Link  -showURL url = showURLParams url []+showURL :: (MonadRoute m) => URL m -> m Text+showURL url = +    do showFn <- askRouteFn+       return (showFn url []) --- |used to embed a RouteT into a larger parent url-nestURL :: (Monad m) => (url2 -> url1) -> RouteT url2 m a -> RouteT url1 m a-nestURL b = withRouteT (. b)+showURLParams  :: (MonadRoute m) => URL m -> [(Text, Maybe Text)] -> m Text+showURLParams url params = +    do showFn <- askRouteFn+       return (showFn url params) -crossURL :: (Monad m) => (url2 -> url1) -> [(String, String)] -> RouteT url1 m (url2 -> Link)-crossURL f params = -    do showF <- askRouteT-       return $ \url2 -> showF (f url2) params+nestURL :: (url1 -> url2) -> RouteT url1 m a -> RouteT url2 m a+nestURL transform (RouteT r) =+    do RouteT $ \showFn ->+           r (\url params -> showFn (transform url) params)
Web/Routes/Site.hs view
@@ -1,5 +1,8 @@ module Web.Routes.Site where +import Data.ByteString+import Data.Monoid+import Data.Text (Text) import Web.Routes.Base (decodePathInfo, encodePathInfo)  {-|@@ -29,11 +32,11 @@                Well behaving applications should use this function to                generating all internal URLs.            -}-             handleSite         :: (url -> [(String, String)] -> String) -> url -> a+             handleSite         :: (url -> [(Text, Maybe Text)] -> Text) -> url -> a            -- | This function must be the inverse of 'parsePathSegments'.-           , formatPathSegments :: url -> ([String], [(String, String)])+           , formatPathSegments :: url -> ([Text], [(Text, Maybe Text)])            -- | This function must be the inverse of 'formatPathSegments'.-           , parsePathSegments  :: [String] -> Either String url+           , parsePathSegments  :: [Text] -> Either String url            }  -- | Override the \"default\" URL, ie the result of 'parsePathSegments' [].@@ -48,15 +51,15 @@   fmap f site = site { handleSite = \showFn u -> f (handleSite site showFn u) }  -- | Retrieve the application to handle a given request.-runSite :: String -- ^ application root, with trailing slash+runSite :: Text -- ^ application root, with trailing slash         -> Site url a-        -> String -- ^ path info, leading slash stripped+        -> ByteString -- ^ path info, leading slash stripped         -> (Either String a) runSite approot site pathInfo =     case parsePathSegments site $ decodePathInfo pathInfo of         (Left errs) -> (Left errs)-        (Right url) -> Right $ handleSite site go url+        (Right url) -> Right $ handleSite site showFn url   where-    go url qs =+    showFn url qs =         let (pieces, qs') = formatPathSegments site url-        in approot ++ encodePathInfo pieces (qs ++ qs')+        in approot `mappend` (encodePathInfo pieces (qs ++ qs'))
web-routes.cabal view
@@ -1,5 +1,5 @@ Name:             web-routes-Version:          0.25.3+Version:          0.26.2 License:          BSD3 License-File:     LICENSE Author:           jeremy@seereason.com@@ -13,10 +13,13 @@  Library         Build-Depends:    base >= 4 && < 5,+                          blaze-builder >= 0.2 && < 0.4,                           parsec >= 2 && <4,                           bytestring >= 0.9 && < 0.10,+                          http-types == 0.6.*,                           mtl,                           network >= 2.2 && < 2.4,+                          text == 0.11.*,                           utf8-string >= 0.3 && < 0.4         Exposed-Modules:  Web.Routes                           Web.Routes.Base