module Network.Wai.Route.Tree
(
Tree
, fromList
, lookup
, foldTree
, mapTree
, toList
, segments
, Payload
, value
, path
, captures
, Captures
, captured
, captureParams
, captureValues
) where
import Control.Applicative
import Data.ByteString (ByteString)
import Data.List (foldl')
import Data.HashMap.Strict (HashMap)
import Data.Maybe (fromMaybe)
import Data.Monoid
import Data.Word
import Network.HTTP.Types (urlDecode, urlEncode)
import Prelude hiding (lookup)
import qualified Data.ByteString as B
import qualified Data.HashMap.Strict as M
data Tree a = Tree
{ subtree :: HashMap ByteString (Tree a)
, capture :: Maybe (Tree a)
, payload :: Maybe (Payload a)
}
data Payload a = Payload
{ value :: !a
, path :: !ByteString
, captures :: !Captures
}
data Captures = Captures
{ params :: [ByteString]
, values :: [ByteString]
}
instance Monoid (Tree a) where
mempty = Tree mempty Nothing Nothing
a `mappend` b = Tree (subtree a <> subtree b)
(capture a <> capture b)
(payload a <|> payload b)
captureParams :: Captures -> [ByteString]
captureParams = params
captureValues :: Captures -> [ByteString]
captureValues = values
captured :: Captures -> [(ByteString, ByteString)]
captured (Captures a b) = zip a b
fromList :: [(ByteString, a)] -> Tree a
fromList = foldl' addRoute mempty
where
addRoute t p = go t (segments (fst p)) []
where
go n [] cs =
n { payload = Just (Payload (snd p) (fst p) (Captures cs [])) }
go n (c:ps) cs | B.head c == colon =
let b = fromMaybe mempty $ capture n in
n { capture = Just $! go b ps (B.tail c : cs) }
go n (d:ps) cs =
let d' = urlEncode False d
b = fromMaybe mempty $ M.lookup d' (subtree n)
in n { subtree = M.insert d' (go b ps cs) (subtree n) }
lookup :: Tree a -> [ByteString] -> Maybe (Payload a)
lookup t p = go p [] t
where
go [] cvs n =
let f e = e { captures = Captures (params (captures e)) cvs } in
f <$> payload n
go (s:ss) cvs n =
maybe (capture n >>= go ss (urlDecode False s : cvs))
(go ss cvs)
(M.lookup s $ subtree n)
foldTree :: (Payload a -> b -> b) -> b -> Tree a -> b
foldTree f z (Tree sub cap pay) =
let a = M.foldl' (foldTree f) z sub
b = maybe a (foldTree f a) cap
c = maybe b (flip f b) pay
in c
mapTree :: (Payload a -> Payload b) -> Tree a -> Tree b
mapTree f t = foldTree apply mempty t
where
apply x tr = tr { payload = Just (f x) }
toList :: Tree a -> [Payload a]
toList = foldTree (:) []
segments :: ByteString -> [ByteString]
segments = filter (not . B.null) . B.split slash
slash, colon :: Word8
slash = 0x2F
colon = 0x3A