packages feed

web-routes 0.27.1 → 0.27.2

raw patch · 6 files changed

+188/−31 lines, 6 filesdep +HUnitdep +QuickCheckdep +ghc-primdep ~bytestringdep ~http-typesdep ~networkPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: HUnit, QuickCheck, ghc-prim, split, test-framework, test-framework-hunit, test-framework-quickcheck2, test-framework-th, web-routes

Dependency ranges changed: bytestring, http-types, network

API changes (from Hackage documentation)

+ Web.Routes.PathInfo: class Generic a
+ Web.Routes.PathInfo: instance (GPathInfo a, Constructor c) => GPathInfo (C1 c a)
+ Web.Routes.PathInfo: instance (GPathInfo a, GPathInfo b) => GPathInfo (a :*: b)
+ Web.Routes.PathInfo: instance (GPathInfo a, GPathInfo b) => GPathInfo (a :+: b)
+ Web.Routes.PathInfo: instance GPathInfo U1
+ Web.Routes.PathInfo: instance GPathInfo a => GPathInfo (D1 c a)
+ Web.Routes.PathInfo: instance GPathInfo a => GPathInfo (S1 c a)
+ Web.Routes.PathInfo: instance PathInfo a => GPathInfo (K1 i a)
- Web.Routes.PathInfo: class PathInfo url
+ Web.Routes.PathInfo: class PathInfo url where toPathSegments = gtoPathSegments . from fromPathSegments = to <$> gfromPathSegments

Files

Web/Routes/Base.hs view
@@ -253,7 +253,7 @@ encodePathInfoUtf8 segments qs = encodePath segments (queryTextToQuery qs)  encodePathInfoString :: [String] -> [(String, String)] -> String-encodePathInfoString pieces qs = +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 ..@@ -261,7 +261,7 @@       y = paramsToQueryString qs    in x pieces ++ y     where-      -- reverse composition +      -- reverse composition       o :: (a -> b) -> (b -> c) -> a -> c       o = flip (.) 
Web/Routes/PathInfo.hs view
@@ -1,4 +1,9 @@-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}+{-# LANGUAGE CPP, FlexibleInstances, TypeSynonymInstances #-}++#if __GLASGOW_HASKELL__ > 702+{-# LANGUAGE DefaultSignatures, OverloadedStrings, ScopedTypeVariables, TypeOperators #-}+#endif+ module Web.Routes.PathInfo     ( stripOverlap     , stripOverlapBS@@ -15,6 +20,10 @@     , fromPathInfo     , mkSitePI     , showParseError+#if __GLASGOW_HASKELL__ > 702+    -- * Re-exported for convenience+    , Generic+#endif     ) where  import Blaze.ByteString.Builder (Builder, toByteString)@@ -27,7 +36,7 @@ import Data.Text.Encoding (decodeUtf8) import Data.Text.Read (decimal, signed) import Data.Maybe (fromJust)-import Network.HTTP.Types +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)@@ -35,6 +44,14 @@ import Web.Routes.Base (decodePathInfo, encodePathInfo) import Web.Routes.Site (Site(..)) +#if __GLASGOW_HASKELL__ > 702+import Control.Applicative ((<$), (<*>), (<|>), pure)+import Data.Char (toLower, isUpper)+import Data.List (intercalate)+import Data.List.Split (split, dropInitBlank, keepDelimsL, whenElt)+import GHC.Generics+#endif+ -- 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 $ [ List.stripPrefix p y | p <- List.tails x]@@ -76,7 +93,7 @@ -- > foo ["foo", "bar"] = Right (Foo Bar) -- > foo ["baz"]        = Right Baz -- > foo _              = Left "parse error"--- +-- -- > patternParse foo patternParse :: ([Text] -> Either String a) -> URLParser a patternParse p =@@ -111,8 +128,8 @@ This requires parsec 3, can't figure out how to do it in parsec 2 yet.  p2u :: Parser a -> URLParser a-p2u p = -  mkPT $ \state@(State sInput sPos sUser) -> +p2u p =+  mkPT $ \state@(State sInput sPos sUser) ->   case sInput of     (s:ss) ->        do r <- runParsecT p (State s sPos sUser)@@ -121,22 +138,22 @@     where       fixReply :: [String] -> (Reply String u a) -> (Reply [String] u a)       fixReply _ (Error err) = (Error err)-      fixReply ss (Ok a (State "" sPos sUser) e) = (Ok a (State ss sPos sUser) e) -      fixReply ss (Ok a (State s sPos sUser) e) = (Ok a (State (s:ss) sPos sUser) e) +      fixReply ss (Ok a (State "" sPos sUser) e) = (Ok a (State ss sPos sUser) e)+      fixReply ss (Ok a (State s sPos sUser) e) = (Ok a (State (s:ss) sPos sUser) e) -}  {- p2u :: Parser a -> URLParser a-p2u p = +p2u p =   do (State sInput sPos sUser) <- getParserState      case sInput of        (s:ss) -> let r = runParser p () "" s                  in case r of                       (Left e) -> return e -}-       + {--  mkPT $ \state@(State sInput sPos sUser) -> +  mkPT $ \state@(State sInput sPos sUser) ->   case sInput of     (s:ss) ->        do r <- runParsecT p (State s sPos sUser)@@ -145,14 +162,92 @@     where       fixReply :: [String] -> (Reply String u a) -> (Reply [String] u a)       fixReply _ (Error err) = (Error err)-      fixReply ss (Ok a (State "" sPos sUser) e) = (Ok a (State ss sPos sUser) e) -      fixReply ss (Ok a (State s sPos sUser) e) = (Ok a (State (s:ss) sPos sUser) e) +      fixReply ss (Ok a (State "" sPos sUser) e) = (Ok a (State ss sPos sUser) e)+      fixReply ss (Ok a (State s sPos sUser) e) = (Ok a (State (s:ss) sPos sUser) e) -} +#if __GLASGOW_HASKELL__ > 702++hyphenate :: String -> Text+hyphenate =+    pack . intercalate "-" . map (map toLower) . split splitter+  where+    splitter = dropInitBlank . keepDelimsL . whenElt $ isUpper++class GPathInfo f where+  gtoPathSegments :: f url -> [Text]+  gfromPathSegments :: URLParser (f url)++instance GPathInfo U1 where+  gtoPathSegments U1 = []+  gfromPathSegments = pure U1++instance GPathInfo a => GPathInfo (D1 c a) where+  gtoPathSegments = gtoPathSegments . unM1+  gfromPathSegments = M1 <$> gfromPathSegments++instance GPathInfo a => GPathInfo (S1 c a) where+  gtoPathSegments = gtoPathSegments . unM1+  gfromPathSegments = M1 <$> gfromPathSegments++instance forall c a. (GPathInfo a, Constructor c) => GPathInfo (C1 c a) where+  gtoPathSegments m@(M1 x) = (hyphenate . conName) m : gtoPathSegments x+  gfromPathSegments = M1 <$ segment (hyphenate . conName $ (undefined :: C1 c a r))+                         <*> gfromPathSegments++instance (GPathInfo a, GPathInfo b) => GPathInfo (a :*: b) where+  gtoPathSegments (a :*: b) = gtoPathSegments a ++ gtoPathSegments b+  gfromPathSegments = (:*:) <$> gfromPathSegments <*> gfromPathSegments++instance (GPathInfo a, GPathInfo b) => GPathInfo (a :+: b) where+  gtoPathSegments (L1 x) = gtoPathSegments x+  gtoPathSegments (R1 x) = gtoPathSegments x+  gfromPathSegments = L1 <$> gfromPathSegments+                  <|> R1 <$> gfromPathSegments++instance PathInfo a => GPathInfo (K1 i a) where+  gtoPathSegments = toPathSegments . unK1+  gfromPathSegments = K1 <$> fromPathSegments++#endif++-- | Simple parsing and rendering for a type to and from URL path segments.+--+-- If you're using GHC 7.2 or later, you can use @DeriveGeneric@ to derive+-- instances of this class:+--+-- > {-# LANGUAGE DeriveGeneric #-}+-- > data Sitemap = Home | BlogPost Int deriving Generic+-- > instance PathInfo Sitemap+--+-- This results in the following instance:+--+-- > instance PathInfo Sitemap where+-- >     toPathSegments Home = ["home"]+-- >     toPathSegments (BlogPost x) = "blog-post" : toPathSegments x+-- >     fromPathSegments = Home <$ segment "home"+-- >                    <|> BlogPost <$ segment "blog-post" <*> fromPathSegments+--+-- And here it is in action:+--+-- >>> toPathInfo (BlogPost 123)+-- "/blog-post/123"+-- >>> fromPathInfo "/blog-post/123" :: Either String Sitemap+-- Right (BlogPost 123)+--+-- To instead derive instances using @TemplateHaskell@, see+-- <http://hackage.haskell.org/package/web-routes-th web-routes-th>. class PathInfo url where   toPathSegments :: url -> [Text]   fromPathSegments :: URLParser url +#if __GLASGOW_HASKELL__ > 702+  default toPathSegments :: (Generic url, GPathInfo (Rep url)) => url -> [Text]+  toPathSegments = gtoPathSegments . from+  default fromPathSegments :: (Generic url, GPathInfo (Rep url)) => URLParser url+  fromPathSegments = to <$> gfromPathSegments+#endif+ -- |convert url into the path info portion of a URL toPathInfo :: (PathInfo url) => url -> Text toPathInfo =  decodeUtf8 . toByteString . toPathInfoUtf8@@ -168,7 +263,7 @@                  -> Text toPathInfoParams url params = encodePathInfo (toPathSegments url) params --- should this fail if not all the input was consumed?  +-- should this fail if not all the input was consumed? -- -- in theory we -- require the pathInfo to have the initial '/', but this code will@@ -182,8 +277,8 @@ -- However, if the pathInfo was prepend with http://example.org/ with -- a trailing slash, then things might not line up. --- | parse a 'String' into 'url' using 'PathInfo'. --- +-- | parse a 'String' into 'url' using 'PathInfo'.+-- -- returns @Left "parse error"@ on failure -- -- returns @Right url@ on success@@ -193,7 +288,7 @@   parseSegments fromPathSegments (decodePathInfo $ dropSlash pi)   where     dropSlash s =-        if ((B.pack "/") `B.isPrefixOf` s) +        if ((B.singleton '/') `B.isPrefixOf` s)         then B.tail s         else s 
Web/Routes/QuickCheck.hs view
@@ -3,6 +3,12 @@ import qualified Data.Text.Encoding as Text import Web.Routes.PathInfo (PathInfo, toPathInfo, fromPathInfo) +-- | test that a 'PathInfo' instance is valid+--+-- Generates 'Arbitrary' 'url' values and checks that:+--+--    fromPathInfo . toPathInfo == id+-- pathInfoInverse_prop :: (Eq url, PathInfo url) => url -> Bool pathInfoInverse_prop url =     case (fromPathInfo $ Text.encodeUtf8 $ toPathInfo url) of
Web/Routes/RouteT.hs view
@@ -38,7 +38,7 @@ -- | 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) +runRouteT :: (url -> RouteT url m a)           -> ((url -> [(Text, Maybe Text)] -> Text) -> url -> m a) runRouteT r = \f u -> (unRouteT (r u)) f @@ -59,7 +59,7 @@ instance (Functor m) => Functor (RouteT url m) where   fmap f = mapRouteT (fmap f) -instance (Applicative m) => Applicative (RouteT url m) where  +instance (Applicative m) => Applicative (RouteT url m) where   pure = liftRouteT . pure   f <*> v = RouteT $ \ url -> unRouteT f url <*> unRouteT v url @@ -90,16 +90,16 @@ instance (MonadFix m) => MonadFix (RouteT url m) where     mfix f = RouteT $ \ url -> mfix $ \ a -> unRouteT (f a) url -instance (MonadIO m) => MonadIO (RouteT url m) where  +instance (MonadIO m) => MonadIO (RouteT url m) where   liftIO = lift . liftIO  instance (MonadReader r m) => MonadReader r (RouteT url m) where   ask   = liftRouteT ask   local f = mapRouteT (local f) -instance (MonadRWS r w s m) => MonadRWS r w s (RouteT url m)  +instance (MonadRWS r w s m) => MonadRWS r w s (RouteT url m) -instance (MonadState s m) => MonadState s (RouteT url m) where  +instance (MonadState s m) => MonadState s (RouteT url m) where   get = liftRouteT get   put s = liftRouteT $ put s @@ -116,12 +116,12 @@     askRouteFn = askRouteT  showURL :: (MonadRoute m) => URL m -> m Text-showURL url = +showURL url =     do showFn <- askRouteFn        return (showFn url [])  showURLParams  :: (MonadRoute m) => URL m -> [(Text, Maybe Text)] -> m Text-showURLParams url params = +showURLParams url params =     do showFn <- askRouteFn        return (showFn url params) 
+ test/Test.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, OverloadedStrings, TemplateHaskell #-}++module Main (main) where++import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import Test.Framework.TH+import Test.HUnit+import Test.QuickCheck+import Web.Routes++newtype ArticleId = ArticleId Int deriving (Eq, Show, Num, PathInfo, Arbitrary)++data Sitemap+    = Home+    | Article ArticleId+    deriving (Eq, Show, Generic)++instance PathInfo Sitemap++instance Arbitrary Sitemap where+    arbitrary = oneof [return Home, fmap Article arbitrary]++prop_PathInfo_isomorphism :: Sitemap -> Bool+prop_PathInfo_isomorphism = pathInfoInverse_prop++case_toPathInfo :: Assertion+case_toPathInfo =+    do toPathInfo Home @?= "/home"+       toPathInfo (Article 0) @?= "/article/0"++case_fromPathInfo :: Assertion+case_fromPathInfo =+    do fromPathInfo "/home" @?= Right Home+       fromPathInfo "/article/0" @?= Right (Article 0)+       case fromPathInfo "/" :: Either String Sitemap of+         Left _ -> return ()+         url    -> assertFailure $ "expected a Left, but got: " ++ show url++main :: IO ()+main = $defaultMainGenerator
web-routes.cabal view
@@ -1,5 +1,5 @@ Name:             web-routes-Version:          0.27.1+Version:          0.27.2 License:          BSD3 License-File:     LICENSE Author:           jeremy@seereason.com@@ -8,19 +8,34 @@ Category:         Web, Language Synopsis:         Library for maintaining correctness and composability of URLs within an application. Description:      A collection of types and functions that ensure that URLs generated by an application are valid. Need more properties here.-Cabal-Version:    >= 1.6+Cabal-Version:    >= 1.8 Build-type:       Simple +test-suite Test+  type             : exitcode-stdio-1.0+  main-is          : Test.hs+  hs-source-dirs   : test+  build-depends    : base == 4.*,+                     HUnit,+                     QuickCheck,+                     test-framework,+                     test-framework-hunit,+                     test-framework-quickcheck2,+                     test-framework-th,+                     web-routes+ Library         Build-Depends:    base >= 4 && < 5,                           blaze-builder >= 0.2 && < 0.4,                           parsec >= 2 && <4,-                          bytestring >= 0.9 && < 0.10,-                          http-types == 0.6.*,+                          bytestring >= 0.9 && < 0.11,+                          http-types >= 0.6,                           mtl,-                          network >= 2.2 && < 2.4,+                          network >= 2.2 && < 2.5,                           text == 0.11.*,                           utf8-string >= 0.3 && < 0.4+        if impl(ghc >= 7.2)+          Build-Depends:  ghc-prim, split         Exposed-Modules:  Web.Routes                           Web.Routes.Base                           Web.Routes.PathInfo@@ -34,4 +49,4 @@  source-repository head     type:     darcs-    location: http://src.seereason.com/web-routes/+    location: http://hub.darcs.net/stepcut/web-routes