packages feed

Diff 0.3.2 → 0.3.3

raw patch · 3 files changed

+264/−12 lines, 3 filesdep +QuickCheckdep +directorydep +processdep ~basedep ~prettyPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck, directory, process, test-framework, test-framework-quickcheck2

Dependency ranges changed: base, pretty

API changes (from Hackage documentation)

- Data.Algorithm.Diff: instance Eq DI
- Data.Algorithm.Diff: instance Eq DL
- Data.Algorithm.Diff: instance Eq a => Eq (Diff a)
- Data.Algorithm.Diff: instance Ord DL
- Data.Algorithm.Diff: instance Show DI
- Data.Algorithm.Diff: instance Show DL
- Data.Algorithm.Diff: instance Show a => Show (Diff a)
- Data.Algorithm.DiffOutput: instance Show LineRange
- Data.Algorithm.DiffOutput: instance Show a => Show (DiffOperation a)
- Data.Algorithm.DiffOutput: lrContents :: LineRange -> [String]
- Data.Algorithm.DiffOutput: lrNumbers :: LineRange -> (LineNo, LineNo)
+ Data.Algorithm.Diff: instance GHC.Classes.Eq Data.Algorithm.Diff.DI
+ Data.Algorithm.Diff: instance GHC.Classes.Eq Data.Algorithm.Diff.DL
+ Data.Algorithm.Diff: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Algorithm.Diff.Diff a)
+ Data.Algorithm.Diff: instance GHC.Classes.Ord Data.Algorithm.Diff.DL
+ Data.Algorithm.Diff: instance GHC.Show.Show Data.Algorithm.Diff.DI
+ Data.Algorithm.Diff: instance GHC.Show.Show Data.Algorithm.Diff.DL
+ Data.Algorithm.Diff: instance GHC.Show.Show a => GHC.Show.Show (Data.Algorithm.Diff.Diff a)
+ Data.Algorithm.DiffOutput: [lrContents] :: LineRange -> [String]
+ Data.Algorithm.DiffOutput: [lrNumbers] :: LineRange -> (LineNo, LineNo)
+ Data.Algorithm.DiffOutput: diffToLineRanges :: [Diff [String]] -> [DiffOperation LineRange]
+ Data.Algorithm.DiffOutput: instance GHC.Classes.Eq Data.Algorithm.DiffOutput.LineRange
+ Data.Algorithm.DiffOutput: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Algorithm.DiffOutput.DiffOperation a)
+ Data.Algorithm.DiffOutput: instance GHC.Classes.Ord Data.Algorithm.DiffOutput.LineRange
+ Data.Algorithm.DiffOutput: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Algorithm.DiffOutput.DiffOperation a)
+ Data.Algorithm.DiffOutput: instance GHC.Read.Read Data.Algorithm.DiffOutput.LineRange
+ Data.Algorithm.DiffOutput: instance GHC.Read.Read a => GHC.Read.Read (Data.Algorithm.DiffOutput.DiffOperation a)
+ Data.Algorithm.DiffOutput: instance GHC.Show.Show Data.Algorithm.DiffOutput.LineRange
+ Data.Algorithm.DiffOutput: instance GHC.Show.Show a => GHC.Show.Show (Data.Algorithm.DiffOutput.DiffOperation a)
+ Data.Algorithm.DiffOutput: parsePrettyDiffs :: String -> [DiffOperation LineRange]
- Data.Algorithm.Diff: getDiff :: Eq t => [t] -> [t] -> [Diff t]
+ Data.Algorithm.Diff: getDiff :: (Eq t) => [t] -> [t] -> [Diff t]
- Data.Algorithm.Diff: getGroupedDiff :: Eq t => [t] -> [t] -> [Diff [t]]
+ Data.Algorithm.Diff: getGroupedDiff :: (Eq t) => [t] -> [t] -> [Diff [t]]

Files

Diff.cabal view
@@ -1,5 +1,5 @@ name:                Diff-version:             0.3.2+version:             0.3.3 synopsis:            O(ND) diff algorithm in haskell. description:         Implementation of the standard diff algorithm, and utilities for pretty printing. category:            Algorithms@@ -9,10 +9,10 @@ maintainer:          s.clover@gmail.com Tested-With:         GHC == 7.8.4 Build-Type:          Simple-Cabal-Version:       >= 1.6+Cabal-Version:       >= 1.8  library-  build-depends:   base >= 3 && <= 6, array, pretty+  build-depends:   base >= 3 && <= 6, array, pretty >= 1.1   hs-source-dirs:  src   exposed-modules:                    Data.Algorithm.Diff,@@ -23,3 +23,12 @@ source-repository head   type:      darcs   location:  http://hub.darcs.net/sterlingclover/Diff++test-suite diff-tests+  type: exitcode-stdio-1.0+  hs-source-dirs: test, src+  main-is: Test.hs+  build-depends: base >= 3 && <= 6, array+                , pretty, QuickCheck, test-framework+                , test-framework-quickcheck2, process+                , directory
src/Data/Algorithm/DiffOutput.hs view
@@ -13,14 +13,12 @@ module Data.Algorithm.DiffOutput where import Data.Algorithm.Diff import Text.PrettyPrint-import Data.Monoid (mappend)+import Data.Char+import Data.List --- | pretty print the differences. The output is similar to the output of the diff utility-ppDiff :: [Diff [String]] -> String-ppDiff gdiff =-   let  diffLineRanges = toLineRange 1 1 gdiff-   in-        render (prettyDiffs diffLineRanges) ++ "\n"+-- | Converts Diffs to DiffOperations+diffToLineRanges :: [Diff [String]] -> [DiffOperation LineRange]+diffToLineRanges = toLineRange 1 1    where           toLineRange :: Int -> Int -> [Diff [String]] -> [DiffOperation LineRange]           toLineRange _ _ []=[]@@ -45,6 +43,14 @@                 in  Change (LineRange (leftLine,leftLine+linesF-1) lsF) (LineRange (rightLine,rightLine+linesS-1) lsS)                         : toLineRange (leftLine+linesF) (rightLine+linesS) rs +-- | pretty print the differences. The output is similar to the output of the diff utility+ppDiff :: [Diff [String]] -> String+ppDiff gdiff =+   let  diffLineRanges = diffToLineRanges gdiff+   in+        render (prettyDiffs diffLineRanges) ++ "\n"++ -- | pretty print of diff operations prettyDiffs :: [DiffOperation LineRange] -> Doc prettyDiffs [] = empty@@ -66,14 +72,59 @@       prettyLines start lins =           vcat (map (\l -> char start <+> text l) lins) +-- | Parse pretty printed Diffs as DiffOperations+parsePrettyDiffs :: String -> [DiffOperation LineRange]+parsePrettyDiffs = reverse . doParse [] . lines+  where+    doParse diffs [] = diffs+    doParse diffs s =+        let (mnd,r) = parseDiff s+        in case mnd of+            Just nd -> doParse (nd:diffs) r+            _          -> doParse diffs r+    parseDiff [] = (Nothing,[])+    parseDiff (h:rs) = let+        (r1,hrs1) = parseRange h+        in case hrs1 of+                ('d':hrs2) -> parseDel r1 hrs2 rs+                ('a':hrs2) -> parseAdd r1 hrs2 rs+                ('c':hrs2) -> parseChange r1 hrs2 rs+                _ -> (Nothing,rs)+    parseDel r1 hrs2 rs = let+        (r2,_) = parseRange hrs2+        (ls,rs2) = span (isPrefixOf "<") rs+        in (Just $ Deletion (LineRange r1 (map (drop 2) ls)) (fst r2), rs2)+    parseAdd r1 hrs2 rs = let+        (r2,_) = parseRange hrs2+        (ls,rs2) = span (isPrefixOf ">") rs+        in (Just $ Addition (LineRange r2 (map (drop 2) ls)) (fst r1), rs2)+    parseChange r1 hrs2 rs = let+        (r2,_) = parseRange hrs2+        (ls1,rs2) = span (isPrefixOf "<") rs+        in case rs2 of+            ("---":rs3) -> let+                (ls2,rs4) = span (isPrefixOf ">") rs3+                in (Just $ Change (LineRange r1 (map (drop 2) ls1)) (LineRange r2 (map (drop 2) ls2)), rs4)+            _ -> (Nothing,rs2)+    parseRange :: String -> ((LineNo, LineNo),String)+    parseRange l = let+        (fstLine,rs) = span isDigit l+        (sndLine,rs3) = case rs of+                                    (',':rs2) -> span isDigit rs2+                                    _ -> (fstLine,rs)+        in ((read fstLine,read sndLine),rs3)++-- | Line number alias type LineNo = Int +-- | Line Range: start, end and contents data LineRange = LineRange { lrNumbers :: (LineNo, LineNo)                            , lrContents :: [String]                            }-            deriving (Show)+            deriving (Show,Read,Eq,Ord) +-- | Diff Operation  representing changes to apply data DiffOperation a = Deletion a LineNo             | Addition a LineNo             | Change a a-            deriving (Show)+            deriving (Show,Read,Eq,Ord)
+ test/Test.hs view
@@ -0,0 +1,192 @@+module Main where++import Test.Framework (defaultMain, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.QuickCheck+import Data.Algorithm.Diff+import Data.Algorithm.DiffOutput+import Text.PrettyPrint++import System.IO+import System.Exit+import System.IO.Unsafe (unsafePerformIO)+import Debug.Trace (trace)+import System.Environment (getArgs)+import Data.Maybe (mapMaybe, catMaybes)+import System.Process (readProcessWithExitCode)+import System.Directory (getTemporaryDirectory)+++main :: IO ()+main = defaultMain [ testGroup "sub props" [+                        slTest "empty in subs" prop_emptyInSubs,+                        slTest "self in subs"  prop_selfInSubs,+                        slTest "count subs"    prop_countSubs,+                        slTest "every sub is a sub" prop_everySubIsSub,+                        slTest2 "sub prop" prop_sub+                     ],+                     testGroup "diff props" [+                        slTest "lcsEmpty" prop_lcsEmpty,+                        slTest "lcsSelf" prop_lcsSelf,+                        slTest2 "lcsBoth" prop_lcsBoth,+                        slTest2 "recover first" prop_recoverFirst,+                        slTest2 "recover second" prop_recoverSecond,+                        slTest2 "lcs" prop_lcs+                     ],+                     testGroup "output props" [+                        testProperty "self generates empty" $ forAll shortLists  prop_ppDiffEqual,+                        --testProperty "compare our lists with diff" $ forAll2 shortLists  prop_ppDiffShort,+                        testProperty "compare random with diff" prop_ppDiffR,+                        testProperty "test parse" prop_parse+                     ]+                   ]++slTest s t = testProperty s $ forAll shortLists   (t :: [Bool] -> Bool)+slTest2 s t = testProperty s $ forAll2 shortLists (t :: [Bool] -> [Bool] -> Bool)++-- We need some quick and dirty subsequence stuff for the diff tests,+-- so we build that and some tests for it.++-- | Determines whether one list is a subsequence of another.+isSub :: (Eq a) => [a] -> [a] -> Bool+isSub [] _ = True+isSub (_:_) [] = False+isSub (x:xs) (y:ys) | x == y = isSub xs ys+                    | otherwise = isSub (x:xs) ys++-- | Lists the subsequences of a list.+subs :: [a] -> [[a]]+subs [] = [[]]+subs (x:rest) = map (x:) restss ++ restss+  where restss = subs rest++prop_emptyInSubs = elem [] . subs+prop_selfInSubs xs = elem xs (subs xs)+prop_countSubs xs = length (subs xs) == 2^(length xs)+prop_sub xs ys = isSub xs ys == elem xs (subs ys)+prop_everySubIsSub xs = all (flip isSub xs) (subs xs)+++-- | Obtains a longest common subsequence of two lists using their+-- diff. Note that there is an @lcs@ function in the+-- 'Data.Algorithm.Diff' module, but it's not exported. It's trivial+-- to reconstruct the LCS though, just by taking the 'B' elements.+diffLCS :: (Eq a) => [a] -> [a] -> [a]+diffLCS xs ys = recoverLCS $ getDiff xs ys++-- | Recovers the (longest) common subsequence from a diff.+recoverLCS :: [Diff a] -> [a]+recoverLCS (Both x _ : xs) = x : recoverLCS xs+recoverLCS (_ : xs) = recoverLCS xs+recoverLCS [] = []++-- | Recovers the first list from a diff.+recoverFirst :: [Diff a] -> [a]+recoverFirst (First x  : xs) = x : recoverFirst xs+recoverFirst (Both x _ : xs) = x : recoverFirst xs+recoverFirst (_ : xs) = recoverFirst xs+recoverFirst [] = []++-- | Recovers the second list from a diff.+recoverSecond :: [Diff a] -> [a]+recoverSecond (Second x  : xs) = x : recoverSecond xs+recoverSecond (Both x _ : xs) = x : recoverSecond xs+recoverSecond (_ : xs) = recoverSecond xs+recoverSecond [] = []++-- | Indicates whether a list is a longest common subsequence of two+-- lists.+isLCS :: (Eq a) => [a] -> [a] -> [a] -> Bool+isLCS ss xs ys = isSub ss ys && isSub ss ys && length ss == lenLCS xs ys++-- | Computes the length of the longest common subsequence of two+-- lists. This is a naive and inefficient recursive implementation+-- that doesn't memoize repeated sub-calls, so don't use it with large+-- lists.+lenLCS :: (Eq a) => [a] -> [a] -> Int+lenLCS [] _ = 0+lenLCS _ [] = 0+lenLCS (x:xs) (y:ys) | x == y = 1 + lenLCS xs ys+                     | otherwise = max (lenLCS (x:xs) ys) (lenLCS xs (y:ys))+++prop_recoverFirst xs ys = recoverFirst (getDiff xs ys) == xs+prop_recoverSecond xs ys = recoverSecond (getDiff xs ys) == ys+prop_lcs xs ys = isLCS (diffLCS xs ys) xs ys+prop_lcsEmpty xs = diffLCS xs [] == [] && diffLCS [] xs == []+prop_lcsSelf xs = diffLCS xs xs == xs+prop_lcsBoth xs ys = all areMatch $ getDiff xs ys+    where areMatch (Both x y) = x == y+          areMatch _ = True++-- | Lists of no more than twelve elements.+shortLists :: (Arbitrary a) => Gen [a]+shortLists = sized $ \n -> resize (min n 12) $ listOf arbitrary++-- | 'forAll' where the generator is used twice.+forAll2 :: (Show a, Testable prop) => Gen a -> (a -> a -> prop) -> Property+forAll2 gen f = forAll gen $ \x -> forAll gen (f x)++prop_ppDiffEqual xs=ppDiff (getGroupedDiff xs xs)=="\n"++-- | truly random tests+prop_ppDiffR :: DiffInput -> Property+prop_ppDiffR (DiffInput le ri) =+    let haskDiff=ppDiff $ getGroupedDiff le ri+        utilDiff= unsafePerformIO (runDiff (unlines le) (unlines ri))+    in  cover (haskDiff == utilDiff) 90 "exact match" $+                classify (haskDiff == utilDiff) "exact match"+                        (div ((length haskDiff)*100) (length utilDiff) < 110) -- less than 10% bigger+    where+      runDiff left right =+          do leftFile <- writeTemp left+             rightFile <- writeTemp right+             (ecode, out, err) <-+                 readProcessWithExitCode "diff" [leftFile, rightFile] ""+             -- putStrLn ("OUT:\n" ++ out)+             -- putStrLn ("ERR:\n" ++ err)+             -- putStrLn ("ECODE:\n" ++ show ecode)+             case ecode of+               ExitSuccess -> return out+               ExitFailure 1 -> return out+               ExitFailure i -> error ("'diff " ++ leftFile ++ " " ++ rightFile +++                                       "' failed with exit code " ++ show i +++                                       ": " ++ show err)+      writeTemp s =+          do dir <- getTemporaryDirectory+             (fp, h) <- openTempFile dir "HTF-diff.txt"+             hPutStr h s+             hClose h+             return fp++-- | Check pretty printed DiffOperations can be parsed again+prop_parse :: DiffInput -> Bool+prop_parse (DiffInput le ri) =+    let difflrs = diffToLineRanges $ getGroupedDiff le ri+        output = render (prettyDiffs difflrs) ++ "\n"+        parsed = parsePrettyDiffs output+    in difflrs == parsed++data DiffInput = DiffInput { diLeft :: [String], diRight :: [String] }+               deriving (Show)++leftDiffInput = ["1", "2", "3", "4", "", "5", "6", "7"]++instance Arbitrary DiffInput where+    arbitrary =+        do let leftLines = leftDiffInput+           rightLinesLines <- mapM modifyLine (leftLines ++ [""])+           return $ DiffInput leftLines+                              (concat rightLinesLines)+      where+        randomString =+            do c <- elements ['a' .. 'z']+               return [c]+        modifyLine :: String -> Gen [String]+        modifyLine str =+            do prefixLen <- frequency [(20-i, return i) | i <- [0..5]]+               prefix <- mapM (const randomString) [1..prefixLen]+               frequency [ (5, return (prefix ++ [str]))+                         , (3, return (prefix ++ ["XXX" ++ str]))+                         , (2, return prefix)+                         , (2, return [str])]