Closed maoe closed 9 years ago
Sorry for the late response.
You can just use guard
or any pattern-match failure to skip a directory.
For example:
example :: Shell FilePath
example = do
file <- find (has "foo") "." -- Loop over all files with "foo" in their name
Just ext <- return (extension file) -- If the pattern match fails, skip this file
guard (ext == "txt") -- If the test fails, skip this file
return file
More generally, anything that calls empty
or mzero
(like guard
does) will skip the current file.
The Shell monad is trippy!
Yeah, the shell monad behaves the same way as the list monad. In the list monad, you can abort the current branch using guard
/mzero
/empty
/pattern-match-failure, too:
example :: [(Int, Int)]
example = do
x <- [1, 2]
y <- [3, 4]
guard (even (x + y))
return (x, y)
-- example = [(1, 3), (2, 4)]
Ah sorry, I should have clarified what I wanted more specifically.
Suppose you'd like to list up all git repositories under a given root directory, the following snippet could do the job.
{-# LANGUAGE OverloadedStrings #-}
import Prelude hiding (FilePath)
import Turtle
import System.Environment
main :: IO ()
main = do
dir:_ <- getArgs
view $ findGitRepos $ fromString dir
findGitRepos :: FilePath -> Shell FilePath
findGitRepos rootDir = do
path <- lstree rootDir
isDir <- liftIO $ testdir path
guard isDir
hasDotGit <- liftIO $ testdir $ path </> ".git"
if hasDotGit
then return path
else empty
What I'd like to archive here is that to prevent lstree
(or find
if it's used instead) from traversing further child directories when a .git
directory is found to speed up the traverse. Find.prune
or SkipDir
can be used for this purpose, whereas IIUC empty
just skips the current entry and will continue the further traverse. This feature is quite important if you have a huge git repositories (like GHC) under rootDir
.
lstree
is implemented in terms of ls
like this:
lstree :: FilePath -> Shell FilePath
lstree path = do
child <- ls path
isDir <- liftIO (testdir child)
if isDir
then return child <|> lstree child
else return child
The only thing you'd have to change is to add another test to decide whether to descend (by taking the second branch of the if
), like this:
prune :: IO Bool -> FilePath -> Shell FilePath
prune test path = do
child <- ls path
isDir <- liftIO (testdir child)
continue <- liftIO test
if isDir && continue
then return child <|> lstree child
else return child
Is that what you need?
I'd expect test
to be FilePath -> IO Bool
.
Oops, yes, test
should take the file path as an argument.
Sorry for the late reply. I was thinking something like https://hackage.haskell.org/package/hierarchy-0.1.1/docs/Pipes-Tree.html#v:winnow.
Okay, so the rough API I'm aiming for is this:
??? :: (FilePath -> IO Bool) -> FilePath -> Shell FilePath
??? test path = do
child <- ls path
isDir <- liftIO (testdir child)
if isDir
then do
continue <- liftIO (test child)
if continue
then return child <|> ??? test child
else return child
else return child
The only thing that's missing is a good name for this function. I might suggest lswhen
but I'm open to other suggestions.
Would it be possible to define a function which skips current file or directory when traversing a directory using
find
?For example Ruby has Find::prune, and Go has SkipDir for this purpose. Do you have any idea to define it in turtle?