packages feed

lambdiff-0.1: src/Lambdiff/DiffParse.hs

module Lambdiff.DiffParse where

import Data.ByteString.Char8 as S
import Control.Applicative ((<|>))

import qualified Data.Attoparsec as Atto
import qualified Data.Attoparsec.Char8 as AttoC

data ParseFileResult = ParseFileResult S.ByteString S.ByteString [ParseChunk]
  deriving Show

data ParseChunk = ParseChunk Int Int [ParseLine]
  deriving Show

data ParseLine = ParseLineNone S.ByteString
               | ParseLineAdd S.ByteString
               | ParseLineSub S.ByteString
               | ParseLineComment S.ByteString
  deriving Show

diffParser :: Atto.Parser [ParseFileResult]
diffParser = Atto.many parseDiff

parseDiff :: Atto.Parser ParseFileResult
parseDiff = do
    (f1, f2) <- consumeHeader
    chunks <- Atto.many parseChunk
    return $ ParseFileResult f1 f2 chunks

parseChunk :: Atto.Parser ParseChunk
parseChunk = do
    Atto.string "@@ -"
    lineNo1 <- AttoC.decimal
    _ <- Atto.many1 $ AttoC.notChar '+'
    _ <- AttoC.char '+'
    lineNo2 <- AttoC.decimal
    _ <- Atto.many1 $ AttoC.notChar '\n'
    _ <- AttoC.char '\n'
    lines <- Atto.many1 parseChunkLine
    return $ ParseChunk lineNo1 lineNo2 lines

parseChunkLine :: Atto.Parser ParseLine
parseChunkLine = do
    const <- noLead <|> addLead <|> subLead <|> comLead
    restOfLine <- AttoC.takeWhile (/='\n')
    AttoC.char '\n'
    return $ const $ S.concat [restOfLine, "\n"]

noLead = AttoC.char ' ' >> (return ParseLineNone)
addLead = AttoC.char '+' >> (return ParseLineAdd)
subLead = AttoC.char '-' >> (return ParseLineSub)
comLead = AttoC.char '\\' >> (return ParseLineComment)

-- | Handle both git-style unified diffs and
-- | diff -u style ones
consumeHeader :: Atto.Parser (S.ByteString, S.ByteString)
consumeHeader = do
    (Atto.string "diff " >> AttoC.scan "" findFileTop) <|> (Atto.string "---")
    _  <- AttoC.char ' '
    f1 <- AttoC.takeWhile1 (\c -> and [(c/='\n'), (c/='\t')])
    _  <- AttoC.takeWhile (/='\n')
    _  <- Atto.string "\n+++ "
    f2 <- AttoC.takeWhile1 (\c -> and [(c/='\n'), (c/='\t')])
    _  <- AttoC.takeWhile (/='\n')
    _  <- AttoC.char '\n'
    return (f1, f2)
  where
    findFileTop "---\n" ' '  = Nothing
    findFileTop _       '\n' = Just "\n"
    findFileTop s       '-'  = Just $ '-' : s
    findFileTop _        _   = Just ""