svndump 0.1.1 → 0.2.0
raw patch · 4 files changed
+90/−81 lines, 4 filesdep +attoparsecdep +containersdep −parsec
Dependencies added: attoparsec, containers
Dependencies removed: parsec
Files
- src/Subversion/Dump.hs +79/−71
- svndump.cabal +4/−2
- test/test-cooked.hs +2/−3
- test/test-raw.hs +5/−5
src/Subversion/Dump.hs view
@@ -13,23 +13,29 @@ , readSvnDump , readSvnDumpRaw++ , parseHeader+ , parseEntry ) where -import Control.Applicative hiding (many, (<|>))+import Control.Applicative hiding (many) import Control.Monad-import qualified Data.ByteString.Lazy as B---import qualified Data.ByteString.Lazy.Char8 as BC+import qualified Data.Attoparsec.Char8 as AC+import Data.Attoparsec.Combinator+import Data.Attoparsec.Lazy as AL+import Data.ByteString as B hiding (map)+import qualified Data.ByteString.Char8 as BC hiding (map)+import qualified Data.ByteString.Lazy as BL hiding (map) import qualified Data.List as L+import Data.Text (Text)+import qualified Data.Text.Encoding as E import Data.Maybe-import Data.Text.Lazy hiding (map, count)-import Data.Text.Lazy.Encoding as E+import Data.Word (Word8) import System.FilePath-import Text.Parsec-import Text.Parsec.ByteString.Lazy as PB import Prelude hiding (getContents) -default (Data.Text.Lazy.Text)+default (ByteString) -- | A parser for Subversion dump files. The objective is to convert a dump -- file into a series of data structures representing that same information.@@ -69,10 +75,10 @@ data Operation = Operation { opKind :: OpKind , opAction :: OpAction , opPathname :: FilePath- , opContents :: B.ByteString+ , opContents :: ByteString , opContentLength :: Int- , opChecksumMD5 :: Maybe String- , opChecksumSHA1 :: Maybe String+ , opChecksumMD5 :: Maybe Text+ , opChecksumSHA1 :: Maybe Text , opCopyFromRev :: Maybe Int , opCopyFromPath :: Maybe FilePath } deriving Show@@ -93,10 +99,11 @@ -- | Reads a dump file from a ByteString in the IO monad into a list of -- Revision values. This is the "cooked" parallel of 'readSvnDumpRaw'. -readSvnDump :: B.ByteString -> IO (Either ParseError [Revision])+readSvnDump :: BL.ByteString -> Either String [Revision] readSvnDump io = do- result <- readSvnDumpRaw io- return $ map processRevs <$> (L.groupBy sameRev <$> result)+ case readSvnDumpRaw io of+ Fail _ _ y -> Left y+ Done _ result -> Right $ map processRevs (L.groupBy sameRev result) where sameRev _ y = isNothing $ L.lookup "Revision-number" (entryTags y)@@ -110,24 +117,24 @@ processRevs [] = error "Unexpected" processRevs (rev:ops) = Revision {- revNumber = read $ tag "Revision-number" rev+ revNumber = readInt $ tag "Revision-number" rev , revDate = parseDate $ prop "svn:date" rev- , revAuthor = propM "svn:author" rev- , revComment = propM "svn:log" rev+ , revAuthor = E.decodeUtf8 <$> propM "svn:author" rev+ , revComment = E.decodeUtf8 <$> propM "svn:log" rev , revOperations = map processOp ops } processOp op = Operation { opKind = getOpKind $ tag "Node-kind" op , opAction = getOpAction $ tag "Node-action" op- , opPathname = tag "Node-path" op+ , opPathname = BC.unpack $ tag "Node-path" op , opContents = entryBody op- , opContentLength = read $ tag "Text-content-length" op- , opCopyFromRev = read <$>+ , opContentLength = readInt $ tag "Text-content-length" op+ , opCopyFromRev = readInt <$> tagM "Node-copyfrom-rev" op- , opCopyFromPath = tagM "Node-copyfrom-path" op- , opChecksumMD5 = tagM "Text-content-md5" op- , opChecksumSHA1 = tagM "Text-content-sha1" op }+ , opCopyFromPath = BC.unpack <$> tagM "Node-copyfrom-path" op+ , opChecksumMD5 = E.decodeUtf8 <$> tagM "Text-content-md5" op+ , opChecksumSHA1 = E.decodeUtf8 <$> tagM "Text-content-sha1" op } getOpKind kind = case kind of "file" -> File@@ -141,83 +148,84 @@ "replace" -> Replace _ -> error "Unexpected" -type FieldMap a = [(String, a)]+type FieldMap = [(ByteString, ByteString)] -data Entry = Entry { entryTags :: FieldMap String- , entryProps :: FieldMap Text- , entryBody :: B.ByteString }- deriving Show+data Entry = Entry { entryTags :: FieldMap+ , entryProps :: FieldMap+ , entryBody :: ByteString }+ deriving Show -readSvnDumpRaw :: B.ByteString -> IO (Either ParseError [Entry])-readSvnDumpRaw dump = return $ parse parseSvnDump "" dump+readSvnDumpRaw :: BL.ByteString -> Result [Entry]+readSvnDumpRaw dump = parse parseSvnDump dump -- These are the Parsec parsers for the various parts of the input file. -parseTag :: PB.Parser (String, String)-parseTag = (,) <$> fieldKey <* char ':' <* space- <*> fieldValue <* newline- where- fieldKey = (:) <$> letter <*> many fieldChar- fieldChar = letter <|> digit <|> oneOf "-_"- fieldValue = many1 (noneOf "\n")+space :: Parser Word8+space = satisfy (== 32) -parseIndicator :: PB.Parser (Char, Integer)-parseIndicator = (,) <$> oneOf "KV" <* space- <*> (read <$> many1 digit <* newline)+newline :: Parser Word8+newline = satisfy (== 10) -readTextRange :: Integer -> PB.Parser B.ByteString-readTextRange len = do- input <- getInput- let value = B.take (fromIntegral len) input- setInput $ B.drop (fromIntegral len) input- return value+parseTag :: Parser (ByteString, ByteString)+parseTag =+ (,) <$> takeWhile1 fieldChar <* string ": " -- :+ <*> takeWhile1 (/= 10) <* newline+ where fieldChar w = (w >= 65 && w <= 90) -- A-Z+ || (w >= 97 && w <= 121) -- a-z+ || (w >= 48 && w <= 57) -- 0-9+ || w == 45 -- -+ || w == 95 -- _ ---readTextRange' :: Integer -> PB.Parser B.ByteString---readTextRange' len = BC.pack <$> count (fromIntegral len) anyChar+parseIndicator :: Parser (Word8, Int)+parseIndicator = (,) <$> satisfy (oneOf 75 86) <* space -- K or V+ <*> AC.decimal <* newline+ where oneOf x y w = w == x || w == y -parseSpecValue :: Char -> PB.Parser Text-parseSpecValue expected = do- (kind, len) <- parseIndicator- when (kind /= expected) $ unexpected "Unexpected spec value char"- value <- readTextRange len- --trace ("Value: " ++ (show value)) $ return ()- _ <- newline- return $ E.decodeUtf8 value+parseSpecValue :: Parser ByteString+parseSpecValue = do+ (_, len) <- parseIndicator+ AL.take len <* newline -parseProperty :: PB.Parser (String, Text)-parseProperty = (,) <$> (unpack <$> parseSpecValue 'K')- <*> parseSpecValue 'V'+parseProperty :: Parser (ByteString, ByteString)+parseProperty = (,) <$> parseSpecValue -- K+ <*> parseSpecValue -- V -parseEntry :: PB.Parser Entry+readInt :: ByteString -> Int+readInt bs = B.foldl' addup 0 bs+ where addup acc x = acc * 10 + (fromIntegral x - 48) -- '0'++parseEntry :: Parser Entry parseEntry = do fields <- many1 parseTag <* newline props <- case L.lookup "Prop-content-length" fields of Nothing -> return []- Just _ -> many parseProperty <* string "PROPS-END\n"+ Just _ -> manyTill parseProperty (try (string "PROPS-END\n")) body <- case L.lookup "Text-content-length" fields of Nothing -> return B.empty- Just len -> readTextRange (read len)+ Just len -> AL.take (readInt len) - _ <- many newline <?> "entry-terminating newline"+ _ <- AL.takeWhile (== 10) return Entry { entryTags = fields , entryProps = props , entryBody = body } -parseHeader :: PB.Parser ()+parseHeader :: Parser ByteString parseHeader = do _ <- string "SVN-fs-dump-format-version: 2\n\n" <?> "Dump file starts without a recognizable tag"- _ <- string "UUID: " <* many1 (hexDigit <|> char '-')- <* newline <* newline- return ()+ string "UUID: " *> takeWhile1 uuidMember+ <* newline <* newline+ where+ -- Accept any hexadecimal character, or '-'+ uuidMember w = w == 45 || (w >= 48 && w <= 57) || (w >= 97 && w <= 102) -parseSvnDump :: PB.Parser [Entry]-parseSvnDump = parseHeader >> many parseEntry+parseSvnDump :: Parser [Entry]+parseSvnDump = parseHeader >> many1 parseEntry -parseDate :: Text -> RevDate-parseDate = id+parseDate :: ByteString -> RevDate+parseDate = E.decodeUtf8 -- SvnDump.hs ends here
svndump.cabal view
@@ -1,6 +1,6 @@ name: svndump category: Subversion-version: 0.1.1+version: 0.2.0 license: BSD3 cabal-version: >= 1.8 license-file: LICENSE@@ -31,9 +31,10 @@ library build-depends: base >= 4.3 && < 5,- parsec >= 3.1.3,+ attoparsec >= 0.10.2, filepath >= 1.3, bytestring >= 0.9 && < 0.10,+ containers >= 0.4.2, text >= 0.11 && < 0.12 exposed-modules:@@ -50,6 +51,7 @@ build-depends: base >= 4.3 && < 5,+ attoparsec >= 0.10.2, bytestring >= 0.9 && < 0.10, zlib >= 0.5 && < 0.6, svndump
test/test-cooked.hs view
@@ -8,8 +8,7 @@ main :: IO () main = do file <- B.readFile "data/cunit.dump.gz"- dump <- readSvnDump $ GZip.decompress file- case dump of+ case readSvnDump (GZip.decompress file) of Left _ -> exitFailure Right xs -> do let len = length xs@@ -18,4 +17,4 @@ then exitSuccess else exitFailure --- raw-parser.hs ends here+-- test-cooked.hs ends here
test/test-raw.hs view
@@ -1,6 +1,7 @@ module Main where import qualified Codec.Compression.GZip as GZip+import Data.Attoparsec.Lazy import qualified Data.ByteString.Lazy as B import Subversion.Dump import System.Exit@@ -8,14 +9,13 @@ main :: IO () main = do file <- B.readFile "data/cunit.dump.gz"- dump <- readSvnDumpRaw $ GZip.decompress file- case dump of- Left _ -> exitFailure- Right xs -> do+ case readSvnDumpRaw (GZip.decompress file) of+ Fail _ _ _ -> exitFailure+ Done _ xs -> do let len = length xs putStrLn $ show len ++ " raw entries found, expecting 1950" if len == 1950 then exitSuccess else exitFailure --- raw-parser.hs ends here+-- test-raw.hs ends here