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 +5/−0
- LICENSE +29/−0
- README.md +65/−0
- Setup.hs +2/−0
- app/Main.hs +78/−0
- app/Main/Options.hs +50/−0
- app/Main/Vendor.hs +50/−0
- doctest/Main.hs +6/−0
- src/Data/VCS/Ignore.hs +89/−0
- src/Data/VCS/Ignore/Core.hs +88/−0
- src/Data/VCS/Ignore/FileSystem.hs +109/−0
- src/Data/VCS/Ignore/Repo.hs +84/−0
- src/Data/VCS/Ignore/Repo/Git.hs +297/−0
- src/Data/VCS/Ignore/Types.hs +60/−0
- test-data/fake-git-repo/.gitignore +1/−0
- test-data/fake-git-repo/a/.gitignore +3/−0
- test-data/fake-git-repo/a/b/.gitignore +1/−0
- test-data/fake-git-repo/a/b/test-b.txt +0/−0
- test-data/fake-git-repo/a/b/test-b.xml +0/−0
- test-data/fake-git-repo/a/test-a.txt +0/−0
- test-data/fake-git-repo/a/test-a.xml +0/−0
- test-data/list-files/a.txt +0/−0
- test-data/list-files/dir1/b.txt +0/−0
- test-data/list-files/dir1/dir2/c.txt +0/−0
- test-data/list-files/dir1/dir2/d.xml +0/−0
- test/Data/VCS/Ignore/CoreSpec.hs +87/−0
- test/Data/VCS/Ignore/FileSystemSpec.hs +65/−0
- test/Data/VCS/Ignore/Repo/GitSpec.hs +139/−0
- test/Spec.hs +2/−0
- vcs-ignore.cabal +124/−0
+ 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