aboutsummaryrefslogtreecommitdiff
path: root/ch09/9_c_2.hs
blob: 5864d934da14a816319f92d7628d1193c36a0957 (plain)
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
-- The foldTree function performs preorder traversal. Modify it to allow the
-- caller to determine the order of traversal.

-- Preorder means that the iterator is applied to (the into of) a directory before
-- it is applied to its children.
-- Postorder means that the iterator is applied to (the into of) a directory after
-- it is applied to its children.

{-- 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 ControlledVisit (Info(..), getInfo, getUsefulContents, isDirectory)

import Control.Monad (liftM)
import Data.Char (toLower)
import System.FilePath ((</>), takeFileName, takeExtension, takeBaseName)

data TraversalOrder = Preorder | Postorder deriving (Eq)

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 -> TraversalOrder -> IO a

foldTree iter initSeed path traversalOrder = do
    endSeed <- fold initSeed path
    return (unwrap endSeed)
  where
    walk = if traversalOrder == Preorder
           then walkPreorder
           else walkPostorder
    fold seed subpath = getUsefulContents subpath >>= walk seed subpath

    walkPreorder 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'
          | isDirectory info -> do
              next <- fold seed' path'
              case next of
                done@(Done _) -> return done
                seed''        -> walk (unwrap seed'') root names
          | otherwise -> walk seed' root names
    walkPreorder seed _ _ = return (Continue seed)
{-- End of code from examples --}

    walkPostorder seed root (name:names) = do
      let path' = root </> name
      info <- getInfo path'
      seed' <- if isDirectory info
               then fold seed path'
               else return (Continue seed)
      case seed' of
        done@(Done _) -> return done
        Skip seed'    -> walk seed' root names
        Continue seed' -> do
          let x = iter seed' info
          case x of
            done@(Done _) -> return done
            seed''        -> walk (unwrap seed'') root names
    walkPostorder seed _ _ = return (Continue seed)

-- Helper iterator that returns the traversed files for easy observing of
-- effects of the order functions.
allPaths :: Iterator [FilePath]
allPaths paths info = Continue (paths ++ [(infoPath info)])


-- ghci> :l 9_c_2.hs
-- [1 of 2] Compiling ControlledVisit  ( ControlledVisit.hs, interpreted )
-- [2 of 2] Compiling Main             ( 9_c_2.hs, interpreted )
-- Ok, two modules loaded.

-- Output of the following commands is manually formatted for clarity
-- ghci> foldTree allPaths [] "test-9_b_1/" Preorder
-- ["test-9_b_1/dirC",
--  "test-9_b_1/dirC/F",
--  "test-9_b_1/dirC/E",
--  "test-9_b_1/A",
--  "test-9_b_1/B",
--  "test-9_b_1/dirD",
--  "test-9_b_1/dirD/G",
--  "test-9_b_1/dirD/H"]

-- ghci> foldTree allPaths [] "test-9_b_1/" Postorder
-- ["test-9_b_1/dirC/F"
--  "test-9_b_1/dirC/E"
--  "test-9_b_1/dirC"
--  "test-9_b_1/A"
--  "test-9_b_1/B"
--  "test-9_b_1/dirD/G"
--  "test-9_b_1/dirD/H"
--  "test-9_b_1/dirD"]