packages feed

hspretty-0.1.0.0: src/PathFilter.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- Description : Filters for file paths.
module PathFilter where

import Data.Text.Short (ShortText)
import qualified Data.Text.Short as ShortText
import Path (Dir, File, Path, Rel)
import qualified Path

-- | Does a filter accept a path?
data PathAccept
  = -- | Filter accepts the path.
    Accept
  | -- | Filter rejects the path.
    Reject
  deriving (Show)

-- | Convert a 'PathAccept' type to a 'Bool' suitable for filtering.
toBool :: PathAccept -> Bool
toBool Accept = True
toBool Reject = False

-- | Convert a 'Bool' to a 'PathAccept'.
fromBool :: Bool -> PathAccept
fromBool True = Accept
fromBool False = Reject

-- | Path filter: examine a relative file and decide if we accept it.
newtype PathFilter = PathFilter
  { unPathFilter :: Path Rel File -> PathAccept
  }

instance Semigroup PathFilter where
  f1 <> f2 = PathFilter $ \path ->
    case unPathFilter f1 path of
      Reject -> Reject
      Accept -> unPathFilter f2 path

instance Monoid PathFilter where
  mempty = PathFilter $ const Accept

-- | PathFilter that excludes hidden files or directories, starting with a
--   period.
--
-- For example:
--
-- >>> :set -XQuasiQuotes
-- >>> import qualified Path
-- >>> unPathFilter pfNoHidden [Path.relfile|src/.hidden/something.txt|]
-- Reject
-- >>> unPathFilter pfNoHidden [Path.relfile|src/nothidden/something.txt|]
-- Accept
pfNoHidden :: PathFilter
pfNoHidden = componentFilter (fromBool . ShortText.isPrefixOf ".")

-- | PathFilter that excludes any path components that are named
--   @dist-newstyle@.
pfNoDistNewstyle :: PathFilter
pfNoDistNewstyle = componentFilter (fromBool . (==) "dist-newstyle")

-- | PathFilter that keeps only files with a given extension.
--
-- The extension to be tested should start with a period.
--
-- For example:
--
-- >>> :set -XQuasiQuotes -XOverloadedStrings
-- >>> import qualified Path
-- >>> pfHs = pfExtension ".hs"
-- >>> unPathFilter pfHs [Path.relfile|src/ModuleA/ModuleB.hs|]
-- Accept
-- >>> unPathFilter pfHs [Path.relfile|src/ModuleA/something.txt|]
-- Reject
pfExtension :: ShortText -> PathFilter
pfExtension extension = PathFilter $ \path -> fromBool (extensionMatches path)
  where
    extensionMatches :: Path Rel File -> Bool
    extensionMatches path = extension == ext path

    ext :: Path Rel File -> ShortText
    ext path = maybe "" ShortText.pack (Path.fileExtension path)

-- | Create a filter from a function that examines each component of a path.
componentFilter :: (ShortText -> PathAccept) -> PathFilter
componentFilter f = PathFilter $ \path ->
  fromBool . not . any (toBool . f) . pathComponents $ path

-- | Return the components of a path as a list.
--
-- For example:
--
-- >>> :set -XQuasiQuotes
-- >>> import qualified Path
-- >>> pathComponents [Path.relfile|dir/package/file.txt|]
-- ["dir","package","file.txt"]
-- >>> pathComponents [Path.relfile|file.txt|]
-- ["file.txt"]
pathComponents :: Path Rel File -> [ShortText]
pathComponents filePath = components
  where
    components :: [ShortText]
    components = reverse (fileName : pathParts)

    fileName :: ShortText
    fileName = ShortText.pack . Path.fromRelFile . Path.filename $ filePath

    pathParts :: [ShortText]
    pathParts = go (Path.parent filePath)

    go :: Path Rel Dir -> [ShortText]
    go dir
      | isTopDir dir = []
      | otherwise = curDirComp dir : go (Path.parent dir)
      where
        isTopDir :: Path Rel Dir -> Bool
        isTopDir d = Path.parent d == d

        curDirComp :: Path Rel Dir -> ShortText
        curDirComp d =
          ShortText.pack . init . Path.fromRelDir . Path.dirname $ d