diff options
| author | Jan Sucan <jan@jansucan.com> | 2023-11-14 13:05:45 +0100 |
|---|---|---|
| committer | Jan Sucan <jan@jansucan.com> | 2023-11-14 20:11:29 +0100 |
| commit | b69c0554aec47443fb126de32ccb75b5d8c47d3e (patch) | |
| tree | aec4777cbca9c934e2cedc83c19fef1fd381ae56 /ch09 | |
| parent | 44b2fffa2114d230d1306360e743c23e702d0c9b (diff) | |
9_d_1: Add solution
Diffstat (limited to 'ch09')
| -rw-r--r-- | ch09/9_d_1.hs | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/ch09/9_d_1.hs b/ch09/9_d_1.hs new file mode 100644 index 0000000..571ab87 --- /dev/null +++ b/ch09/9_d_1.hs @@ -0,0 +1,147 @@ +-- Although the file finding code we described in this chapter is a good vehicle +-- for learning, it's not ideal for real systems programming tasks, because +-- Haskell's portable I/O libraries don't expose enough information to let us +-- write interesting and complicated queries. +-- +-- Port the code from this chapter to your platform's native API, either +-- System.Posix or System.Win32. + + +{-- From examples/examples/ch09/FoldDir.hs modified according to the assignment + -- + -- Added missing import of takeBaseName. + -- Fixed appending correct root path to directory entries when constructing + -- their full paths. + --} +import Control.Exception +import Control.Monad (liftM) +import Data.Char (toLower) + +import System.FilePath ((</>), takeFileName, takeExtension, takeBaseName) +import System.Posix.Directory (DirStream, openDirStream, readDirStream, closeDirStream) +import System.Posix.Types (EpochTime, FileOffset, FileMode) +import System.Posix.Files (getFileStatus, fileSize, modificationTime, fileMode, isDirectory) + + +data Iterate seed = Done { unwrap :: seed } + | Skip { unwrap :: seed } + | Continue { unwrap :: seed } + deriving (Show) + +type Iterator seed = seed -> Info -> Iterate seed + + +foldTree :: Iterator a -> a -> FilePath -> IO a + +foldTree iter initSeed path = do + endSeed <- fold initSeed path + return (unwrap endSeed) + where + fold seed subpath = getUsefulContents subpath >>= walk seed subpath + + walk seed root (name:names) = do + let path' = root </> name + info <- getInfo path' + case iter seed info of + done@(Done _) -> return done + Skip seed' -> walk seed' root names + Continue seed' + | infoIsDirectory info -> do + next <- fold seed' path' + case next of + done@(Done _) -> return done + seed'' -> walk (unwrap seed'') root names + | otherwise -> walk seed' root names + walk seed _ _ = return (Continue seed) + +atMostThreePictures :: Iterator [FilePath] + +atMostThreePictures paths info + | length paths == 3 + = Done paths + | infoIsDirectory info && takeFileName path == ".svn" + = Skip paths + | extension `elem` [".jpg", ".png"] + = Continue (path : paths) + | otherwise + = Continue paths + where extension = map toLower (takeExtension path) + path = infoPath info +{-- End of code from examples --} + + +{-- From examples/examples/ch09/ControlledVisit.hs modified according to the assignment --} +data Info = Info { + infoPath :: FilePath + , infoIsDirectory :: Bool + , infoPerms :: Maybe FileMode + , infoSize :: Maybe FileOffset + , infoModTime :: Maybe EpochTime + } deriving (Eq, Ord, Show) + +getInfo :: FilePath -> IO Info + +maybeIO :: IO a -> IO (Maybe a) +maybeIO act = handle (\(SomeException _) -> return Nothing) (Just `liftM` act) + +getInfo path = do + fileStatus <- getFileStatus path + let isDir = isDirectory fileStatus + perms <- maybeIO (return (fileMode fileStatus)) + size <- maybeIO (return (fileSize fileStatus)) + modified <- maybeIO (return (modificationTime fileStatus)) + return (Info path isDir perms size modified) + + +getDirContents :: FilePath -> IO [String] +getDirContents path = do + entries <- bracket (openDirStream path) closeDirStream (readStream []) + return entries + where + readStream :: [String] -> DirStream -> IO [String] + readStream entries ds = do + e <- readDirStream ds + if e == "" + then return entries + else readStream (entries ++ [e]) ds + + +getUsefulContents :: FilePath -> IO [String] +getUsefulContents path = do + names <- handle (\(SomeException _) -> return []) (getDirContents path) + return (filter (`notElem` [".", ".."]) names) +{-- End of code from examples --} + + +allFiles :: Iterator [FilePath] +allFiles paths info = Continue ((infoPath info) : paths) + +-- Contents of the test directory (reused from the 9_c_3 exercise): +-- test-9_c_3/ +-- test-9_c_3/e.jpg +-- test-9_c_3/dirA +-- test-9_c_3/dirA/b.jpg +-- test-9_c_3/dirA/a.png +-- test-9_c_3/.svn +-- test-9_c_3/.svn/svn.jpg +-- test-9_c_3/dirB +-- test-9_c_3/dirB/d.png +-- test-9_c_3/dirB/c.jpg + +-- ghci> :l 9_d_1.hs +-- [1 of 1] Compiling Main ( 9_d_1.hs, interpreted ) +-- Ok, one module loaded. + +-- ghci> foldTree atMostThreePictures [] "test-9_c_3/" +-- ["test-9_c_3/dirA/a.png","test-9_c_3/dirA/b.jpg","test-9_c_3/e.jpg"] + +-- ghci> foldTree allFiles [] "test-9_c_3/" +-- ["test-9_c_3/dirB/c.jpg", +-- "test-9_c_3/dirB/d.png", +-- "test-9_c_3/dirB", +-- "test-9_c_3/.svn/svn.jpg", +-- "test-9_c_3/.svn", +-- "test-9_c_3/dirA/a.png", +-- "test-9_c_3/dirA/b.jpg", +-- "test-9_c_3/dirA", +-- "test-9_c_3/e.jpg"] |
