{-# LANGUAGE OverloadedStrings #-} module Data.Git.Parser (gitObject) where import Control.Applicative import qualified Data.Attoparsec as AP (takeWhile) import qualified Data.Attoparsec.Char8 as AP (take) import Data.Attoparsec.Char8 hiding (take) import Data.Bits import qualified Data.ByteString as BS (foldl', foldr) import qualified Data.ByteString.Char8 as BS (unpack) import Data.Git.Types import Data.Char ---------------------------------------------------------------- gitObject :: Parser GitObject gitObject = do (typ,len) <- header case typ of GtBlob -> GoBlob len <$> blob len GtTree -> GoTree len <$> tree GtCommit -> GoCommit len <$> commit GtTag -> GoTag len <$> tag ---------------------------------------------------------------- header :: Parser (GitType, Int) header = (,) <$> (gitType <* spc) <*> (decimal <* nul) gitType :: Parser GitType gitType = GtBlob <$ string "blob" <|> GtTree <$ string "tree" <|> GtCommit <$ string "commit" <|> GtTag <$ string "tag" ---------------------------------------------------------------- blob :: Int -> Parser Blob blob = AP.take ---------------------------------------------------------------- tree :: Parser [GitTreeEntry] tree = many1 entry entry :: Parser GitTreeEntry entry = GitTreeEntry <$> (filetype <* spc) <*> (filepath <* nul) <*> binarySha1 where filepath = many1 $ noneOf "\0" filetype :: Parser FileType filetype = getType . fromIntegral <$> octal where getMode x = x .&. 0o7777 getType x | isBitSet x 0o0040000 = Directory | isBitSet x 0o0120000 = SymbolicLink | isBitSet x 0o0160000 = GitLink | otherwise = RegularFile (getMode x) isBitSet x mask = x .&. mask == mask ---------------------------------------------------------------- commit :: Parser GitCommit commit = GitCommit <$> tre <*> parents <*> author <*> owner <*> logmsg where tre = string "tree " *> sha1 <* endOfLine parents = many parent parent = string "parent " *> sha1 <* endOfLine author = string "author " *> line owner = string "committer " *> line logmsg = endOfLine *> AP.takeWhile (const True) <* endOfInput line = AP.takeWhile (not.isEndOfLine) <* endOfLine ---------------------------------------------------------------- tag :: Parser GitTag tag = GitTag <$> obj <*> typ <*> name <*> owner <*> tagmsg where obj = string "object " *> sha1 <* endOfLine typ = string "type " *> line name = string "tag " *> line owner = string "tagger " *> line tagmsg = endOfLine *> AP.takeWhile (const True) <* endOfInput line = AP.takeWhile (not.isEndOfLine) <* endOfLine ---------------------------------------------------------------- binarySha1 :: Parser SHA1 binarySha1 = SHA1 . (flip toASCII "") <$> AP.take 20 where toASCII = BS.foldr (\w ss -> toHex w . ss) id toHex w = let (u,d) = w `divMod` 16 hex2 = toH u : toH d : [] in (hex2 ++) toH w | w >= 10 = chr $ baseA + (w' - 10) | otherwise = chr $ base0 + w' where w' = fromIntegral w baseA = ord 'a' base0 = ord '0' sha1 :: Parser SHA1 sha1 = SHA1 . BS.unpack <$> AP.take 40 octal :: Parser Int octal = BS.foldl' step 0 <$> AP.takeWhile isDig where isDig w = 48 <= w && w <= 55 step a w = a * 8 + fromIntegral (w - 48) ---------------------------------------------------------------- {- oneOf :: String -> Parser Char oneOf = satisfy . inClass -} noneOf :: String -> Parser Char noneOf = satisfy . notInClass spc :: Parser () spc = () <$ char ' ' nul :: Parser () nul = () <$ char '\0'