packages feed

directory-tree (empty) → 0.1

raw patch · 5 files changed

+437/−0 lines, 5 filesdep +basesetup-changed

Dependencies added: base

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) 2009 Brandon Simmons++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Directory/Tree.hs view
@@ -0,0 +1,288 @@++--------------------------------------------------------------------+-- |+-- Module    : System.Directory.Tree+-- Copyright : (c) Brandon Simmons+-- License   : BSD3+--+-- Maintainer:  Brandon Simmons <brandon.m.simmons@gmail.com>+-- Stability :  experimental+-- Portability: portable+--+-- Provides a simple data structure mirroring a directory tree on the +-- filesystem, as well as useful functions for reading and writing +-- file and directory structures in the IO monad. +-- +-- Errors are caught in a special constructor in the DirTree type.+-- +-- Defined instances of Functor, Traversable and Foldable allow for+-- easily operating on a directory of files. For example, you could use+-- Foldable.foldr to create a hash of the entire contents of a directory.+-- +-- The AnchoredDirTree type is a simple wrapper for DirTree to keep track +-- of a base directory context for the DirTree. +--+-- Please send me any requests, bugs, or other feedback on this module!+--+--------------------------------------------------------------------++module System.Directory.Tree (+         +       -- * Data types for representing directory trees+         DirTree (..)+       , AnchoredDirTree (..)+       , FileName+ +       -- * High level IO functions+       , readDirectory+       , readDirectoryWith+       , writeDirectory+       , writeDirectoryWith                            +                                                                        +       -- * Lower level functions+       , zipPaths+       , build+       , openDirectory+       , writeJustDirs                 +                                                                        +       -- * Utility functions+       -- ** Handling failure+       , successful+       , anyFailed+       , failures+       , failedMap+       -- ** Misc.+       , free                          +    ) where++{- +TODO:+    - add some tests+    - tree combining functions+    - tree searching based on file names+    - look into comonad abstraction+-}+++import System.Directory+import System.FilePath+import System.IO+import Control.Exception (handle, Exception)++import Data.Function (on)+import Data.List (sort, (\\))+import Control.Monad (liftM, filterM, liftM2, ap)++import Control.Applicative+import qualified Data.Traversable as T+import qualified Data.Foldable as F+++++-- | the String in the name field is always a file name, never a full path.+-- The free type variable is used in the File constructor and can hold Handles,+-- Strings representing a file's contents or anything else you can think of.+-- We catch any IO errors in the Failed constructor. an Exception can be +-- converted to a String with 'show'.+data DirTree a = Dir { name     :: FileName,+                       contents :: [DirTree a]  } +               | File { name :: FileName,+                        file :: a }+               | Failed { name :: FileName,+                          err  :: Exception }+                 deriving (Show, Eq)+++instance (Ord a)=> Ord (DirTree a) where+    compare = compare `on` name+++-- | a simple wrapper to hold a base directory name, which can be either +-- an absolute or relative path. This lets us give the DirTree a context,+-- while still letting us store only directory and file NAMES (not full paths)+-- in the DirTree. (uses an infix constructor; don't be scared)+data AnchoredDirTree a = FilePath :/ DirTree a+                     deriving (Show, Ord, Eq)++-- | an element in a FilePath:+type FileName = String+++instance Functor DirTree where+    fmap = T.fmapDefault ++instance F.Foldable DirTree where+    foldMap = T.foldMapDefault++instance T.Traversable DirTree where+    traverse f (Dir n cs)   = Dir n <$> T.traverse (T.traverse f) cs+    traverse f (File n a)   = File n <$> f a+    traverse f (Failed n e) = pure (Failed n e)+++++   +    ----------------------------+    --[ HIGH LEVEL FUNCTIONS ]--+    ----------------------------+++-- | build an AnchoredDirTree, given the path to a directory, opening the files+-- using readFile.+readDirectory :: FilePath -> IO (AnchoredDirTree String)+readDirectory = readDirectoryWith readFile++-- | same as readDirectory but allows us to, for example, use +-- ByteString.readFile to return a tree of ByteStrings.+readDirectoryWith :: (FilePath -> IO a) -> FilePath -> IO (AnchoredDirTree a)+readDirectoryWith f p = do (b:/t) <- build p+                           t'     <- T.mapM f t+                           return $ b:/t'+                        ++-- | write a DirTree of strings to disk. clobbers files of the same name. +-- doesn't affect files in the directories (if any already exist) with +-- different names:+writeDirectory :: AnchoredDirTree String -> IO ()+writeDirectory = writeDirectoryWith writeFile++-- | writes the directory structure to disc, then uses the provided function to +-- write the contents of Files to disc. +writeDirectoryWith :: (FilePath -> a -> IO ()) -> AnchoredDirTree a -> IO ()+writeDirectoryWith f t = do writeJustDirs t+                            F.mapM_ (uncurry f) (zipPaths t)++++++    -----------------------------+    --[ LOWER LEVEL FUNCTIONS ]--+    -----------------------------+++-- | a simple application of readDirectoryWith openFile:+openDirectory :: FilePath -> IOMode -> IO (AnchoredDirTree Handle)+openDirectory p m = readDirectoryWith (flip openFile m) p++++-- | builds a DirTree from the contents of the directory passed to it, saving +-- the base directory in the Anchored* wrapper. Errors are caught in the tree in+-- the Failed constructor. The 'file' fields initially are populated with full +-- paths to the files they are abstracting.+build :: FilePath -> IO (AnchoredDirTree FilePath)+build p = do let base = baseDir p+             tree <- build' p+             return (base :/ tree)+                     +-- HELPER: not exported:+build' :: FilePath -> IO (DirTree FilePath)+build' p = +    handle (return . Failed n) $ +           do isFile <- doesFileExist p    +              if isFile                         +                  -- store full path to the file in 'file' field:+                 then return (File n p)              +                  -- else is directory, build a Dir from contents:+                 else do cs <- getDirsFiles p+                         Dir n <$> T.mapM (build' . combine p) cs+      -- the directory to build, located under "base":+     where n = topDir p+++++                                +    -----------------+    --[ UTILITIES ]--+    -----------------++++++---- HANDLING FAILURES ----++-- | True if any Failed constructors in the tree+anyFailed :: DirTree a -> Bool+anyFailed = not . successful++-- | True if there are no Failed constructors in the tree+successful :: DirTree a -> Bool+successful = null . failures+++-- | returns a list of 'Failed' constructors only:+failures :: DirTree a -> [DirTree a]+failures (Dir _ cs) = concatMap failures cs+failures (File _ _) = []+failures f          = [f]+++-- | maps a function to convert Failed DirTrees to Files or Dirs+failedMap :: (FileName -> Exception -> DirTree a) -> DirTree a -> DirTree a+failedMap f (Dir n cs)   = Dir n $map (failedMap f) cs+failedMap f (Failed n e) = f n e+failedMap _ fle          = fle++++---- OTHER ----++-- | strips away base directory wrapper:+free :: AnchoredDirTree a -> DirTree a+free (_:/t) = t++++    ---------------+    --[ HELPERS ]--+    ---------------+++---- PATH CONVERSIONS ----++++-- | tuple up the complete filename with the File contents, by building up the +-- path, trie-style, from the root. The filepath will be relative to the current+-- directory.+-- This allows us to, for example, mapM_ 'uncurry writeFile' over a DirTree of +-- strings. +zipPaths :: AnchoredDirTree a -> DirTree (FilePath, a)+zipPaths (b :/ t) = zipP b t+    where zipP p (File n a)   = File n (p</>n , a)+          zipP p (Dir n cs)   = Dir n $ map (zipP $ p</>n) cs+          zipP p (Failed n e) = Failed n e+++-- extracting pathnames and base names:+topDir = last . splitDirectories ++baseDir = joinPath . init . splitDirectories++++---- IO HELPERS: ----+++-- | writes the directory structure (not files) of a DirTree to the anchored +-- directory. can be preparation for writing files:+writeJustDirs :: AnchoredDirTree a -> IO ()+writeJustDirs (b:/t) = write' b t+    where write' b' (Dir n cs) = do let bas = b' </> n+                                    createDirectoryIfMissing True bas+                                    mapM_ (write' bas) cs+          write' _ _           = return ()+++----- the let expression is an annoying hack, because dropFileName "." == ""+----- and getDirectoryContents fails epically on ""+-- prepares the directory contents list. we sort so that we can be sure of +-- a consistent fold/traversal order on the same directory:+getDirsFiles cs = do let cs' = if null cs then "." else cs +                     dfs <- getDirectoryContents cs'+                     return $ sort $ dfs \\ [".",".."]
+ directory-tree.cabal view
@@ -0,0 +1,60 @@+name:            directory-tree+version:         0.1+homepage:        http://coder.bsimmons.name/blog/2009/05/directory-tree-module-released/+synopsis:        A simple directory-like tree datatype, with useful IO functions +description:     A simple directory-like tree datatype, with useful IO functions and Foldable and Traversable instance  + .+ Provides a simple data structure mirroring a directory tree on the + filesystem, as well as useful functions for reading and writing + file and directory structures in the IO monad.+ .+ Importing the library and optional (useful) Foldable and Traverable libraries:+ .+ > import System.Directory.Tree+ > import qualified Data.Foldable as F+ > import qualified Data.Traversable as T+ .+ Write a hand-made directory tree of textfiles (strings) to the disk. + Simulates creating a new user Tux's home directory on a unix machine:+ .+ > writeDirectory$ "/home" :/ Dir "Tux" [File "README" "Welcome!"]+ .+ "read" a directory by opening all the files at a filepath with readFile,+ returning an 'AnchoredDirTree String' (d2). Then check for any IO failures:+ .+ > do (base :/ d2) <- readDirectory "../parent_dir/dir2/"+ >    let failed = anyFailed d2+ >    if failed then ...+ .+ Use Foldable instance function to concat a directory 'dir' of text files into a+ single file under the same directory:+ .+ > do (b :/ dt) <- readDirectory dir+ >    let f = F.concat dt+ >    return$ b :/ File "ALL_TEXT" f+ .+ Open all the files in the current directory as lazy bytestrings, ignoring + the base path in Anchored wrapper:+ .+ > import qualified Data.ByteString.Lazy as B+ > do (_ :/ dTree) <- readDirectoryWith B.readFile "./"     + .+ Please send me any comments, requests or bug reports+ .+ +category:        Data, System+license:         BSD3+license-file:    LICENSE+copyright:       (c) 2009, Brandon Simmons <brandon.m.simmons@gmail.com>+author:          Brandon Simmons+maintainer:      Brandon Simmons <brandon.m.simmons@gmail.com>+cabal-version:   >= 1.2.0+build-type:      Simple+tested-with:     GHC ==6.8.2+extra-source-files: examples.hs+++library+    exposed-modules: System.Directory.Tree+    build-depends: base+    ghc-options:       -Wall
+ examples.hs view
@@ -0,0 +1,61 @@+module Main+    where++import System.Directory.Tree+import qualified Data.Foldable as F+import qualified Data.Traversable as T++-- for main2:+import Data.Digest.Pure.MD5+import qualified Data.ByteString.Lazy as B +++main = darcsInitialize++-- simple example of creating a directory by hand and writing to disk: here we +-- replicate (kind of) running the command "darcs initialize" in the current +-- directory:+darcsInitialize = writeDirectory ("source_dir" :/ darcs_d) +    where darcs_d = Dir "_darcs" [prist_d, prefs_d, patch_d, inven_f, forma_f]++          prist_d = Dir "pristine.hashed" [hash_f]+          prefs_d = Dir "prefs" [motd_f, bori_f, bina_f]+          patch_d = Dir "patches" []+          inven_f = File "hashed_inventory"  ""+          forma_f = File "format"  "hashed\ndarcs-2\n"+          +          hash_f = File "da39a3ee5..."  ""+          motd_f = File "motd"          ""+          bori_f = File "boring"        "# Boring file regexps:\n..."+          bina_f = File "binaries"      "# Binary file regexps:\n..."+++-- here we read directories from different locations on the disk and combine +-- them into a new directory structure, ignoring the anchored base directory,+-- then simply 'print' the structure to screen:+combineDirectories = +        do (_:/d1) <- readDirectory "../dir1/"+           (b:/d2) <- readDirectory "/home/me/dir2"+           let readme = File "README"  "nothing to see here"+            +            -- anchor to the parent directory:+           print $  b:/Dir "Combined_Dir_Test" [d1,d2,readme]+++-- read two directory structures using readFile from Data.ByteString, and build +-- up an MD5 hash of all the files in each directory, compare the two hashes +-- to see if the directories are identical in their files. (note: doesn't take +-- into account directory name mis-matches)+verifyDirectories = +        do (_:/bsd1) <- readByteStrs "./dir_modified"+           (_:/bsd2) <- readByteStrs "./dir"+           let hash1 = hashDir bsd1+           let hash2 = hashDir bsd2+           print $ if hash1 == hash2+                      then "directories match with hash: " ++ show hash1+                      else show hash1 ++ " doesn't match " ++ show hash2++    where readByteStrs = readDirectoryWith B.readFile+          hashDir = md5Finalize. F.foldl' md5Update md5InitialContext++