module Filediff
(
diffFiles
, diffDirectories
, applyToFile
, applyToDirectory
) where
import qualified System.IO as IO
import qualified System.Directory as D
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Data.Maybe (isJust, fromJust, catMaybes)
import Data.List ((\\), intersect)
import Data.Monoid
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Either
import Control.Monad.IO.Class (liftIO)
import Filediff.Types
import Filediff.Sequence (SeqDiff(..), diffSequences, applySequenceDiff)
import Filediff.Utils
( (</>)
, (<.>)
, getFileDirectory
, removeDotDirs
, createFileWithContents
, dropUntil
, removeFirstPathComponent
, getDirectoryContentsRecursiveSafe )
diffFiles :: FilePath -> FilePath -> IO Filediff
diffFiles a b = do
aIsDir <- D.doesDirectoryExist a
bIsDir <- D.doesDirectoryExist b
when (aIsDir || bIsDir) $ error $ "One or both of " ++ a ++ " and " ++ b ++ "is not a file, but a directory."
aExists <- D.doesFileExist a
bExists <- D.doesFileExist b
case (aExists, bExists) of
(False, False) -> return $ Filediff a b mempty
(False, True ) -> addCase
(True , False) -> delCase
(True , True ) -> modCase
where
addCase :: IO Filediff
addCase = do
let aLines = []
bLines <- T.lines <$> TIO.readFile b
return Filediff
{ base = a
, comp = b
, change = Add $ diffSequences aLines bLines }
delCase :: IO Filediff
delCase = do
aLines <- T.lines <$> TIO.readFile a
let bLines = []
return Filediff
{ base = a
, comp = b
, change = Del $ diffSequences aLines bLines }
modCase :: IO Filediff
modCase = do
aLines <- T.lines <$> TIO.readFile a
bLines <- T.lines <$> TIO.readFile b
return Filediff
{ base = a
, comp = b
, change = Mod $ diffSequences aLines bLines }
diffDirectories :: FilePath -> FilePath -> IO Diff
diffDirectories a b = do
aIsFile <- D.doesFileExist a
bIsFile <- D.doesFileExist b
when (aIsFile || bIsFile) $ error $ "One or both of " ++ a ++ " and " ++ b ++ "is not a directory, but a file."
aContents <- getDirectoryContentsRecursiveSafe a
bContents <- getDirectoryContentsRecursiveSafe b
intersectionDiffs <- getDiffs $ intersect aContents bContents
aOnlyDiffs <- getDiffs $ aContents \\ bContents
bOnlyDiffs <- getDiffs $ bContents \\ aContents
let allDiffs = map removeFirstPathComponentFromDiff $ intersectionDiffs ++ aOnlyDiffs ++ bOnlyDiffs
return $ Diff allDiffs
where
getDiffs :: [FilePath] -> IO [Filediff]
getDiffs filepaths
= filter (not . isIdentityFileDiff)
<$> mapM (\fp -> diffFiles (a </> fp) (b </> fp)) filepaths
isIdentityFileDiff :: Filediff -> Bool
isIdentityFileDiff = (==) mempty . change
removeFirstPathComponentFromDiff :: Filediff -> Filediff
removeFirstPathComponentFromDiff
(Filediff base comp change) =
Filediff
(removeFirstPathComponent base)
(removeFirstPathComponent comp)
change
applyToFile :: Filediff -> FilePath -> IO [Line]
applyToFile (Filediff _ _ change) filepath = do
case change of
Del _ -> delCase
Mod seqdiff -> modCase seqdiff
Add seqdiff -> addCase seqdiff
where
delCase :: IO [Line]
delCase = D.removeFile filepath >> return []
addCase :: SeqDiff Line -> IO [Line]
addCase seqdiff = createFileWithContents filepath "" >> modCase seqdiff
modCase :: SeqDiff Line -> IO [Line]
modCase seqdiff = do
file <- TIO.readFile filepath
let result = applySequenceDiff seqdiff . T.lines $ file
TIO.writeFile filepath (safeInit . T.unlines $ result)
return result
safeInit :: T.Text -> T.Text
safeInit x = if T.null x then x else T.init x
applyToDirectory :: Diff -> FilePath -> IO ()
applyToDirectory (Diff filediffs) filepath = mapM_ apply filediffs
where
apply :: Filediff -> IO [Line]
apply diff@(Filediff base compare _)
= applyToFile diff (filepath </> base)