{-# LANGUAGE ScopedTypeVariables #-}
module Data.MockIO.FileSystem (
FileSystem(..)
, File(..)
, emptyFileSystem
, fileExists
, hasFile
, deleteFile
, getLines
, writeLines
, appendLines
, readLine
) where
import Data.Maybe
import Data.List
import Test.QuickCheck
( Arbitrary(..), Positive(..), Gen, vectorOf )
data File a = File
{ _fileHandle :: a
, _fileContents :: [String]
} deriving Eq
instance (Show a) => Show (File a) where
show (File h lns) = unlines $
[ ">>>>> " ++ show h ++ ":" ] ++ lns ++ ["<<<<<"]
data FileSystem a = FileSystem [File a]
instance (Eq a) => Eq (FileSystem a) where
(FileSystem as) == (FileSystem bs) = and
[ all (`elem` bs) as
, all (`elem` as) bs
]
instance (Show a) => Show (FileSystem a) where
show (FileSystem fs) = concatMap show fs
instance (Eq a, Arbitrary a) => Arbitrary (FileSystem a) where
arbitrary = do
Positive n <- arbitrary :: Gen (Positive Int)
handles <- fmap nub $ vectorOf (n `mod` 20) arbitrary
FileSystem <$> mapM (\k -> File k <$> arbitrary ) handles
emptyFileSystem :: FileSystem a
emptyFileSystem = FileSystem []
getFile :: (Eq a) => a -> FileSystem a -> Maybe (File a)
getFile h (FileSystem fs) = lookup fs
where
lookup zs = case zs of
[] -> Nothing
f:rest -> if h == _fileHandle f
then Just f
else lookup rest
putFile :: (Eq a) => File a -> FileSystem a -> FileSystem a
putFile f (FileSystem fs) = FileSystem $ putFile' fs
where
putFile' zs = case zs of
[] -> [f]
(g:rest) -> if _fileHandle f == _fileHandle g
then f : rest
else g : putFile' rest
fileExists
:: (Eq a)
=> a
-> FileSystem a
-> Bool
fileExists h = isJust . getFile h
hasFile
:: (Eq a)
=> a
-> [String]
-> FileSystem a
-> Bool
hasFile h lns fs = case getLines h fs of
Nothing -> False
Just ms -> ms == lns
getLines
:: (Eq a)
=> a
-> FileSystem a
-> Maybe [String]
getLines h = fmap _fileContents . getFile h
writeLines
:: (Eq a)
=> a
-> [String]
-> FileSystem a
-> FileSystem a
writeLines a lns = putFile (File a lns)
appendLines
:: (Eq a)
=> a
-> [String]
-> FileSystem a
-> FileSystem a
appendLines h ls (FileSystem fs) = FileSystem $ appendLines' fs
where
appendLines' zs = case zs of
[] -> [File h ls]
(File u ms):rest -> if u == h
then (File u (ms ++ ls)) : rest
else (File u ms) : appendLines' rest
deleteFile
:: (Eq a)
=> a
-> FileSystem a
-> FileSystem a
deleteFile h (FileSystem fs) = FileSystem $ deleteFile' fs
where
deleteFile' zs = case zs of
[] -> []
m:rest -> if h == _fileHandle m
then rest
else m : deleteFile' rest
readLine
:: (Eq a)
=> e
-> e
-> a
-> FileSystem a
-> Either e (String, FileSystem a)
readLine notFound eof k (FileSystem fs) = getline fs []
where
getline xs ys = case xs of
[] -> Left notFound
(File u x):rest -> if k == u
then case x of
[] -> Left eof
w:ws -> Right (w, FileSystem $ [File k ws] ++ rest ++ ys)
else getline rest ((File u x):ys)