packages feed

gitignore-ospath-0.1.0: test/IgnoreSpec.hs

module Main (main) where

import Data.Text (Text)
import qualified Data.Text as T
import Ignore
import Ignore.Internal
import System.OsString (OsChar, OsString, unsafeEncodeUtf, unsafeFromChar)
import Test.Hspec

os :: String -> OsString
os = unsafeEncodeUtf

osc :: Char -> OsChar
osc = unsafeFromChar

main :: IO ()
main = hspec spec

path :: String -> Segment
path p = Literal (os p)

parseln :: [Text] -> Ignore
parseln patterns = parse (T.intercalate "\n" patterns)

pat :: [Segment] -> Bool -> Bool -> Bool -> Pattern
pat segs dir negated anchored =
  Pattern
    { pSegments = segs,
      pDir = dir,
      pNegated = negated,
      pAnchored = anchored
    }

spec :: Spec
spec = do
  describe "parse" $ do
    it "parses an ignore correctly" $ do
      parse "" `shouldBe` Ignore []
      parse "\n  \n\t\t\n \n" `shouldBe` Ignore []
      parse "# test" `shouldBe` Ignore []
      parse "  # test " `shouldBe` Ignore []
      parse "  # test\n#test" `shouldBe` Ignore []
      parse "path" `shouldBe` Ignore [pat [path "path"] False False False]
      parse "!path" `shouldBe` Ignore [pat [path "path"] False True False]
      parse "!!path" `shouldBe` Ignore [pat [path "!path"] False True False]
      parse "path/" `shouldBe` Ignore [pat [path "path"] True False False]
      parse "path///" `shouldBe` Ignore [pat [path "path"] True False False]
      parse "/path/" `shouldBe` Ignore [pat [path "path"] True False True]
      parse "/path/foo/" `shouldBe` Ignore [pat [path "path", path "foo"] True False True]
      parse "/path///foo/" `shouldBe` Ignore [pat [path "path", path "foo"] True False True]
      parse "**/path/" `shouldBe` Ignore [pat [DAsterisk, path "path"] True False True]
      parse "/path/**" `shouldBe` Ignore [pat [path "path", DAsterisk] False False True]
      parse "/path/**/" `shouldBe` Ignore [pat [path "path", DAsterisk] True False True]
      parse "a?" `shouldBe` Ignore [pat [Glob [Single (osc 'a'), Wildcard False]] False False False]
      parse "*a*" `shouldBe` Ignore [pat [Glob [Wildcard True, Single (osc 'a'), Wildcard True]] False False False]
      parse "a]" `shouldBe` Ignore [pat [Glob [Single (osc 'a'), Single (osc ']')]] False False False]
      parse "\\a" `shouldBe` Ignore [pat [Glob [Single (osc 'a')]] False False False]
      parse "a\\?" `shouldBe` Ignore [pat [Glob [Single (osc 'a'), Single (osc '?')]] False False False]
      parse "[ab]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'a'), ClassSingle (osc 'b')]]] False False False]
      parse "[a\\b]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'a'), ClassSingle (osc 'b')]]] False False False]
      parse "[a\\]]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'a'), ClassSingle (osc ']')]]] False False False]
      parse "[-]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc '-')]]] False False False]
      parse "[--]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc '-', osc '-')]]] False False False]
      parse "[---]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc '-')]]] False False False]
      parse "[a-]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc 'a', osc 'a')]]] False False False]
      parse "[a-z]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc 'a', osc 'z')]]] False False False]
      parse "[a--z]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'a'), ClassSingle (osc 'z')]]] False False False]
      parse "[1a-z]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc '1'), ClassRange (osc 'a', osc 'z')]]] False False False]
      parse "[a-z2]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc 'a', osc 'z'), ClassSingle (osc '2')]]] False False False]
      parse "[a-zA-Z]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc 'a', osc 'z'), ClassRange (osc 'A', osc 'Z')]]] False False False]
      parse "[-a-z]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc '-'), ClassRange (osc 'a', osc 'z')]]] False False False]
      parse "[a-z-]" `shouldBe` Ignore [pat [Glob [Class [ClassRange (osc 'a', osc 'z'), ClassSingle (osc '-')]]] False False False]
      parse "[z-a]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'z')]]] False False False]
      parse "[z-z]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'z')]]] False False False]
      parse "[z-a-m]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc 'z'), ClassSingle (osc '-'), ClassSingle (osc 'm')]]] False False False]
      parse "[]]" `shouldBe` Ignore [pat [Glob [Single (osc ']')]] False False False]
      parse "[[]" `shouldBe` Ignore [pat [Glob [Class [ClassSingle (osc '[')]]] False False False]
      parse "a[" `shouldBe` Ignore [] -- Unclosed class
      parse "a[b" `shouldBe` Ignore [] -- Unclosed class
      parse "[b[" `shouldBe` Ignore [] -- Unclosed class
      parse "[]" `shouldBe` Ignore [] -- Empty
      parse "[a\\]" `shouldBe` Ignore [] -- Unclosed class
      parse "\\" `shouldBe` Ignore []

  describe "ignores" $ do
    it "ignores paths correctly (single pattern)" $ do
      ignores (parse "") (os "") False `shouldBe` False
      ignores (parse "") (os "foo") False `shouldBe` False
      ignores (parse "!") (os "foo") False `shouldBe` False
      ignores (parse "!*") (os "foo") False `shouldBe` False
      ignores (parse "bar") (os "foo") False `shouldBe` False
      ignores (parse "!bar") (os "foo") False `shouldBe` False
      ignores (parse "foo") (os "foo") False `shouldBe` True
      ignores (parse "/") (os "foo") False `shouldBe` False
      ignores (parse "/") (os "foo") True `shouldBe` False
      ignores (parse "/") (os "") False `shouldBe` False
      ignores (parse "!foo") (os "foo") False `shouldBe` False
      ignores (parse "!!foo") (os "foo") False `shouldBe` False
      ignores (parse "foo") (os "foo") True `shouldBe` True
      ignores (parse "foo/") (os "foo") False `shouldBe` False
      ignores (parse "foo/") (os "foo") True `shouldBe` True
      ignores (parse "/foo") (os "foo") False `shouldBe` True
      ignores (parse "foo/bar") (os "foo/bar") False `shouldBe` True
      ignores (parse "foo/bar") (os "foo/baz") False `shouldBe` False
      ignores (parse "/foo/bar") (os "foo/bar") False `shouldBe` True
      ignores (parse "/foo/bar") (os "baz/foo/bar") False `shouldBe` False
      ignores (parse "bar") (os "foo/bar") False `shouldBe` True
      ignores (parse "bar/") (os "foo/bar") True `shouldBe` True
      ignores (parse "/bar") (os "foo/bar") False `shouldBe` False
      ignores (parse "foo/bar") (os "baz/foo/bar") False `shouldBe` False
      ignores (parse "*") (os "foo") False `shouldBe` True
      ignores (parse "*") (os "foo") True `shouldBe` True
      ignores (parse "*") (os "foo/bar/baz") False `shouldBe` True
      ignores (parse "/*") (os "foo") False `shouldBe` True
      ignores (parse "/*") (os "foo/bar") False `shouldBe` False
      ignores (parse "*/*") (os "foo/bar") False `shouldBe` True
      ignores (parse "*/*") (os "foo/bar/baz") False `shouldBe` False
      ignores (parse "*/*") (os "foo") False `shouldBe` False
      ignores (parse "foo/bar") (os "foo/x/bar") False `shouldBe` False
      ignores (parse "foo/*/bar") (os "foo/x/bar") False `shouldBe` True
      ignores (parse "foo/*/bar") (os "foo/x/baz") False `shouldBe` False
      ignores (parse "foo/*/bar") (os "foo/x") False `shouldBe` False
      ignores (parse "**") (os "foo") False `shouldBe` True
      ignores (parse "**") (os "foo/bar/baz") False `shouldBe` True
      ignores (parse "**/bar") (os "x/foo/bar") False `shouldBe` True
      ignores (parse "**/foo/bar") (os "x/foo/bar") False `shouldBe` True
      ignores (parse "**/foo/baz") (os "x/foo/bar") False `shouldBe` False
      ignores (parse "bar/**") (os "bar/a") False `shouldBe` True
      ignores (parse "bar/***") (os "bar/a") False `shouldBe` True
      ignores (parse "*/**") (os "bar/a") False `shouldBe` True
      ignores (parse "bar/**") (os "bar") False `shouldBe` False
      ignores (parse "bar/**/x") (os "bar/x") False `shouldBe` True
      ignores (parse "bar/**/x") (os "bar/1/2/3/x") False `shouldBe` True
      ignores (parse "bar/**/x/") (os "bar/1/2/3/x") False `shouldBe` False
      ignores (parse "bar/**/x/") (os "bar/1/2/3/x") True `shouldBe` True
      ignores (parse "bar/***/x/") (os "bar/1/2/3/x") True `shouldBe` True
      ignores (parse "bar/**/") (os "bar/test") True `shouldBe` True
      ignores (parse "bar/**/") (os "bar/test") False `shouldBe` False
      ignores (parse "**/x") (os "foo") False `shouldBe` False
      ignores (parse "**/**") (os "foo") False `shouldBe` True
      ignores (parse "**/**") (os "foo/bar") False `shouldBe` True
      ignores (parse "!**/foo") (os "bar/foo") False `shouldBe` False
      ignores (parse "a/**/b/**/c") (os "a/x/b/y/c") False `shouldBe` True
      ignores (parse "a/**/m/**/c") (os "a/x/b/y/c") False `shouldBe` False
      ignores (parse "/**") (os "foo") False `shouldBe` True
      ignores (parse "/**") (os "foo/yyy") False `shouldBe` True
      ignores (parse "/**/foo") (os "foo") False `shouldBe` True
      ignores (parse "/**/foo") (os "x/foo") False `shouldBe` True
      ignores (parse "/**/foo") (os "x/y/foo") False `shouldBe` True
      ignores (parse "/***/foo") (os "x/y/foo") False `shouldBe` True
      ignores (parse "!foo/") (os "foo") True `shouldBe` False
      ignores (parse "!foo") (os "foo") True `shouldBe` False
      ignores (parse ".*") (os "foo") False `shouldBe` False
      ignores (parse ".*") (os ".foo") False `shouldBe` True
      ignores (parse ".*") (os "bar/.foo") False `shouldBe` True
      ignores (parse ".*") (os "bar/.") False `shouldBe` True
      ignores (parse "*.py") (os "test.py") False `shouldBe` True
      ignores (parse "*.py") (os "test.c") False `shouldBe` False
      ignores (parse "*.py") (os "foo/test.py") False `shouldBe` True
      ignores (parse "foo/*.py") (os "test.py") False `shouldBe` False
      ignores (parse "foo/*.py") (os "foo/test.py") False `shouldBe` True
      ignores (parse "**/*.py") (os "a/b/foo/test.py") False `shouldBe` True
      ignores (parse "**/*.py") (os "test.py") False `shouldBe` True
      ignores (parse "**/") (os "test") True `shouldBe` True
      ignores (parse "**/") (os "test/dir/foo") True `shouldBe` True
      ignores (parse "**/") (os "test/file") False `shouldBe` False
      ignores (parse "/*/") (os "test") True `shouldBe` True
      ignores (parse "/*/") (os "test/foo") True `shouldBe` False
      ignores (parse "**/*.py") (os "zzz") False `shouldBe` False
      ignores (parse "!**/*.py") (os "zzz") False `shouldBe` False
      ignores (parse "/baz/**") (os "a/baz/foo") False `shouldBe` False
      -- Globs
      ignores (parse "??") (os "ab") False `shouldBe` True
      ignores (parse "??") (os "abc") False `shouldBe` False
      ignores (parse "?ab*") (os "aabcd") False `shouldBe` True
      ignores (parse "a*c") (os "abc") False `shouldBe` True
      ignores (parse "a*c") (os "ac") False `shouldBe` True
      ignores (parse "a*cb") (os "acb") False `shouldBe` True
      ignores (parse "a*c") (os "abx") False `shouldBe` False
      ignores (parse "foo]") (os "foo]") False `shouldBe` True
      ignores (parse "\\?") (os "?") False `shouldBe` True
      ignores (parse "[]") (os "a") False `shouldBe` False
      ignores (parse "[-]") (os "-") False `shouldBe` True
      ignores (parse "[ab]") (os "a") False `shouldBe` True
      ignores (parse "[ab]") (os "c") False `shouldBe` False
      ignores (parse "[a-z]") (os "c") False `shouldBe` True
      ignores (parse "[a-z]") (os "0") False `shouldBe` False
      ignores (parse "[a-zA-Z]") (os "A") False `shouldBe` True
      ignores (parse "[z-a]") (os "z") False `shouldBe` True
      ignores (parse "*.?[a-z][-]\\?\\**") (os "abc.xa-?*-") False `shouldBe` True
      ignores (parse "*.?[a-z][-]\\?\\**") (os "abcxa-?*-") False `shouldBe` False

      -- From https://git-scm.com/docs/gitignore:
      ignores (parse "doc/frotz/") (os "doc/frotz") True `shouldBe` True
      ignores (parse "doc/frotz/") (os "a/doc/frotz") True `shouldBe` False
      ignores (parse "frotz/") (os "frotz") True `shouldBe` True
      ignores (parse "frotz/") (os "a/frotz") True `shouldBe` True
      ignores (parse "**/foo") (os "a/foo") False `shouldBe` True
      ignores (parse "**/foo/bar") (os "a/b/foo/bar") False `shouldBe` True
      ignores (parse "abc/**") (os "abc/foo/bar") False `shouldBe` True
      ignores (parse "a/**/b") (os "a/b") False `shouldBe` True
      ignores (parse "a/**/b") (os "a/x/b") False `shouldBe` True
      ignores (parse "a/**/b") (os "a/x/y/b") False `shouldBe` True
      ignores (parse "foo/*") (os "foo/test.json") False `shouldBe` True
      ignores (parse "foo/*") (os "foo/bar") True `shouldBe` True
      ignores (parse "foo/*") (os "foo/bar/hello.c") False `shouldBe` False

    it "ignores paths correctly (multiple patterns)" $ do
      ignores (parseln ["foo", "!foo"]) (os "foo") False `shouldBe` False
      ignores (parseln ["!foo", "!!foo"]) (os "foo") False `shouldBe` False
      ignores (parseln ["*", "!foo"]) (os "foo") False `shouldBe` False
      ignores (parseln ["*", "!*"]) (os "foo") False `shouldBe` False
      ignores (parseln ["*", "!bar"]) (os "foo") False `shouldBe` True
      ignores (parseln ["*", "!foo", "foo"]) (os "foo") False `shouldBe` True
      ignores (parseln ["test/**", "!test/foo"]) (os "test/foo") False `shouldBe` False
      ignores (parseln ["*.py", "!test.py"]) (os "test.py") False `shouldBe` False
      ignores (parseln ["*", "!*/"]) (os "foo/bar") True `shouldBe` False
      ignores (parseln ["*", "!*/"]) (os "foo/bar") False `shouldBe` True
      ignores (parseln ["*", "!*/", "!**/*.py"]) (os "bar.py") False `shouldBe` False
      ignores (parseln ["*", "!*/", "!**/*.py"]) (os "foo/bar.py") False `shouldBe` False
      ignores (parseln ["*", "!*/", "!*.py"]) (os "foo/bar.py") False `shouldBe` False