Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

recursive directory walk function in stdlib?

291 views
Skip to first unread message

Alia K

unread,
Feb 12, 2009, 4:59:56 AM2/12/09
to
Hello,

As an learning exercise I've been trying to translate a python recipe
(http://code.activestate.com/recipes/576643/) I wrote a few weeks ago
into haskell.

One of the functions I seem to be unable to find in the haskell
standard library is a directory walker which takes a filepath and
recursively returns list of all files/directories in the path i.e:

walk :: FilePath -> IO [FilePath]

or even a walker which applies function to each filepath

walk :: FilePath -> (FilePath -> IO ()) -> IO [FilePath]

In any case, I started to attempt to write it from scratch using
System.Directory.getDirectoryContents but then when I got to the
directory check part I found that I have to resort to a non-portable
posix only function System.Posix.Files.isDirectory which seems to me
to be an unacceptable tradeoff.

So I am a bit confused on how to proceed, and would appreciate any
advice on doing this simply and portably.

Aliak

Michael Oswald

unread,
Feb 12, 2009, 7:14:05 AM2/12/09
to
Alia K wrote:
> One of the functions I seem to be unable to find in the haskell
> standard library is a directory walker which takes a filepath and
> recursively returns list of all files/directories in the path i.e:
>


Maybe you can have a look at:

http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html


hth,
Michael

Alia K

unread,
Feb 12, 2009, 7:17:38 AM2/12/09
to
Michael Oswald wrote:
> Maybe you can have a look at:
>
> http://book.realworldhaskell.org/read/io-case-study-a-library-for-sea...

Thanks for that! That's exactly what I was looking for.

Best,

AK

Alia K

unread,
Feb 14, 2009, 9:41:04 AM2/14/09
to
Incidentally, here's my first attempt at the translation (of the
python recipe http://code.activestate.com/recipes/576643/) which was
realized with a lot of help from this newsgroup (-:

<Clean.hs>

module Main where

import System.Environment (getArgs)
import System.Console.GetOpt
import System.IO
import Control.Monad (forM)
import System.FilePath (joinPath, takeExtension)
import qualified System.Directory as Dir

actions :: [(String, FilePath -> IO ())]
actions = [
("delete", delete),
("clean endings", clean_endings)
]

-- types and synonyms
data Flag = Verbose | Endings | Negated | Path String
deriving (Show, Eq)

-- utility functions
getPath :: [Flag] -> FilePath
getPath flags = head [ path | Path path <- flags ++ [Path "."]]


-- recursive directory walk


walk :: FilePath -> IO [FilePath]

walk topdir = do
names <- Dir.getDirectoryContents topdir
let proper_names = filter (`notElem` [".", ".."]) names
paths <- forM proper_names $ \name -> do
let path = joinPath [topdir, name]
isDirectory <- Dir.doesDirectoryExist path
if isDirectory
then walk path
else return [path]
return (concat paths)

find :: (FilePath -> Bool) -> FilePath -> IO [FilePath]
find predicate path = do
names <- walk path
return (filter predicate names)

-- path functions
display :: FilePath -> IO ()
display path = do
isfile <- Dir.doesFileExist path
if isfile then putStrLn $ "|--> " ++ path else do
isdir <- Dir.doesDirectoryExist path
if isdir then putStrLn $ "+--> " ++ path else do
error $ path ++ " does not exist"

delete :: FilePath -> IO ()
delete path = do
isfile <- Dir.doesFileExist path
if isfile then Dir.removeFile path else do
isdir <- Dir.doesDirectoryExist path
if isdir then Dir.removeDirectoryRecursive path else do
error $ path ++ " does not exist"

clean_endings :: FilePath -> IO ()
clean_endings path = do
infile <- openFile path ReadMode
contents <- hGetContents infile
hClose infile
let cleaned = unlines [strip line ++ "\n" | line <- lines
contents ]
outfile <- openFile path WriteMode
hPutStr outfile cleaned
hClose outfile
where strip line = unwords $ words line

-- matcher functions
endswith :: [String] -> FilePath -> Bool
endswith patterns path = or [takeExtension path == p | p <- patterns]

-- primary processing function
process :: [Flag] -> [String] -> IO ()
process flags patterns = do
let path = getPath flags
dump flags patterns
if elem Negated flags then do
files <- find (not . endswith patterns) path
manage files flags
else do
files <- find (endswith patterns) path
manage files flags
where
manage files flags
| elem Endings flags = execute "clean endings" files
| otherwise = execute "delete" files

execute cmd files = do
mapM_ display files
let (Just action) = lookup cmd actions
putStrLn $ "Apply '" ++ cmd ++ "' to all (y/n)?"
answer <- getChar
if answer == 'y' then do
mapM_ action files
else error $ cmd ++" action aborted"

dump flags patterns =
if elem Verbose flags then do
putStrLn $ "options: " ++ show flags
putStrLn $ "patterns: " ++ show patterns
else return ()


options :: [OptDescr Flag]
options = [
Option ['p'] ["path"] (ReqArg Path "PATH") "set path to
process",
Option ['v'] ["verbose"] (NoArg Verbose) "show verbose
output",
Option ['e'] ["endings"] (NoArg Endings) "clean line
endings",
Option ['n'] ["negated"] (NoArg Negated) "clean all except
patterns"
]

main = do
args <- getArgs
case getOpt RequireOrder options args of
([], [], []) -> error $ usageInfo header options
(flags, [], []) -> error $ usageInfo header options
(flags, nonOpts, []) -> process flags nonOpts
(_, _, msgs) -> error $ concat msgs ++ usageInfo header
options
where header = "Usage: clean [options] patterns"

</Clean.hs>

AK

Mark T.B. Carroll

unread,
Feb 14, 2009, 3:23:40 PM2/14/09
to
Alia K <alia_...@yahoo.com> writes:

> Incidentally, here's my first attempt at the translation (of the
> python recipe http://code.activestate.com/recipes/576643/) which was
> realized with a lot of help from this newsgroup (-:

Thank you for sharing it with us.

> let proper_names = filter (`notElem` [".", ".."]) names

I use this sort of thing too, and I wish I didn't have to; I don't know
how portable it is. I wish there were a way to check directory contents
with weird things omitted, especially for traversing filesystems. Or
maybe it's more portable than I fear, and I worry needlessly?

Mark

Alia K

unread,
Feb 15, 2009, 4:52:56 PM2/15/09
to
Mark T.B. Carroll wrote:

> Thank you for sharing it with us.

My pleasure. It's my first bit of useful haskell code ever (-:

> > let proper_names = filter (`notElem` [".", ".."]) names

> I use this sort of thing too, and I wish I didn't have to; I don't
> know how portable it is. I wish there were a way to check
> directory contents with weird things omitted, especially for
> traversing filesystems. Or maybe it's more portable than I fear, > and I worry needlessly?

Good question. I couldn't say myself as I actually lifted that
function wholesale from (http://book.realworldhaskell.org/read/io-case-
study-a-library-for-sea) having not found anything equivalent in the
ghc stdlib -- which is surprising given how much esoterica it already
contains.

Still, the thing I miss most (coming from python) is flexibility in
terms of defining namespaces: I know one can use modules but I would
have loved some way to group my functions a bit more readily without
having to create multiple files.

AK

Mark T.B. Carroll

unread,
Feb 15, 2009, 8:28:26 PM2/15/09
to
Alia K <alia_...@yahoo.com> writes:

> Good question. I couldn't say myself as I actually lifted that
> function wholesale from (http://book.realworldhaskell.org/read/io-case-
> study-a-library-for-sea) having not found anything equivalent in the
> ghc stdlib -- which is surprising given how much esoterica it already
> contains.

Ah, I haven't gotten far through that book yet. I'm going through slowly
because I figure that, like my possible overuse of Maybe, I've found
ways to do things that omit simpler alternatives, so I'm checking I'm
not missing something basic that I already found a workaround for.

The side references to geek culture have been very cute though in some
of the examples.

> Still, the thing I miss most (coming from python) is flexibility in
> terms of defining namespaces: I know one can use modules but I would
> have loved some way to group my functions a bit more readily without
> having to create multiple files.

You must hate Java. (-:

Mark

Chris Saunders

unread,
Feb 17, 2009, 6:46:16 AM2/17/09
to
Couldn't get the URL you posted to work, could you post one to the intended
location that does?

Regards
Chris Saunders

"Alia K" <alia_...@yahoo.com> wrote in message
news:a84acf93-c3bf-4a38...@x10g2000yqk.googlegroups.com...

Mark T.B. Carroll

unread,
Feb 17, 2009, 9:54:13 AM2/17/09
to
"Chris Saunders" <ev...@mountaincable.net> writes:

> "Alia K" <alia_...@yahoo.com> wrote in message
> news:a84acf93-c3bf-4a38...@x10g2000yqk.googlegroups.com...
>>

>> http://book.realworldhaskell.org/read/io-case-
>> study-a-library-for-sea

> Couldn't get the URL you posted to work, could you post one to the intended
> location that does?

It was meant to be
http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html
which can be shortened to http://tinyurl.com/b94ymt

Mark

0 new messages