packages feed

vcs-ignore (empty) → 0.0.1.0

raw patch · 30 files changed

+1434/−0 lines, 30 filesdep +Globdep +basedep +containerssetup-changed

Dependencies added: Glob, base, containers, directory, doctest, exceptions, filepath, hspec, optparse-applicative, text, vcs-ignore

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Changelog for vcs-ignore++## v0.0.1.0 (2021-05-10)+- initial release+
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2020-2021, Vaclav Svejcar+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its+   contributors may be used to endorse or promote products derived from+   this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,65 @@++# vcs-ignore+`vcs-ignore` is small Haskell library used to find, check and process files ignored by selected _VCS_.++## 1. Table of Contents++- [1. Table of Contents](#1-table-of-contents)+- [2. Example of Use](#2-example-of-use)+    - [2.1. Listing all files/directories ignored by VCS](#21-listing-all-filesdirectories-ignored-by-vcs)+    - [2.2. Walking files/directories ignored by VCS](#22-walking-filesdirectories-ignored-by-vcs)+    - [2.3. Checking if path is ignored by VCS](#23-checking-if-path-is-ignored-by-vcs)++++## 2. Example of Use+Because this library is really simple to use, following example should be enough to understand how to use it for your project.++### 2.1. Listing all files/directories ignored by VCS+```haskell+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.VCS.Ignore ( Git, Repo(..), listRepo )++example :: IO [FilePath]+example = do+  repo <- scanRepo @Git "path/to/repo"+  listRepo repo+```++### 2.2. Walking files/directories ignored by VCS+```haskell+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.Maybe       ( catMaybes )+import System.Directory ( doesFileExist )+import Data.VCS.Ignore  ( Git, Repo(..), walkRepo )++onlyFiles :: IO [FilePath]+onlyFiles = do+  repo <- scanRepo @Git "path/to/repo"+  catMaybes <$> walkRepo repo walkFn+ where+  walkFn path = do+    file <- doesFileExist path+    pure (if file then Just path else Nothing)++```++### 2.3. Checking if path is ignored by VCS+```haskell+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.VCS.Ignore ( Git, Repo(..) )++checkIgnored :: IO Bool+checkIgnored = do+  repo <- scanRepo @Git "path/to/repo"+  isIgnored repo "/some/path/.DS_Store"+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE RecordWildCards  #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns     #-}++{-|+Module      : Main+Description : Simple application using the /vcs-ignore/ library+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++This simple application demonstrates the use of "vcs-ignore" library. It allows+to check whether path given as argument is ignored within existing /GIT/ repo.+-}++module Main+  ( main+  )+where++import           Control.Monad                  ( when )+import           Data.VCS.Ignore.Core           ( findRepo )+import           Data.VCS.Ignore.Repo           ( Repo(..) )+import           Data.VCS.Ignore.Repo.Git       ( Git )+import           Main.Options                   ( Mode(..)+                                                , Options(..)+                                                , optionsParser+                                                )+import           Options.Applicative            ( execParser )+import           System.Directory               ( canonicalizePath+                                                , getCurrentDirectory+                                                )+import           System.Exit                    ( exitFailure+                                                , exitSuccess+                                                )+import           System.FilePath                ( makeRelative )+++main :: IO ()+main = do+  options <- execParser optionsParser+  repo    <- findRepoOrFail @Git options+  executeMode repo options+++findRepoOrFail :: (Repo r, Show r) => Options -> IO r+findRepoOrFail Options {..} = do+  repoDir   <- getCurrentDirectory+  maybeRepo <- findRepo repoDir+  case maybeRepo of+    Just repo -> do+      putStrLn $ "Found repository at: " <> repoRoot repo+      when oDebug (putStrLn $ "Repository details: " <> show repo)+      pure repo+    Nothing -> do+      putStrLn $ "No repository found for path: " <> repoDir+      exitFailure+++executeMode :: Repo r => r -> Options -> IO ()+executeMode repo (oMode -> Path path) = checkPath repo path+++checkPath :: Repo r => r -> FilePath -> IO ()+checkPath repo path = do+  relative <- makeRelative (repoRoot repo) <$> canonicalizePath path+  excluded <- isIgnored repo relative+  if excluded then reportIgnored else reportNotIgnored+ where+  reportIgnored = do+    putStrLn $ "Path '" <> path <> "' IS ignored"+    exitSuccess+  reportNotIgnored = do+    putStrLn $ "Path '" <> path <> "' IS NOT ignored"+    exitFailure+
+ app/Main/Options.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE StrictData #-}++{-|+Module      : Main.Options+Description : Options definitions for "optparse-applicative"+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++Options definitions for "optparse-applicative".+-}++module Main.Options+  ( Options(..)+  , Mode(..)+  , optionsParser+  )+where++import           Main.Vendor                    ( productDesc+                                                , productInfo+                                                )+import           Options.Applicative+++data Options = Options+  { oMode  :: Mode+  , oDebug :: Bool+  }+  deriving (Eq, Show)++data Mode = Path FilePath+  deriving (Eq, Show)+++optionsParser :: ParserInfo Options+optionsParser = info (options <**> helper)+                     (fullDesc <> progDesc productDesc <> header productInfo)+ where+  options =+    Options+      <$> (   Path+          <$> strOption+                (long "path" <> short 'p' <> metavar "PATH" <> help+                  "path to check"+                )+          )+      <*> switch (long "debug" <> help "produce more verbose output")
+ app/Main/Vendor.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}++{-|+Module      : Main.Vendor+Description : Details about the application.+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++Module providing info about this application.+-}++module Main.Vendor+  ( buildVersion+  , productDesc+  , productInfo+  , productName+  , webRepo+  )+where++import           Data.String                    ( IsString(..) )+import           Data.Version                   ( showVersion )+import           Paths_vcs_ignore               ( version )+++-- | Product version.+buildVersion :: IsString a => a+buildVersion = fromString . showVersion $ version+++productDesc :: IsString a => a+productDesc = "library for handling files ignored by VCS systems"+++-- | Product info.+productInfo :: (IsString a, Monoid a) => a+productInfo = mconcat [productName, ", v", buildVersion, " :: ", webRepo]+++-- | Product full name.+productName :: IsString a => a+productName = "vcs-ignore"+++-- | Product source code repository.+webRepo :: IsString a => a+webRepo = "https://github.com/vaclavsvejcar/vcs-ignore"
+ doctest/Main.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Test.DocTest+    +main :: IO ()+main = doctest ["-XOverloadedStrings", "src"]
+ src/Data/VCS/Ignore.hs view
@@ -0,0 +1,89 @@+{-|+Module      : Data.VCS.Ignore+Description : Reexported modules for convenience+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++@vcs-ignore@ is small Haskell library used to find, check and process files+ignored by selected /VCS/.++= Example of Use+Because this library is really simple to use, following example should be+enough to understand how to use it for your project.++== Listing all files/directories ignored by VCS+@+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.VCS.Ignore ( Git, Repo(..), listRepo )++example :: IO [FilePath]+example = do+  repo <- scanRepo @Git "path/to/repo"+  listRepo repo+@++== Walking files/directories ignored by VCS+@+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.Maybe       ( catMaybes )+import System.Directory ( doesFileExist )+import Data.VCS.Ignore  ( Git, Repo(..), walkRepo )++onlyFiles :: IO [FilePath]+onlyFiles = do+  repo <- scanRepo @Git "path/to/repo"+  catMaybes <$> walkRepo repo walkFn+ where+  walkFn path = do+    file <- doesFileExist path+    pure (if file then Just path else Nothing)+@++== Checking if path is ignored by VCS+@+{-# LANGUAGE TypeApplications #-}++module Data.VCS.Test where++import Data.VCS.Ignore ( Git, Repo(..) )++checkIgnored :: IO Bool+checkIgnored = do+  repo <- scanRepo @Git "path/to/repo"+  isIgnored repo "/some/path/.DS_Store"+@+-}++module Data.VCS.Ignore+  ( -- Working with ignored files+    findRepo+  , listRepo+  , walkRepo+    -- * Repo /type class/+  , Repo(..)+  , RepoError(..)+    -- * /GIT/ implementation+  , Git(..)+    -- * Common data types+  , VCSIgnoreError(..)+  )+where++import           Data.VCS.Ignore.Core           ( findRepo+                                                , listRepo+                                                , walkRepo+                                                )+import           Data.VCS.Ignore.Repo           ( Repo(..)+                                                , RepoError(..)+                                                )+import           Data.VCS.Ignore.Repo.Git       ( Git(..) )+import           Data.VCS.Ignore.Types          ( VCSIgnoreError(..) )
+ src/Data/VCS/Ignore/Core.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE StrictData       #-}+{-# LANGUAGE TypeApplications #-}++{-|+Module      : Data.VCS.Ignore.Core+Description : Core operations over the repository+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++This module contains core operations you can perform over the scanned 'Repo'.+-}++module Data.VCS.Ignore.Core+  ( findRepo+  , listRepo+  , walkRepo+  )+where++import           Control.Exception              ( try )+import           Control.Monad.IO.Class         ( MonadIO+                                                , liftIO+                                                )+import qualified Data.List                     as L+import           Data.Maybe                     ( catMaybes+                                                , fromMaybe+                                                )+import           Data.VCS.Ignore.FileSystem     ( walkPaths )+import           Data.VCS.Ignore.Repo           ( Repo(..) )+import           Data.VCS.Ignore.Types          ( VCSIgnoreError )+import           System.FilePath                ( pathSeparator+                                                , takeDirectory+                                                )+++-- | Attempts to find (and scan via 'scanRepo') repository at given path.+-- If given path doesn't contain valid repository, it recursively tries in every+-- parent directory until the root directory (e.g. @C:@ or @/@) is reached.+findRepo :: (MonadIO m, Repo r)+         => FilePath+         -- ^ path where to start scanning+         -> m (Maybe r)+         -- ^ scanned 'Repo' (if found)+findRepo = liftIO . go+ where+  go dir = do+    let parent = takeDirectory dir+    maybeRepo <- try @VCSIgnoreError (scanRepo dir)+    case maybeRepo of+      Left _ | parent == dir -> pure Nothing+      Left  _                -> go parent+      Right repo             -> pure . Just $ repo+++-- | Resursively lists all non-ignored paths withing the given repository+-- (both files and directories).+listRepo :: (MonadIO m, Repo r)+         => r+         -- ^ repository to list+         -> m [FilePath]+         -- ^ list of non-ignored paths within the repository+listRepo repo = walkRepo repo pure+++-- | Similar to 'listRepo', but allows to perform any action on every+-- non-ignored path within the repository.+walkRepo :: (MonadIO m, Repo r)+         => r+         -- ^ repository to walk+         -> (FilePath -> m a)+         -- ^ action to perform on every non-excluded filepath+         -> m [a]+         -- ^ list of paths transformed by the action function+walkRepo repo fn = do+  let search path | L.null path = pure Nothing+                  | otherwise   = doSearch path+  catMaybes <$> walkPaths root' (search . relativePath)+ where+  ps           = [pathSeparator]+  root         = repoRoot repo+  root'        = if ps `L.isSuffixOf` root then root else root <> ps+  relativePath = dropPrefix root'+  dropPrefix   = \prefix t -> fromMaybe t (L.stripPrefix prefix t)+  doSearch     = \path -> isIgnored repo path >>= process path+  process      = \path x -> if x then pure Nothing else Just <$> fn path
+ src/Data/VCS/Ignore/FileSystem.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE MultiWayIf #-}++{-|+Module      : Data.VCS.Ignore.FileSystem+Description : Helper functions for working with file system+Copyright   : (c) 2020-2021 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 (performing possible I/O actions)+          -> 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 (performing possible I/O actions)+          -> 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
+ src/Data/VCS/Ignore/Repo.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE StrictData #-}++{-|+Module      : Data.VCS.Ignore.Repo+Description : Type class representing the VCS repository+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++This module contains /type class/ representing the selected type of /VCS/+repository.+-}++module Data.VCS.Ignore.Repo+  ( Repo(..)+  , RepoError(..)+  )+where++import           Control.Exception              ( Exception(..) )+import           Control.Monad.Catch            ( MonadThrow )+import           Control.Monad.IO.Class         ( MonadIO )+import           Data.Text                      ( Text )+import qualified Data.Text                     as T+import           Data.VCS.Ignore.Types          ( fromVCSIgnoreError+                                                , toVCSIgnoreError+                                                )+++-- | /Type class/ representing instance of /VCS/ repository of selected type.+-- In order to obtain instance, the physical repository needs to be scanned+-- first by the 'scanRepo' method. Then absolute path to the repository root is+-- provided by 'repoRoot' method. To check if any path (relative to the repo+-- root) is ignored or not, use the 'isIgnored' method.+class Repo r where++  -- | Returns name of the repository (e.g. @GIT@).+  repoName :: r+           -- ^ /VCS/ repository instance+           -> Text+           -- ^ name of the repository++  -- | Returns absolute path to the root of the /VCS/ repository.+  repoRoot :: r+           -- ^ /VCS/ repository instance+           -> FilePath+           -- ^ absolute path to the repository++  -- | Scans repository at given path. If the given path doesn't contain valid+  -- repository, 'RepoError' may be thrown.+  scanRepo :: (MonadIO m, MonadThrow m)+           => FilePath+           -- ^ path to the /VCS/ repository root+           -> m r+           -- ^ scanned repository (or failure)++  -- | Checks whether the given path is ignored. The input path is expected to+  -- be relative to the repository root, it might or might not point to existing+  -- file or directory.+  isIgnored :: MonadIO m+            => r+            -- ^ /VCS/ repository instance+            -> FilePath+            -- ^ path to check, relative to the repository root+            -> m Bool+            -- ^ whether the path is ignored or not+++-- | Represents error related to operations over the /VCS/ repository.+data RepoError = InvalidRepo FilePath Text+  -- ^ Given 'FilePath' doesn't contain valid /VCS/ repository root.+  deriving (Eq, Show)++instance Exception RepoError where+  displayException = displayException'+  fromException    = fromVCSIgnoreError+  toException      = toVCSIgnoreError+++displayException' :: RepoError -> String+displayException' (InvalidRepo path reason) =+  mconcat ["Path '", path, "' is not a valid repository: ", T.unpack reason]
+ src/Data/VCS/Ignore/Repo/Git.hs view
@@ -0,0 +1,297 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StrictData          #-}+{-# LANGUAGE TupleSections       #-}++{-|+Module      : Data.VCS.Ignore.Repo.Git+Description : Implementation of 'Repo' for /GIT/+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++This module contains implementation of 'Repo' /type class/ for the /GIT/ content+versioning system. Most of the public functions is exported only to make them+visible for tests, end user of this library really shouldn't need to use them.+-}++module Data.VCS.Ignore.Repo.Git+  ( Git(..)+  , Pattern(..)+  , compilePattern+  , matchesPattern+  , parsePatterns+  , loadPatterns+  , findGitIgnores+  , gitIgnorePatterns+  , repoPatterns+  , globalPatterns+  , scanRepo'+  , isIgnored'+  , isGitRepo+  )+where++import           Control.Exception              ( SomeException+                                                , catch+                                                )+import           Control.Monad.Catch            ( MonadThrow+                                                , throwM+                                                )+import           Control.Monad.IO.Class         ( MonadIO+                                                , liftIO+                                                )+import qualified Data.List                     as L+import           Data.Maybe                     ( fromMaybe+                                                , maybeToList+                                                )+import           Data.String                    ( IsString(..) )+import           Data.Text                      ( Text )+import qualified Data.Text                     as T+import qualified Data.Text.IO                  as T+import           Data.VCS.Ignore.FileSystem     ( findPaths+                                                , toPosixPath+                                                )+import           Data.VCS.Ignore.Repo           ( Repo(..)+                                                , Repo(..)+                                                , RepoError(..)+                                                )+import           System.Directory               ( XdgDirectory(XdgConfig)+                                                , canonicalizePath+                                                , doesDirectoryExist+                                                , getXdgDirectory+                                                , makeAbsolute+                                                )+import           System.FilePath                ( makeRelative+                                                , (</>)+                                                )+import qualified System.FilePath.Glob          as G+++-- | Data type representing scanned instance of /GIT/ repository.+data Git = Git+  { gitRepoRoot :: FilePath+  -- ^ absolute path to the repository root+  , gitPatterns :: [(FilePath, [Pattern])]+  -- ^ patterns ignored at given repository paths+  }+  deriving (Eq, Show)++instance Repo Git where+  repoName  = const "Git"+  repoRoot  = gitRepoRoot+  scanRepo  = scanRepo' globalPatterns repoPatterns gitIgnorePatterns isGitRepo+  isIgnored = isIgnored'++-- | Represents single pattern to be used as a rule for ignoring paths.+data Pattern = Pattern+  { pPatterns  :: [G.Pattern]+  -- ^ underlying implementation+  , pRaw       :: Text+  -- ^ raw textual representation of the pattern+  , pIsNegated :: Bool+  -- ^ whether the pattern is the negation (starts with @!@)+  }+  deriving (Eq, Show)++instance IsString Pattern where+  fromString = compilePattern . T.pack+++------------------------------  PUBLIC FUNCTIONS  ------------------------------++-- | Compiles pattern.+compilePattern :: Text+               -- ^ raw pattern as text+               -> Pattern+               -- ^ compiled pattern+compilePattern raw =+  let woPrefix = fromMaybe raw $ T.stripPrefix "!" raw+      patterns = r2 . r1 $ woPrefix+  in  Pattern { pPatterns  = fmap (G.compile . T.unpack) patterns+              , pRaw       = raw+              , pIsNegated = raw /= woPrefix+              }+ where+  r1 p | any (`T.isPrefixOf` p) ["/", "*"] = p+       | length (filter (not . T.null) . T.splitOn "/" $ p) == 1 = "**/" <> p+       | otherwise                         = "/" <> p+  r2 p | "/" `T.isSuffixOf` p = [p <> "**"]+       | otherwise            = [p, p <> "/**"]+++-- | Tests whether given path matches against the pattern.+matchesPattern :: Pattern+               -- ^ pattern to match against+               -> FilePath+               -- ^ path to check+               -> Bool+               -- ^ check result+matchesPattern ptn path = any (`G.match` path) (pPatterns ptn)+++-- | Parses /Glob/ patterns from given text source. Each line in input text is+-- considered to be single pattern. Lines starting with @#@ (comments) and blank+-- lines are skipped.+--+-- >>> parsePatterns "*.xml\n.DS_Store"+-- [Pattern {pPatterns = [compile "*.xml",compile "*.xml/*"], pRaw = "*.xml", pIsNegated = False},Pattern {pPatterns = [compile "**/.DS_Store",compile "**/.DS_Store/*"], pRaw = ".DS_Store", pIsNegated = False}]+parsePatterns :: Text+              -- ^ text to parse+              -> [Pattern]+              -- ^ parsed patterns+parsePatterns = fmap compilePattern . filter (not . excluded) . T.lines+ where+  excluded = \line -> or $ fmap ($ T.stripStart line) [comment, T.null]+  comment  = \line -> "#" `T.isPrefixOf` line+++-- | Loads /Glob/ patterns from given text file. If the fille cannot be read for+-- any reason, empty list is returned. See 'parsePatterns' for more details+-- about parsing.+loadPatterns :: MonadIO m+             => FilePath+             -- ^ path to text file to parse+             -> m [Pattern]+             -- ^ parsed /Glob/ patterns+loadPatterns path = parsePatterns <$> liftIO content+ where+  content = catch (T.readFile path) (\(_ :: SomeException) -> pure T.empty)+++-- | Recursively finds all @.gitignore@ files within the given directory path.+findGitIgnores :: MonadIO m+               => FilePath+               -- ^ path to the directory to search in+               -> m [FilePath]+               -- ^ paths of found @.gitignore@ files+findGitIgnores repoDir = findPaths repoDir isGitIgnore+  where isGitIgnore path = pure $ ".gitignore" `L.isSuffixOf` path+++-- | Recursively finds all @.gitignore@ files within the given directory path+-- and parses them into /Glob/ patterns. See 'loadPatterns' and 'findGitIgnores'+-- for more details.+gitIgnorePatterns :: MonadIO m+                  => FilePath+                  -- ^ path to the directory to search @.gitignore@ files in+                  -> m [(FilePath, [Pattern])]+                  -- ^ list of @.gitignore@ paths and parsed /Glob/ patterns+gitIgnorePatterns repoDir = do+  gitIgnores <- findGitIgnores repoDir+  mapM (\p -> (toPosixPath . path $ p, ) <$> loadPatterns p) gitIgnores+  where path p = stripSuffix' ".gitignore" $ stripPrefix' repoDir p+++-- | Loads /GIT/ repository specific ignore patterns, present in+-- @REPO_ROOT\/info\/exclude@ file.+repoPatterns :: MonadIO m+             => FilePath+             -- ^ path to the /GIT/ repository root+             -> m [Pattern]+             -- ^ parsed /Glob/ patterns+repoPatterns repoDir = loadPatterns $ repoDir </> "info" </> "exclude"+++-- | Loads global /GIT/  ignore patterns, present in+-- @XDG_CONFIG_GOME\/git\/ignore@ file.+globalPatterns :: MonadIO m => m [Pattern]+               -- ^ parsed /Glob/ patterns+globalPatterns =+  (liftIO . getXdgDirectory XdgConfig $ ("git" </> "ignore")) >>= loadPatterns+++-- | Internal version of 'scanRepo', where individual functions needs to be+-- explicitly provided, which is useful mainly for testing purposes.+scanRepo' :: (MonadIO m, MonadThrow m)+          => m [Pattern]+          -- ^ reference to 'globalPatterns' function (or similar)+          -> (FilePath -> m [Pattern])+          -- ^ reference to 'repoPatterns' function (or similar)+          -> (FilePath -> m [(FilePath, [Pattern])])+          -- ^ reference to 'gitIgnorePatterns' function (or similar)+          -> (FilePath -> m Bool)+          -- ^ reference to 'isGitRepo' function (or similar)+          -> FilePath+          -- ^ path to /GIT/ repository root+          -> m Git+          -- ^ scanned /Git/ repository+scanRepo' globalPatternsFn repoPatternsFn gitIgnoresFn isGitRepoFn repoDir = do+  absRepoDir <- liftIO $ makeAbsolute repoDir+  gitRepo    <- isGitRepoFn absRepoDir+  (if gitRepo then proceed else abort) absRepoDir+ where+  abort repoDir' = throwM $ InvalidRepo repoDir' "not a valid GIT repository"+  proceed repoDir' = do+    globalPatterns' <- globalPatternsFn+    repoPatterns'   <- repoPatternsFn repoDir'+    gitIgnores      <- gitIgnoresFn repoDir'+    let (r, o)   = sep gitIgnores+        patterns = [("/", globalPatterns' <> repoPatterns' <> r)] <> o+    pure Git { gitRepoRoot = repoDir', gitPatterns = patterns }+  sep xs =+    let predicate = \(p, _) -> p == "/"+        woRoot    = filter (not . predicate) xs+        root      = concat . maybeToList $ snd <$> L.find predicate xs+    in  (root, woRoot)+++-- | Internal version of 'isIgnored' function.+isIgnored' :: MonadIO m+           => Git+           -- ^ scanned /GIT/ repository+           -> FilePath+           -- ^ path to check if ignored+           -> m Bool+           -- @True@ if given path is ignored+isIgnored' git@(Git _ patterns) path = do+  np <- toPosixPath <$> normalize (repoRoot git) path+  let ignored = any (check2 np False) (filtered np)+      negated = any (check2 np True) (filtered np)+  pure $ ignored && not negated+ where+  sanitized  = addPrefix "/"+  asRepoPath = \np -> (`stripPrefix'` sanitized np)+  filtered   = \np -> filter (onPath np) patterns+  onPath     = \np (p, _) -> p `L.isPrefixOf` sanitized np+  check2     = \np negated (prefix, ptns) ->+    any (`matchesPattern` asRepoPath np prefix)+      . filter (\p -> pIsNegated p == negated)+      $ ptns+++-- | Checks whether given directory path is valid /GIT/ repository.+isGitRepo :: MonadIO m+          => FilePath+          -- ^ path to the directory to check+          -> m Bool+          -- ^ @True@ if the given directory is valid /GIT/ repository+isGitRepo path = liftIO . doesDirectoryExist $ path </> ".git"+++------------------------------  PRIVATE FUNCTIONS  -----------------------------++addPrefix :: String -> String -> String+addPrefix prefix str | prefix `L.isPrefixOf` str = str+                     | otherwise                 = prefix <> str+++stripPrefix' :: String -> String -> String+stripPrefix' prefix str =+  maybe str T.unpack (T.stripPrefix (T.pack prefix) (T.pack str))+++stripSuffix' :: String -> String -> String+stripSuffix' suffix str =+  maybe str T.unpack (T.stripSuffix (T.pack suffix) (T.pack str))+++normalize :: MonadIO m => FilePath -> FilePath -> m FilePath+normalize repoDir path = do+  canonicalized <- liftIO . canonicalizePath $ repoDir </> stripPrefix' "/" path+  isDir         <- liftIO $ doesDirectoryExist canonicalized+  let suffix = if isDir || "/" `L.isSuffixOf` path then "/" else ""+  pure $ makeRelative repoDir canonicalized <> suffix+
+ src/Data/VCS/Ignore/Types.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE StrictData                #-}++{-|+Module      : Data.VCS.Ignore.Types+Description : Shared data types+Copyright   : (c) 2020-2021 Vaclav Svejcar+License     : BSD-3-Clause+Maintainer  : vaclav.svejcar@gmail.com+Stability   : experimental+Portability : POSIX++This module contains data types and functions shared across the library.+-}++module Data.VCS.Ignore.Types+  ( VCSIgnoreError(..)+  , fromVCSIgnoreError+  , toVCSIgnoreError+  )+where++import           Control.Exception              ( Exception(..)+                                                , SomeException+                                                )+import           Data.Typeable                  ( cast )+++---------------------------------  DATA TYPES  ---------------------------------++-- | Top-level of any exception thrown by this library.+data VCSIgnoreError = forall e . Exception e => VCSIgnoreError e++instance Show VCSIgnoreError where+  show (VCSIgnoreError e) = show e++instance Exception VCSIgnoreError where+  displayException (VCSIgnoreError e) = displayException e+++------------------------------  PUBLIC FUNCTIONS  ------------------------------++-- | Unwraps given exception from 'VCSIgnoreError'.+fromVCSIgnoreError :: Exception e+                   => SomeException+                   -- ^ exception to unwrap+                   -> Maybe e+                   -- ^ unwrapped exception+fromVCSIgnoreError e = do+  VCSIgnoreError e' <- fromException e+  cast e'+++-- | Wraps given exception from 'VCSIgnoreError'.+toVCSIgnoreError :: Exception e+                 => e+                 -- ^ exception to wrap+                 -> SomeException+                 -- ^ wrapped exception+toVCSIgnoreError = toException . VCSIgnoreError
+ test-data/fake-git-repo/.gitignore view
@@ -0,0 +1,1 @@+foo
+ test-data/fake-git-repo/a/.gitignore view
@@ -0,0 +1,3 @@+# some comment++**/*.xml
+ test-data/fake-git-repo/a/b/.gitignore view
@@ -0,0 +1,1 @@+*.txt
+ test-data/fake-git-repo/a/b/test-b.txt view
+ test-data/fake-git-repo/a/b/test-b.xml view
+ test-data/fake-git-repo/a/test-a.txt view
+ test-data/fake-git-repo/a/test-a.xml view
+ test-data/list-files/a.txt view
+ test-data/list-files/dir1/b.txt view
+ test-data/list-files/dir1/dir2/c.txt view
+ test-data/list-files/dir1/dir2/d.xml view
+ test/Data/VCS/Ignore/CoreSpec.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE StrictData        #-}+{-# LANGUAGE TypeApplications  #-}++module Data.VCS.Ignore.CoreSpec+  ( spec+  )+where++import           Control.Monad.Catch            ( throwM )+import qualified Data.List                     as L+import           Data.VCS.Ignore.Core++import           Data.VCS.Ignore.Repo           ( Repo(..)+                                                , RepoError(..)+                                                )+import           System.FilePath                ( (</>) )+import           Test.Hspec+++spec :: Spec+spec = do++  describe "findRepo" $ do+    it "finds repo for some path inside repo" $ do+      let path     = testRepoRoot </> "a" </> "b"+          expected = TestRepo testRepoRoot+      findRepo path `shouldReturn` Just expected++    it "finds no repo for path outside repo" $ do+      let path = "some" </> "path"+      findRepo path `shouldReturn` Nothing @TestRepo+++  describe "listRepo" $ do+    it "lists repository paths, based on the search filter" $ do+      let expected =+            [ ".gitignore"+            , "a"+            , "a" </> ".gitignore"+            , "a" </> "b"+            , "a" </> "b" </> ".gitignore"+            , "a" </> "b" </> "test-b.txt"+            , "a" </> "b" </> "test-b.xml"+            , "a" </> "test-a.txt"+            , "a" </> "test-a.xml"+            ]+      repo   <- scanRepo @TestRepo testRepoRoot+      result <- listRepo repo+      L.sort result `shouldBe` L.sort expected+++  describe "walkRepo" $ do+    it "walks repository paths, based on the search filter" $ do+      let fn = \path -> pure ("foo" </> path)+          expected =+            [ "foo" </> ".gitignore"+            , "foo" </> "a"+            , "foo" </> "a" </> ".gitignore"+            , "foo" </> "a" </> "b"+            , "foo" </> "a" </> "b" </> ".gitignore"+            , "foo" </> "a" </> "b" </> "test-b.txt"+            , "foo" </> "a" </> "b" </> "test-b.xml"+            , "foo" </> "a" </> "test-a.txt"+            , "foo" </> "a" </> "test-a.xml"+            ]+      repo   <- scanRepo @TestRepo testRepoRoot+      result <- walkRepo repo fn+      L.sort result `shouldBe` L.sort expected+++data TestRepo = TestRepo+  { trPath :: FilePath+  }+  deriving (Eq, Show)++instance Repo TestRepo where+  repoName = const "TestRepo"+  repoRoot TestRepo {..} = trPath+  scanRepo path | path == testRepoRoot = pure TestRepo { trPath = path }+                | otherwise            = throwM $ InvalidRepo path "err"+  isIgnored _ path = pure $ "excluded.txt" `L.isSuffixOf` path+++testRepoRoot :: FilePath+testRepoRoot = "test-data" </> "fake-git-repo"
+ test/Data/VCS/Ignore/FileSystemSpec.hs view
@@ -0,0 +1,65 @@+module Data.VCS.Ignore.FileSystemSpec+  ( spec+  )+where++import qualified Data.List                     as L+import           Data.Maybe                     ( catMaybes )+import           Data.VCS.Ignore.FileSystem+import           System.FilePath                ( (</>) )+import           Test.Hspec+++spec :: Spec+spec = do+  describe "findPaths" $ do+    it "recursively finds paths filtered by given predicate" $ do+      let expected =+            ["test-data" </> "list-files" </> "dir1" </> "dir2" </> "d.xml"]+          predicate = pure <$> ("d.xml" `L.isSuffixOf`)+      result <- findPaths ("test-data" </> "list-files") predicate+      result `shouldBe` expected+++  describe "listPaths" $ do+    it "returns empty list if path doesn't exist" $ do+      filePaths <- listPaths "non-existing-path"+      filePaths `shouldBe` []++    it "recursively finds all paths in directory" $ do+      result <- listPaths $ "test-data" </> "list-files"+      let expected =+            [ "test-data" </> "list-files"+            , "test-data" </> "list-files" </> "a.txt"+            , "test-data" </> "list-files" </> "dir1"+            , "test-data" </> "list-files" </> "dir1" </> "b.txt"+            , "test-data" </> "list-files" </> "dir1" </> "dir2"+            , "test-data" </> "list-files" </> "dir1" </> "dir2" </> "c.txt"+            , "test-data" </> "list-files" </> "dir1" </> "dir2" </> "d.xml"+            ]+      L.sort result `shouldBe` L.sort expected+++  describe "walkPaths" $ do+    it "recursively traverses and processes paths in directory" $ do+      let+        fn path =+          if ".txt" `L.isSuffixOf` path then pure $ Just path else pure Nothing+        expected =+          [ "test-data" </> "list-files" </> "a.txt"+          , "test-data" </> "list-files" </> "dir1" </> "b.txt"+          , "test-data" </> "list-files" </> "dir1" </> "dir2" </> "c.txt"+          ]+      actual <- catMaybes <$> walkPaths ("test-data" </> "list-files") fn+      L.sort actual `shouldBe` L.sort expected+++  describe "toPosixPath" $ do+    it "replaces any backward slashes with forward ones" $ do+      let sample   = "foo\\bar\\x.txt"+          expected = "foo/bar/x.txt"+      toPosixPath sample `shouldBe` expected++    it "keep forwars slashes as is" $ do+      let sample = "foo/bar/x.txt"+      toPosixPath sample `shouldBe` sample
+ test/Data/VCS/Ignore/Repo/GitSpec.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.VCS.Ignore.Repo.GitSpec where++import qualified Data.List                     as L+import qualified Data.Text                     as T+import           Data.VCS.Ignore.Repo           ( RepoError(..) )+import           Data.VCS.Ignore.Repo.Git+import           System.Directory               ( makeAbsolute )+import           System.FilePath                ( (</>) )+import           Test.Hspec+++spec :: Spec+spec = do+  let repo = "test-data" </> "fake-git-repo"++  describe "compilePattern" $ do+    it "compiles negated pattern" $ do+      compilePattern "!/foo/bar"+        `shouldBe` Pattern ["/foo/bar", "/foo/bar/**"] "!/foo/bar" True++    it "compiles pattern matching files or directories inside path" $ do+      compilePattern ".hidden"+        `shouldBe` Pattern ["**/.hidden", "**/.hidden/**"] ".hidden" False+      compilePattern ".hidden/"+        `shouldBe` Pattern ["**/.hidden/**"] ".hidden/" False+++  describe "matchesPattern" $ do+    it "matches all content in subdirectory" $ do+      matchesPattern (compilePattern "/foo/*") "/foo" `shouldBe` False+      matchesPattern (compilePattern "/foo/*") "/foo/bar" `shouldBe` True++    it "matches all content in all subdirectories" $ do+      matchesPattern (compilePattern "/foo/**") "/foo/bar" `shouldBe` True+      matchesPattern (compilePattern "/foo/**") "/foo/bar/c" `shouldBe` True++    it "matches negated pattern" $ do+      matchesPattern (compilePattern "!/foo/*") "/foo" `shouldBe` False+      matchesPattern (compilePattern "!/foo/*") "/foo/bar" `shouldBe` True++    it "matches dir pattern anywhere in the path" $ do+      matchesPattern (compilePattern "bar/") "/foo/bar/x" `shouldBe` True+      matchesPattern (compilePattern "bar/") "/foo/bar" `shouldBe` False++    it "matches dir or file pattern anywhere in the path" $ do+      matchesPattern (compilePattern "bar") "/foo/bar/x" `shouldBe` True+      matchesPattern (compilePattern "bar") "/foo/bar" `shouldBe` True+++  describe "parsePatterns" $ do+    it "parses glob patterns from input text (pattern per line)" $ do+      let input    = T.unlines [".cabal-sandbox/", "## comment", ".DS_Store"]+          expected = [".cabal-sandbox/", ".DS_Store"]+      parsePatterns input `shouldBe` expected+++  describe "loadPatterns" $ do+    it "loads and parses glob patterns from input file" $ do+      let source   = repo </> "a" </> ".gitignore"+          expected = ["**/*.xml"]+      loadPatterns source `shouldReturn` expected++    it "returns empty list if input cannot be read" $ do+      let source = repo </> "non-existing"+      loadPatterns source `shouldReturn` []+++  describe "findGitIgnores" $ do+    it "finds all .gitignore files in repo" $ do+      let expected =+            [ repo </> "a" </> ".gitignore"+            , repo </> "a" </> "b" </> ".gitignore"+            , repo </> ".gitignore"+            ]+      L.sort <$> findGitIgnores repo `shouldReturn` L.sort expected+++  describe "gitIgnorePatterns" $ do+    it "loads patterns for all .gitignore files in repo" $ do+      let expected =+            [("/a/", ["**/*.xml"]), ("/a/b/", ["*.txt"]), ("/", ["foo"])]+      sortFst <$> gitIgnorePatterns repo `shouldReturn` sortFst expected+++  describe "scanRepo'" $ do+    it "scans repository for ignored patterns" $ do+      absRepo <- makeAbsolute repo+      let+        fn1      = pure []+        fn2      = const $ pure []+        fn3      = const $ pure True+        expected = Git+          { gitPatterns = sortFst+            [("/", ["foo"]), ("/a/", ["**/*.xml"]), ("/a/b/", ["*.txt"])]+          , gitRepoRoot = absRepo+          }+      result <- scanRepo' fn1 fn2 gitIgnorePatterns fn3 repo+      let result' = result { gitPatterns = sortFst (gitPatterns result) }+      result' `shouldBe` expected++    it "aborts scanning if given path is not valid GIT repo" $ do+      let fn1 = pure []+          fn2 = const $ pure []+          fn3 = const $ pure False+      let err (InvalidRepo _ _) = True+      scanRepo' fn1 fn2 gitIgnorePatterns fn3 repo `shouldThrow` err+++  describe "isIgnored'" $ do+    it "checks whether given path is excluded" $ do+      absRepo <- makeAbsolute repo+      let git = Git+            { gitPatterns = [ ("/"    , ["!keep.xml", ".hidden/", ".hid"])+                            , ("/a/"  , ["**/*.xml"])+                            , ("/a/b/", ["*.txt"])+                            ]+            , gitRepoRoot = absRepo+            }+      isIgnored' git "foo/bar" `shouldReturn` False+      isIgnored' git "a/hello.txt" `shouldReturn` False+      isIgnored' git "a/hello.xml" `shouldReturn` True+      isIgnored' git "a/b/hello.xml" `shouldReturn` True+      isIgnored' git "a/b/keep.xml" `shouldReturn` False+      isIgnored' git "/foo/bar" `shouldReturn` False+      isIgnored' git "/a/hello.txt" `shouldReturn` False+      isIgnored' git "/a/hello.xml" `shouldReturn` True+      isIgnored' git "/a/b/hello.xml" `shouldReturn` True+      isIgnored' git "/a/b/../hello.txt" `shouldReturn` False+      isIgnored' git "/a/b/../hello.xml" `shouldReturn` True+      isIgnored' git "/a/b/.hidden" `shouldReturn` False+      isIgnored' git "/a/b/.hidden/foo" `shouldReturn` True+      isIgnored' git "/a/b/.hid" `shouldReturn` True+      isIgnored' git "/a/b/.hid/foo" `shouldReturn` True+++sortFst :: Ord a => [(a, b)] -> [(a, b)]+sortFst = L.sortOn fst
+ test/Spec.hs view
@@ -0,0 +1,2 @@+-- hspec test auto discovery - https://hspec.github.io/hspec-discover.html+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ vcs-ignore.cabal view
@@ -0,0 +1,124 @@+cabal-version: 2.2++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           vcs-ignore+version:        0.0.1.0+synopsis:       Library for handling files ignored by VCS systems.+description:    vcs-ignore is small Haskell library used to find, check and process files ignored by selected VCS.+category:       Development+homepage:       https://github.com/vaclavsvejcar/vcs-ignore+bug-reports:    https://github.com/vaclavsvejcar/vcs-ignore/issues+author:         Vaclav Svejcar+maintainer:     vaclav.svejcar@gmail.com+copyright:      Copyright (c) 2020-2021 Vaclav Svejcar+license:        BSD-3-Clause+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    CHANGELOG.md+    test-data/fake-git-repo/.gitignore+    test-data/fake-git-repo/a/test-a.txt+    test-data/fake-git-repo/a/test-a.xml+    test-data/fake-git-repo/a/.gitignore+    test-data/fake-git-repo/a/b/test-b.txt+    test-data/fake-git-repo/a/b/test-b.xml+    test-data/fake-git-repo/a/b/.gitignore+    test-data/list-files/a.txt+    test-data/list-files/dir1/b.txt+    test-data/list-files/dir1/dir2/c.txt+    test-data/list-files/dir1/dir2/d.xml++source-repository head+  type: git+  location: https://github.com/vaclavsvejcar/vcs-ignore++library+  exposed-modules:+      Data.VCS.Ignore+      Data.VCS.Ignore.Core+      Data.VCS.Ignore.FileSystem+      Data.VCS.Ignore.Repo+      Data.VCS.Ignore.Repo.Git+      Data.VCS.Ignore.Types+  other-modules:+      Paths_vcs_ignore+  autogen-modules:+      Paths_vcs_ignore+  hs-source-dirs:+      src+  ghc-options: -optP-Wno-nonportable-include-path -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -Werror=incomplete-patterns+  build-depends:+      Glob+    , base >=4.7 && <5+    , containers+    , directory+    , exceptions+    , filepath+    , text+  default-language: Haskell2010++executable ignore+  main-is: Main.hs+  other-modules:+      Main.Options+      Main.Vendor+      Paths_vcs_ignore+  hs-source-dirs:+      app+  ghc-options: -optP-Wno-nonportable-include-path -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -Werror=incomplete-patterns -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , containers+    , directory+    , exceptions+    , filepath+    , optparse-applicative+    , text+    , vcs-ignore+  default-language: Haskell2010++test-suite doctest+  type: exitcode-stdio-1.0+  main-is: Main.hs+  other-modules:+      Paths_vcs_ignore+  hs-source-dirs:+      doctest+  ghc-options: -optP-Wno-nonportable-include-path -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -Werror=incomplete-patterns+  build-depends:+      base >=4.7 && <5+    , containers+    , directory+    , doctest+    , exceptions+    , filepath+    , text+  default-language: Haskell2010++test-suite spec+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Data.VCS.Ignore.CoreSpec+      Data.VCS.Ignore.FileSystemSpec+      Data.VCS.Ignore.Repo.GitSpec+      Paths_vcs_ignore+  hs-source-dirs:+      test+  ghc-options: -optP-Wno-nonportable-include-path -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -Werror=incomplete-patterns -threaded -rtsopts -with-rtsopts=-N+  build-tool-depends:+      hspec-discover:hspec-discover+  build-depends:+      base >=4.7 && <5+    , containers+    , directory+    , exceptions+    , filepath+    , hspec+    , text+    , vcs-ignore+  default-language: Haskell2010