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
Maybe you can have a look at:
http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html
hth,
Michael
Thanks for that! That's exactly what I was looking for.
Best,
AK
<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
> 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
> 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
> 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
Regards
Chris Saunders
"Alia K" <alia_...@yahoo.com> wrote in message
news:a84acf93-c3bf-4a38...@x10g2000yqk.googlegroups.com...
> "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