packages feed

vcs-ignore-0.0.2.0: src/Data/VCS/Ignore/FileSystem.hs

{-# LANGUAGE MultiWayIf #-}

{-|
Module      : Data.VCS.Ignore.FileSystem
Description : Helper functions for working with file system
Copyright   : (c) 2020-2022 Vaclav Svejcar
License     : BSD-3-Clause
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

This module contains mainly helper functions, that are internally used by this
library.
-}

module Data.VCS.Ignore.FileSystem
  ( findPaths
  , listPaths
  , walkPaths
  , toPosixPath
  )
where


import           Control.Monad                  ( forM
                                                , mfilter
                                                )
import           Control.Monad.IO.Class         ( MonadIO
                                                , liftIO
                                                )
import           Data.Maybe                     ( catMaybes
                                                , fromMaybe
                                                )
import           System.Directory               ( doesDirectoryExist
                                                , doesFileExist
                                                , getDirectoryContents
                                                )
import           System.FilePath                ( (</>) )


-- | Recursively finds paths on given path whose filename matches the predicate.
findPaths :: MonadIO m
          => FilePath             -- ^ path to traverse
          -> (FilePath -> m Bool) -- ^ predicate to match filename
          -> m [FilePath]         -- ^ list of found paths
findPaths entryPath predicate = catMaybes <$> walkPaths entryPath process
 where
  process path = (\p -> if p then Just path else Nothing) <$> predicate path


-- | Recursively finds all paths on given path. If file reference is passed
-- instead of directory, such path is returned.
listPaths :: MonadIO m
          => FilePath      -- ^ path to traverse
          -> m [FilePath]  -- ^ list of found paths
listPaths entryPath = walkPaths entryPath pure


-- | Recursively walks the given path and performs selected action for each
-- found file. Output of this function is:
--
--   * If the given __path is file__, only this single path is processed and
--     returned.
--   * If the given __path is directory__, all subdirectories and files are
--     recursively processed and returned.
--   * If the given __path doesn't exist__, empy list will be returned.
walkPaths :: MonadIO m
          => FilePath          -- ^ path to traverse
          -> (FilePath -> m a) -- ^ function to process path
          -> m [a]             -- ^ result of traversed & processed paths
walkPaths entryPath fn = do
  isDir  <- liftIO $ doesDirectoryExist entryPath
  isFile <- liftIO $ doesFileExist entryPath
  if
    | isDir     -> fn entryPath >>= (\p -> (p :) <$> listDirectory entryPath)
    | isFile    -> pure <$> fn entryPath
    | otherwise -> pure []
 where
  listDirectory dir = do
    names <- liftIO $ getDirectoryContents dir
    paths <- forM (filter (`notElem` [".", ".."]) names) $ \name -> do
      let path = dir </> name
      isDirectory <- liftIO $ doesDirectoryExist path
      if isDirectory then walkPaths path fn else pure <$> fn path
    pure $ concat paths


-- | If the given path contains backward slashes (Windows style), converts them
-- into forward ones (Unix style).
--
-- >>> toPosixPath "foo\\bar\\x.txt"
-- "foo/bar/x.txt"
--
-- >>> toPosixPath "foo/bar/x.txt"
-- "foo/bar/x.txt"
toPosixPath :: FilePath -- ^ input filepath to convert
            -> FilePath -- ^ output filepath
toPosixPath = replace '\\' '/'
  where replace a b = fmap $ fromMaybe b . mfilter (/= a) . Just