directory-tree (empty) → 0.1
raw patch · 5 files changed
+437/−0 lines, 5 filesdep +basesetup-changed
Dependencies added: base
Files
- LICENSE +26/−0
- Setup.hs +2/−0
- System/Directory/Tree.hs +288/−0
- directory-tree.cabal +60/−0
- examples.hs +61/−0
+ 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++