1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
module Main (main) where
import Control.Monad
import qualified Data.ByteString.Char8 as BSC
import Data.Function
import Data.List
import qualified Data.Map as Map
import Data.Map (Map)
import System.Environment
import System.FilePath
data CleanWhat = CleanFile FilePath
| CleanRec FilePath
deriving (Read, Show)
newtype FilePathFragment = FilePathFragment BSC.ByteString
deriving (Show, Eq, Ord)
toFilePathFragments :: FilePath -> [FilePathFragment]
toFilePathFragments
= map (FilePathFragment . BSC.pack) . splitDirectories . normalise
fromFilePathFragments :: [FilePathFragment] -> FilePath
fromFilePathFragments xs = joinPath $ map f $ reverse xs
where f (FilePathFragment frag) = BSC.unpack frag
data Tree = Node !FileInfo !(Map FilePathFragment Tree)
data FileInfo = FileInfo {
fiBefore :: !Bool,
fiAfter :: !Bool,
fiDeleted :: !Bool
}
beforeFileInfo :: FileInfo
beforeFileInfo = noFileInfo { fiBefore = True }
afterFileInfo :: FileInfo
afterFileInfo = noFileInfo { fiAfter = True }
noFileInfo :: FileInfo
noFileInfo = FileInfo {
fiBefore = False,
fiAfter = False,
fiDeleted = False
}
readTree :: FileInfo -> FilePath -> IO (Tree)
readTree fi fp = do xs <- readFile fp
return $ mkTree fi $ lines xs
mkTree :: FileInfo -> [FilePath] -> Tree
mkTree fi fps = f (sort fragss)
where fragss = map toFilePathFragments fps
f xs = let xs' = g $ groupBy ((==) `on` head)
$ filter (not . null) xs
in Node fi xs'
g xss = mapFromList' [ (head (head xs), f (map tail xs))
| xs <- xss ]
mapFromList' :: Ord a => [(a, b)] -> Map a b
mapFromList' xs = seqAll xs `seq` Map.fromList xs
where seqAll [] = ()
seqAll ((x, y) : xys) = x `seq` y `seq` seqAll xys
{-
... = OK: will happen if a file in a non-existent directory is rm'd [1]
..D = OK: will happen if a non-existent file is rm'd [1]
.A. = suspicious: Why wasn't this file cleaned?
.AD = OK: This is what object files look like
B.. = suspicious: Where did the file go?
B.D = suspicious: Why are we removing a file that existed before?
BA. = OK: This is what source files look like
BAD = suspicious: Why are we removing a file that existed before?
[1] some files may only be created on certain platforms, or in certain
build-system configurations, but the cleaning code is deliberately
simple so it will always clean them regardless
-}
pprSuspicious :: Tree -> [String]
pprSuspicious t = f [] t
where f ps (Node fi m) = suspicious (fromFilePathFragments ps) fi
++ concat [ f (p : ps) m' | (p, m') <- Map.toList m ]
suspicious fp (FileInfo False True False) = ["File not deleted: " ++ show fp]
suspicious fp (FileInfo True False False) = ["File disappeared: " ++ show fp]
suspicious fp (FileInfo True False True) = ["Deleted before file: " ++ show fp]
suspicious fp (FileInfo True True True) = ["Deleted before file: " ++ show fp]
suspicious _ _ = []
pprTree :: Tree -> [String]
pprTree t = f [] t
where f ps (Node fi m) = (pprInfo fi ++ " " ++ fromFilePathFragments ps)
: concat [ f (p : ps) m' | (p, m') <- Map.toList m ]
pprInfo :: FileInfo -> String
pprInfo (FileInfo before after deleted) = [if before then 'B' else '.',
if after then 'A' else '.',
if deleted then 'D' else '.']
mergeTree :: Tree -> Tree -> Tree
mergeTree (Node fi1 m1) (Node fi2 m2)
= Node (mergeFileInfo fi1 fi2)
(Map.unionWith mergeTree m1 m2)
mergeFileInfo :: FileInfo -> FileInfo -> FileInfo
mergeFileInfo (FileInfo before1 after1 deleted1)
(FileInfo before2 after2 deleted2)
= FileInfo (before1 || before2) (after1 || after2) (deleted1 || deleted2)
main :: IO ()
main = do args <- getArgs
case args of
[contentsBeforeFile, contentsAfterFile, wouldBeCleanedFile] ->
doit contentsBeforeFile contentsAfterFile wouldBeCleanedFile
_ ->
error "Bad args"
doit :: FilePath -> FilePath -> FilePath -> IO ()
doit contentsBeforeFile contentsAfterFile wouldBeCleanedFile
= do contentsBefore <- readTree beforeFileInfo contentsBeforeFile
contentsAfter <- readTree afterFileInfo contentsAfterFile
let contentsMerged = mergeTree contentsBefore contentsAfter
wouldBeCleaned <- liftM (map read . lines) $ readFile wouldBeCleanedFile
let contentsCleaned = simulateCleans contentsMerged wouldBeCleaned
mapM_ putStrLn $ pprSuspicious contentsCleaned
simulateCleans :: Tree -> [CleanWhat] -> Tree
simulateCleans = foldl' simulateClean
simulateClean :: Tree -> CleanWhat -> Tree
simulateClean t (CleanFile fp) = at t fp markDeleted
simulateClean t (CleanRec fp) = at t fp markSubtreeDeleted
markDeleted :: Tree -> Tree
markDeleted (Node fi m) = Node (fi { fiDeleted = True }) m
markSubtreeDeleted :: Tree -> Tree
markSubtreeDeleted (Node fi m) = Node fi' (Map.map markSubtreeDeleted m)
where fi' = -- "rm -r" will only delete things that are there afterwards
if fiAfter fi then fi { fiDeleted = True } else fi
at :: Tree -> FilePath -> (Tree -> Tree) -> Tree
at t fp f = at' t (toFilePathFragments fp) f
at' :: Tree -> [FilePathFragment] -> (Tree -> Tree) -> Tree
at' t [] f = f t
at' (Node fi m) (p : ps) f = Node fi m'
where m' = Map.insert p (at' t ps f) m
t = Map.findWithDefault (Node noFileInfo Map.empty) p m
|