{-# LANGUAGE OverloadedStrings #-} module Links (links) where import Control.Applicative import Control.DeepSeq (NFData(..)) import Criterion.Main (Benchmark, bench, nf) import Data.Attoparsec as A import Data.Attoparsec.Char8 as A8 import Data.ByteString.Char8 as B8 data Link = Link { linkURL :: ByteString , linkParams :: [(ByteString, ByteString)] } deriving (Eq, Show) instance NFData Link where rnf l = rnf (linkURL l) `seq` rnf (linkParams l) link :: Parser Link link = Link <$> url <*> many (char8 ';' *> skipSpace *> param) where url = char8 '<' *> A8.takeTill (=='>') <* char8 '>' <* skipSpace param :: Parser (ByteString, ByteString) param = do name <- paramName skipSpace *> "=" *> skipSpace c <- peekChar' let isTokenChar = A.inClass "!#$%&'()*+./0-9:<=>?@a-zA-Z[]^_`{|}~-" val <- case c of '"' -> quotedString _ -> A.takeWhile isTokenChar skipSpace return (name, val) data Quot = Literal | Backslash quotedString :: Parser ByteString quotedString = char '"' *> (fixup <$> body) <* char '"' where body = A8.scan Literal $ \s c -> case (s,c) of (Literal, '\\') -> backslash (Literal, '"') -> Nothing _ -> literal literal = Just Literal backslash = Just Backslash fixup = B8.pack . go . B8.unpack where go ('\\' : x@'\\' : xs) = x : go xs go ('\\' : x@'"' : xs) = x : go xs go (x : xs) = x : go xs go xs = xs paramName :: Parser ByteString paramName = do name <- A.takeWhile1 $ A.inClass "a-zA-Z0-9!#$&+-.^_`|~" c <- peekChar return $ case c of Just '*' -> B8.snoc name '*' _ -> name links :: Benchmark links = bench "links" $ nf (A.parseOnly link) lnk where lnk = "; rel=\"next\", ; rel=\"last\""