Gabriella439 / turtle

Shell programming, Haskell style
BSD 3-Clause "New" or "Revised" License
940 stars 90 forks source link

Skip current file or directory when traversing a directory using find #39

Closed maoe closed 9 years ago

maoe commented 9 years ago

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?

Gabriella439 commented 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.

3noch commented 9 years ago

The Shell monad is trippy!

Gabriella439 commented 9 years ago

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)]
maoe commented 9 years ago

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.

Gabriella439 commented 9 years ago

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?

3noch commented 9 years ago

I'd expect test to be FilePath -> IO Bool.

Gabriella439 commented 9 years ago

Oops, yes, test should take the file path as an argument.

maoe commented 9 years ago

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.

Gabriella439 commented 9 years ago

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.