packages feed

Glob 0.3.2 → 0.4

raw patch · 21 files changed

+1237/−433 lines, 21 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- System.FilePath.Glob: tryCompile :: String -> Either String Pattern
+ System.FilePath.Glob: CompOptions :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> CompOptions
+ System.FilePath.Glob: MatchOptions :: Bool -> Bool -> Bool -> MatchOptions
+ System.FilePath.Glob: characterClasses :: CompOptions -> Bool
+ System.FilePath.Glob: characterRanges :: CompOptions -> Bool
+ System.FilePath.Glob: commonDirectory :: Pattern -> (FilePath, Pattern)
+ System.FilePath.Glob: compDefault :: CompOptions
+ System.FilePath.Glob: compPosix :: CompOptions
+ System.FilePath.Glob: compileWith :: CompOptions -> String -> Pattern
+ System.FilePath.Glob: data CompOptions
+ System.FilePath.Glob: data MatchOptions
+ System.FilePath.Glob: decompile :: Pattern -> String
+ System.FilePath.Glob: errorRecovery :: CompOptions -> Bool
+ System.FilePath.Glob: globDirWith :: MatchOptions -> [Pattern] -> FilePath -> IO ([[FilePath]], [FilePath])
+ System.FilePath.Glob: ignoreCase :: MatchOptions -> Bool
+ System.FilePath.Glob: ignoreDotSlash :: MatchOptions -> Bool
+ System.FilePath.Glob: matchDefault :: MatchOptions
+ System.FilePath.Glob: matchDotsImplicitly :: MatchOptions -> Bool
+ System.FilePath.Glob: matchPosix :: MatchOptions
+ System.FilePath.Glob: matchWith :: MatchOptions -> Pattern -> FilePath -> Bool
+ System.FilePath.Glob: numberRanges :: CompOptions -> Bool
+ System.FilePath.Glob: pathSepInRanges :: CompOptions -> Bool
+ System.FilePath.Glob: recursiveWildcards :: CompOptions -> Bool
+ System.FilePath.Glob: simplify :: Pattern -> Pattern
+ System.FilePath.Glob: tryCompileWith :: CompOptions -> String -> Either String Pattern
+ System.FilePath.Glob: wildcards :: CompOptions -> Bool
+ System.FilePath.Glob.Primitive: charRange :: Bool -> [Either Char (Char, Char)] -> Pattern
+ System.FilePath.Glob.Primitive: literal :: String -> Pattern
+ System.FilePath.Glob.Primitive: numberRange :: Maybe Integer -> Maybe Integer -> Pattern
+ System.FilePath.Glob.Primitive: recursiveWildcard :: Pattern
+ System.FilePath.Glob.Primitive: singleWildcard :: Pattern
+ System.FilePath.Glob.Primitive: wildcard :: Pattern

Files

CHANGELOG.txt view
@@ -1,3 +1,59 @@+0.4, 2009-01-31:+	New functions:+		System.FilePath.Glob.commonDirectory :: Pattern -> (FilePath, Pattern)++		System.FilePath.Glob.simplify  :: Pattern -> Pattern+		System.FilePath.Glob.decompile :: Pattern -> String++		System.FilePath.Glob.tryCompileWith :: CompOptions -> String -> Either String Pattern+		System.FilePath.Glob.compileWith    :: CompOptions -> String -> Pattern+		System.FilePath.Glob.compDefault    :: CompOptions+		System.FilePath.Glob.compPosix      :: CompOptions++		System.FilePath.Glob.matchWith     :: MatchOptions -> Pattern -> FilePath -> Bool+		System.FilePath.Glob.globDirWith   :: MatchOptions -> [Pattern] -> FilePath -> IO ([[FilePath]], [FilePath])+		System.FilePath.Glob.matchDefault  :: MatchOptions+		System.FilePath.Glob.matchPosix    :: MatchOptions++		System.FilePath.Glob.Primitives.literal           :: String -> Pattern+		System.FilePath.Glob.Primitives.singleWildcard    :: Pattern+		System.FilePath.Glob.Primitives.wildcard          :: Pattern+		System.FilePath.Glob.Primitives.recursiveWildcard :: Pattern+		System.FilePath.Glob.Primitives.charRange         :: Bool -> [Either Char (Char,Char)] -> Pattern+		System.FilePath.Glob.Primitives.numberRange       :: Maybe Integer -> Maybe Integer -> Pattern++	Removed functions:+		System.FilePath.Glob.tryCompile (no longer needed / superceded by tryCompileWith)++	New instances:+		Monoid Pattern+		Read Pattern++	Change: ".." can now be matched, by patterns such as ".*".+	Change: globDir, given "" as the directory, uses getCurrentDirectory.+	Change: globDir now keeps track of the number of path separators, thus+	        "a//*" will return "a//b" instead of "a/b" as a match result.+	Change: if character ranges begin with ! or ^, these characters are now+	        considered the start of the range: [^-~] is the range ^ through ~,+	        not the inverse of [-~].++	Regression fix: handle directories without read permissions even more+	                properly.++	Bug fix: globDir doesn't convert patterns like "a./b" to "ab".+	Bug fix: the Show instance used to show "[?]" as the very different "?" (and+	         a few similar cases).+	Bug fix: "//./" wasn't getting optimized properly.+	Bug fix: ".//a" matched "/a" and not "a" or "./a".+	Bug fix: "f**/a" didn't match "foo/a".+	Bug fix: ".**/a" didn't match ".foo/a".+	Bug fix: ".**/a" matched "../a".+	Bug fixes: globDir and match, in general, handled patterns starting with+	           ".*" or ".**/" quite differently.+	Bug fix: globDir never matched "foo/" to the directory "foo".+	Bug fix: globDir never matched "foo**/" to the directory "foo".+	Bug fix: show (compile "[a!b]") resulted in "[!ab]".+ 0.3.2, 2008-12-20: 	Regression fix: handle directories without read permissions properly. @@ -24,5 +80,5 @@ 		System.FilePath.Glob.compile    :: String -> Pattern 		System.FilePath.Glob.match      :: Pattern -> FilePath -> Bool 		System.FilePath.Glob.globDir    :: [Pattern] -> FilePath -> IO ([[FilePath]], [FilePath])-	+ 	Dependencies: base, containers, directory, filepath, mtl.
+ CREDITS.txt view
@@ -0,0 +1,4 @@+In alphabetical order by surname:++Stephen Hicks+Matti   Niemenmaa
Glob.cabal view
@@ -1,7 +1,7 @@ Cabal-Version: >= 1.6  Name:        Glob-Version:     0.3.2+Version:     0.4 Homepage:    http://iki.fi/matti.niemenmaa/glob/ Synopsis:    Globbing library Category:    System@@ -17,6 +17,7 @@ Build-Type: Simple  Extra-Source-Files: CHANGELOG.txt+                    CREDITS.txt                     tests/README.txt                     tests/*.hs                     tests/Tests/*.hs@@ -33,9 +34,9 @@       Build-Depends: Win32 == 2.*     Exposed-Modules: System.FilePath.Glob+                    System.FilePath.Glob.Primitive    Other-Modules:   System.FilePath.Glob.Base-                    System.FilePath.Glob.Compile                     System.FilePath.Glob.Directory                     System.FilePath.Glob.Match-                    System.FilePath.Glob.Optimize+                    System.FilePath.Glob.Simplify                     System.FilePath.Glob.Utils
LICENSE.txt view
@@ -1,4 +1,8 @@-Copyright (c) 2008, Matti Niemenmaa
+The code in Glob is released under the license below. Copyrights to parts of
+the code are held by whoever wrote the code in question: see CREDITS.txt for a
+list of authors.
+
+Copyright (c) 2008-2009 <authors>
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
System/FilePath/Glob.hs view
@@ -10,13 +10,32 @@      Pattern      -- * Functions      -- ** Compilation-   , tryCompile, compile+   , compile, decompile, simplify+     -- *** Options+   , CompOptions(..)+   , compileWith, tryCompileWith+     -- **** Predefined option sets+   , compDefault, compPosix      -- ** Matching    , match    , globDir+     -- *** Options+   , MatchOptions(..)+   , matchWith+   , globDirWith+     -- **** Predefined option sets+   , matchDefault, matchPosix+     -- ** Miscellaneous+   , commonDirectory    ) where -import System.FilePath.Glob.Base      (Pattern)-import System.FilePath.Glob.Compile   (compile, tryCompile)-import System.FilePath.Glob.Directory (globDir)-import System.FilePath.Glob.Match     (match)+import System.FilePath.Glob.Base      ( Pattern+                                      , CompOptions(..), MatchOptions(..)+                                      , compDefault, compPosix+                                      , matchDefault, matchPosix+                                      , compile, compileWith, tryCompileWith+                                      , decompile+                                      )+import System.FilePath.Glob.Directory (globDir, globDirWith, commonDirectory)+import System.FilePath.Glob.Match     (match, matchWith)+import System.FilePath.Glob.Simplify  (simplify)
System/FilePath/Glob/Base.hs view
@@ -1,12 +1,46 @@ -- File created: 2008-10-10 13:29:26 -module System.FilePath.Glob.Base where+{-# LANGUAGE CPP #-} -import Data.Maybe      (fromMaybe)-import System.FilePath (pathSeparator, extSeparator)+module System.FilePath.Glob.Base+   ( Token(..), Pattern(..) --- Todo? data Options = Options { allow_dots :: Bool }+   , CompOptions(..), MatchOptions(..)+   , compDefault, compPosix, matchDefault, matchPosix +   , decompile++   , compile+   , compileWith, tryCompileWith+   , tokenize -- for tests++   , optimize++   , liftP, tokToLower+   ) where++import Control.Arrow               (first)+import Control.Monad.Error         (ErrorT, runErrorT, throwError)+import Control.Monad.Writer.Strict (Writer, runWriter, tell)+import Control.Exception           (assert)+import Data.Char                   (isDigit, isAlpha, toLower)+import Data.List                   (find, sortBy)+import Data.Maybe                  (fromMaybe)+import Data.Monoid                 (Monoid, mappend, mempty, mconcat)+import System.FilePath             ( pathSeparator, extSeparator+                                   , isExtSeparator, isPathSeparator+                                   )++import System.FilePath.Glob.Utils ( dropLeadingZeroes+                                  , isLeft, fromLeft+                                  , increasingSeq+                                  , addToRange, overlap+                                  )++#if __GLASGOW_HASKELL__+import Text.Read (readPrec, lexP, parens, prec, Lexeme(Ident))+#endif+ data Token    -- primitives    = Literal !Char@@ -22,11 +56,32 @@    | LongLiteral !Int String    deriving (Eq) +-- Note: CharRanges aren't converted, because this is tricky in general.+-- Consider for instance [@-[], which includes the range A-Z. This would need+-- to become [@[a-z]: so essentially we'd need to either:+--+--    1) Have a list of ranges of uppercase Unicode. Check if our range+--       overlaps with any of them and if it does, take the non-overlapping+--       part and combine it with the toLower of the overlapping part.+--+--    2) Simply expand the entire range to a list and map toLower over it.+--+-- In either case we'd need to re-optimize the CharRange—we can't assume that+-- if the uppercase characters are consecutive, so are the lowercase.+--+-- 1) might be feasible if someone bothered to get the latest data.+--+-- 2) obviously isn't since you might have 'Right (minBound, maxBound)' in+-- there somewhere.+--+-- The current solution is to just check both the toUpper of the character and+-- the toLower.+tokToLower :: Token -> Token+tokToLower (Literal     c)   = Literal       (toLower c)+tokToLower (LongLiteral n s) = LongLiteral n (map toLower s)+tokToLower tok               = tok+ -- |An abstract data type representing a compiled pattern.--- --- The 'Show' instance is essentially the inverse of @'compile'@. Though it may--- not return exactly what was given to @'compile'@ it will return code which--- produces the same 'Pattern'. -- -- Note that the 'Eq' instance cannot tell you whether two patterns behave in -- the same way; only whether they compile to the same 'Pattern'. For instance,@@ -39,22 +94,533 @@ liftP f (Pattern pat) = Pattern (f pat)  instance Show Token where-   show (Literal c)         = [c]+   show (Literal c)+      | c `elem` "*?[<" || isExtSeparator c+                            = ['[',c,']']+      | otherwise           = assert (not $ isPathSeparator c) [c]    show ExtSeparator        = [ extSeparator]    show PathSeparator       = [pathSeparator]    show NonPathSeparator    = "?"    show AnyNonPathSeparator = "*"    show AnyDirectory        = "**/"-   show (LongLiteral _ s)   = s-   show (CharRange b r)     =-      '[' : (if b then "" else "^") ++-            concatMap (either (:[]) (\(x,y) -> [x,'-',y])) r ++ "]"+   show (LongLiteral _ s)   = concatMap (show . Literal) s    show (OpenRange a b)     =       '<' : fromMaybe "" a ++ "-" ++             fromMaybe "" b ++ ">" -   showList = showList . concatMap show+   -- We have to be careful here with ^ and ! lest [a!b] become [!ab]. So we+   -- just put them at the end.+   --+   -- Also, [^x-] was sorted and should not become [^-x].+   show (CharRange b r)     =+      let f = either (:[]) (\(x,y) -> [x,'-',y])+          (caret,exclamation,fs) =+             foldr (\c (ca,ex,ss) ->+                case c of+                     Left '^' -> ("^",ex,ss)+                     Left '!' -> (ca,"!",ss)+                     _        -> (ca,  ex,(f c ++) . ss)+                   )+                   ("", "", id)+                   r+          (beg,rest) = let s' = fs []+                           (x,y) = splitAt 1 s'+                           in if not b && x == "-"+                                 then (y,x)+                                 else (s',"")+       in concat [ "["+                 , if b then "" else "^"+                 , beg, caret, exclamation, rest+                 , "]"+                 ]  instance Show Pattern where-   showsPrec d (Pattern ts) =-      showParen (d > 10) $ showString "compile " . shows ts+   showsPrec d p = showParen (d > 10) $+      showString "compile " . showsPrec (d+1) (decompile p)++instance Read Pattern where+#if __GLASGOW_HASKELL__+   readPrec = parens . prec 10 $ do+      Ident "compile" <- lexP+      fmap compile readPrec+#else+   readsPrec d = readParen (d > 10) $ \r -> do+      ("compile",string) <- lex r+      (xs,rest) <- readsPrec (d+1) string+      [(compile xs, rest)]+#endif++instance Monoid Pattern where+   mempty                          = Pattern []+   mappend (Pattern a) (Pattern b) = optimize . Pattern $ (a ++ b)+   mconcat                         = optimize . Pattern . concatMap unPattern++-- |Options which can be passed to the 'tryCompileWith' or 'compileWith'+-- functions: with these you can selectively toggle certain features at compile+-- time.+--+-- Note that some of these options depend on each other: classes can never+-- occur if ranges aren't allowed, for instance.++-- We could presumably put locale information in here, too.+data CompOptions = CompOptions+    { characterClasses   :: Bool -- ^Allow character classes, @[[:...:]]@.+    , characterRanges    :: Bool -- ^Allow character ranges, @[...]@.+    , numberRanges       :: Bool -- ^Allow open ranges, @\<...>@.+    , wildcards          :: Bool -- ^Allow wildcards, @*@ and @?@.+    , recursiveWildcards :: Bool -- ^Allow recursive wildcards, @**/@.++    , pathSepInRanges    :: Bool+      -- ^Allow path separators in character ranges.+      --+      -- If true, @a[/]b@ never matches anything (since character ranges can't+      -- match path separators); if false and 'errorRecovery' is enabled,+      -- @a[/]b@ matches itself, i.e. a file named @]b@ in the subdirectory+      -- @a[@.++    , errorRecovery      :: Bool+      -- ^If the input is invalid, recover by turning any invalid part into+      -- literals. For instance, with 'characterRanges' enabled, @[abc@ is an+      -- error by default (unclosed character range); with 'errorRecovery', the+      -- @[@ is turned into a literal match, as though 'characterRanges' were+      -- disabled.+    } deriving (Show,Read,Eq)++-- |The default set of compilation options: closest to the behaviour of the+-- @zsh@ shell, with 'errorRecovery' enabled.+--+-- All options are enabled.+compDefault :: CompOptions+compDefault = CompOptions+   { characterClasses   = True+   , characterRanges    = True+   , numberRanges       = True+   , wildcards          = True+   , recursiveWildcards = True+   , pathSepInRanges    = True+   , errorRecovery      = True+   }++-- |Options for POSIX-compliance, as described in @man 7 glob@.+--+-- 'numberRanges', 'recursiveWildcards', and 'pathSepInRanges' are disabled.+compPosix :: CompOptions+compPosix = CompOptions+   { characterClasses   = True+   , characterRanges    = True+   , numberRanges       = False+   , wildcards          = True+   , recursiveWildcards = False+   , pathSepInRanges    = False+   , errorRecovery      = True+   }++-- |Options which can be passed to the 'matchWith' or 'globDirWith' functions:+-- with these you can selectively toggle certain features at matching time.+data MatchOptions = MatchOptions+    { matchDotsImplicitly :: Bool+      -- ^Allow @*@, @?@, and @**/@ to match @.@ at the beginning of paths.++    , ignoreCase          :: Bool+      -- ^Case-independent matching.++    , ignoreDotSlash      :: Bool+      -- ^Treat @./@ as a no-op in both paths and patterns.+      --+      -- (Of course e.g. @../@ means something different and will not be+      -- ignored.)+    }++-- |The default set of execution options: closest to the behaviour of the @zsh@+-- shell.+--+-- Currently identical to 'matchPosix'.+matchDefault :: MatchOptions+matchDefault = matchPosix++-- |Options for POSIX-compliance, as described in @man 7 glob@.+--+-- 'ignoreDotSlash' is enabled, the rest are disabled.+matchPosix :: MatchOptions+matchPosix = MatchOptions+   { matchDotsImplicitly = False+   , ignoreCase          = False+   , ignoreDotSlash      = True+   }++-- |Decompiles a 'Pattern' object into its textual representation: essentially+-- the inverse of 'compile'.+--+-- Note, however, that due to internal optimization, @decompile . compile@ is+-- not the identity function. Instead, @compile . decompile@ is.+--+-- Be careful with 'CompOptions': 'decompile' always produces a 'String' which+-- can be passed to 'compile' to get back the same 'Pattern'. @compileWith+-- options . decompile@ is /not/ the identity function unless @options@ is+-- 'compDefault'.+decompile :: Pattern -> String+decompile = concatMap show . unPattern++------------------------------------------+-- COMPILATION+------------------------------------------+++-- |Compiles a glob pattern from its textual representation into a 'Pattern'+-- object.+--+-- For the most part, a character matches itself. Recognized operators are as+-- follows:+--+-- [@?@]      Matches any character except path separators.+--+-- [@*@]      Matches any number of characters except path separators,+--            including the empty string.+--+-- [@[..\]@]  Matches any of the enclosed characters. Ranges of characters can+--            be specified by separating the endpoints with a @\'-'@. @\'-'@ or+--            @']'@ can be matched by including them as the first character(s)+--            in the list. Never matches path separators: @[\/]@ matches+--            nothing at all. Named character classes can also be matched:+--            @[:x:]@ within @[]@ specifies the class named @x@, which matches+--            certain predefined characters. See below for a full list.+--+-- [@[^..\]@ or @[!..\]@] Like @[..]@, but matches any character /not/ listed.+--                        Note that @[^-x]@ is not the inverse of @[-x]@, but+--                        the range @[^-x]@.+--+-- [@\<m-n>@] Matches any integer in the range m to n, inclusive. The range may+--            be open-ended by leaving out either number: @\"\<->\"@, for+--            instance, matches any integer.+--+-- [@**/@]    Matches any number of characters, including path separators,+--            excluding the empty string.+--+-- Supported character classes:+--+-- [@[:alnum:\]@]  Equivalent to @\"0-9A-Za-z\"@.+--+-- [@[:alpha:\]@]  Equivalent to @\"A-Za-z\"@.+--+-- [@[:blank:\]@]  Equivalent to @\"\\t \"@.+--+-- [@[:cntrl:\]@]  Equivalent to @\"\\0-\\x1f\\x7f\"@.+--+-- [@[:digit:\]@]  Equivalent to @\"0-9\"@.+--+-- [@[:graph:\]@]  Equivalent to @\"!-~\"@.+--+-- [@[:lower:\]@]  Equivalent to @\"a-z\"@.+--+-- [@[:print:\]@]  Equivalent to @\" -~\"@.+--+-- [@[:punct:\]@]  Equivalent to @\"!-\/:-\@[-`{-~\"@.+--+-- [@[:space:\]@]  Equivalent to @\"\\t-\\r \"@.+--+-- [@[:upper:\]@]  Equivalent to @\"A-Z\"@.+--+-- [@[:xdigit:\]@] Equivalent to @\"0-9A-Fa-f\"@.+--+-- Note that path separators (typically @\'/\'@) have to be matched explicitly+-- or using the @**/@ pattern. In addition, extension separators (typically+-- @\'.\'@) have to be matched explicitly at the beginning of the pattern or+-- after any path separator.+--+-- If a system supports multiple path separators, any one of them will match+-- any of them. For instance, on Windows, @\'/\'@ will match itself as well as+-- @\'\\\'@.+--+-- Error recovery will be performed: erroneous operators will not be considered+-- operators, but matched as literal strings. Such operators include:+--+-- * An empty @[]@ or @[^]@ or @[!]@+--+-- * A @[@ or @\<@ without a matching @]@ or @>@+--+-- * A malformed @\<>@: e.g. nonnumeric characters or no hyphen+--+-- So, e.g. @[]@ will match the string @\"[]\"@.+compile :: String -> Pattern+compile = compileWith compDefault++-- |Like 'compile', but recognizes operators according to the given+-- 'CompOptions' instead of the defaults.+--+-- If an error occurs and 'errorRecovery' is disabled, 'error' will be called.+compileWith :: CompOptions -> String -> Pattern+compileWith opts = either error id . tryCompileWith opts++-- |A safe version of 'compileWith'.+--+-- If an error occurs and 'errorRecovery' is disabled, the error message will+-- be returned in a 'Left'.+tryCompileWith :: CompOptions -> String -> Either String Pattern+tryCompileWith opts = fmap optimize . tokenize opts++tokenize :: CompOptions -> String -> Either String Pattern+tokenize opts = fmap Pattern . sequence . go+ where+   err _ c cs | errorRecovery opts = Right (Literal c) : go cs+   err s _ _                       = [Left s]++   go :: String -> [Either String Token]+   go [] = []+   go ('?':cs) | wcs = Right NonPathSeparator : go cs+   go ('*':cs) | wcs =+      case cs of+           '*':p:xs | rwcs && isPathSeparator p+              -> Right AnyDirectory        : go xs+           _  -> Right AnyNonPathSeparator : go cs++   go ('[':cs) | crs = let (range,rest) = charRange opts cs+                        in case range of+                                Left s -> err s '[' cs+                                r      -> r : go rest++   go ('<':cs) | ors =+      let (range, rest) = break (=='>') cs+       in if null rest+             then err "compile :: unclosed <> in pattern" '<' cs+             else case openRange range of+                       Left s -> err s '<' cs+                       r      -> r : go (tail rest)+   go (c:cs)+      | isPathSeparator c = Right PathSeparator : go cs+      | isExtSeparator  c = Right  ExtSeparator : go cs+      | otherwise         = Right (Literal c)   : go cs++   wcs  = wildcards          opts+   rwcs = recursiveWildcards opts+   crs  = characterRanges    opts+   ors  = numberRanges       opts++-- <a-b> where a > b can never match anything; this is not considered an error+openRange :: String -> Either String Token+openRange ['-']   = Right $ OpenRange Nothing Nothing+openRange ('-':s) =+   case span isDigit s of+        (b,"") -> Right $ OpenRange Nothing (openRangeNum b)+        _      -> Left $ "compile :: bad <>, expected number, got " ++ s+openRange s =+   case span isDigit s of+        (a,"-")    -> Right $ OpenRange (openRangeNum a) Nothing+        (a,'-':s') ->+           case span isDigit s' of+                (b,"") -> Right $ OpenRange (openRangeNum a) (openRangeNum b)+                _ -> Left $ "compile :: bad <>, expected number, got " ++ s'+        _ -> Left $ "compile :: bad <>, expected number followed by - in " ++ s++openRangeNum :: String -> Maybe String+openRangeNum = Just . dropLeadingZeroes++type CharRange = [Either Char (Char,Char)]++charRange :: CompOptions -> String -> (Either String Token, String)+charRange opts zs =+   case zs of+        y:ys | y `elem` "^!" ->+           case ys of+                -- [!-#] is not the inverse of [-#], it is the range ! through+                -- #+                '-':']':xs -> (Right (CharRange False [Left '-']), xs)+                '-'    :_  -> first (fmap (CharRange True )) (start zs)+                xs         -> first (fmap (CharRange False)) (start xs)+        _                  -> first (fmap (CharRange True )) (start zs)+ where+   start :: String -> (Either String CharRange, String)+   start (']':xs) = run $ char ']' xs+   start ('-':xs) = run $ char '-' xs+   start xs       = run $ go xs++   run :: ErrorT String (Writer CharRange) String+       -> (Either String CharRange, String)+   run m = case runWriter.runErrorT $ m of+                (Left   err,  _) -> (Left err, [])+                (Right rest, cs) -> (Right cs, rest)++   go :: String -> ErrorT String (Writer CharRange) String+   go ('[':':':xs) | characterClasses opts = readClass xs+   go (    ']':xs) = return xs+   go (      c:xs) =+      if not (pathSepInRanges opts) && isPathSeparator c+         then throwError "compile :: path separator within []"+         else char c xs+   go []           = throwError "compile :: unclosed [] in pattern"++   char :: Char -> String -> ErrorT String (Writer CharRange) String+   char c ('-':x:xs) =+      if x == ']'+         then tell [Left c, Left '-'] >> return xs+         else tell [Right (c,x)]      >>     go xs++   char c xs = tell [Left c] >> go xs++   readClass :: String -> ErrorT String (Writer CharRange) String+   readClass xs = let (name,end) = span isAlpha xs+                   in case end of+                           ':':']':rest -> charClass name           >> go rest+                           _            -> tell [Left '[',Left ':'] >> go xs++   charClass :: String -> ErrorT String (Writer CharRange) ()+   charClass name =+      -- The POSIX classes+      --+      -- TODO: this is ASCII-only, not sure how this should be extended+      --       Unicode, or with a locale as input, or something else?+      case name of+           "alnum"  -> tell [digit,upper,lower]+           "alpha"  -> tell [upper,lower]+           "blank"  -> tell blanks+           "cntrl"  -> tell [Right ('\0','\x1f'), Left '\x7f']+           "digit"  -> tell [digit]+           "graph"  -> tell [Right ('!','~')]+           "lower"  -> tell [lower]+           "print"  -> tell [Right (' ','~')]+           "punct"  -> tell punct+           "space"  -> tell spaces+           "upper"  -> tell [upper]+           "xdigit" -> tell [digit, Right ('A','F'), Right ('a','f')]+           _        ->+              throwError ("compile :: unknown character class '" ++name++ "'")++   digit  = Right ('0','9')+   upper  = Right ('A','Z')+   lower  = Right ('a','z')+   punct  = map Right [('!','/'), (':','@'), ('[','`'), ('{','~')]+   blanks = [Left '\t',         Left ' ']+   spaces = [Right ('\t','\r'), Left ' ']+++------------------------------------------+-- OPTIMIZATION+------------------------------------------+++optimize :: Pattern -> Pattern+optimize = liftP (fin . go)+ where+   fin [] = []++   -- Literals to LongLiteral+   -- Has to be done here: we can't backtrack in go, but some cases might+   -- result in consecutive Literals being generated.+   -- E.g. "a[b]".+   fin (x:y:xs) | isLiteral x && isLiteral y =+      let (ls,rest) = span isLiteral xs+       in fin $ LongLiteral (length ls + 2)+                            (foldr (\(Literal a) -> (a:)) [] (x:y:ls))+                : rest++   -- concatenate LongLiterals+   -- Has to be done here because LongLiterals are generated above.+   --+   -- So one could say that we have one pass (go) which flattens everything as+   -- much as it can and one pass (fin) which concatenates what it can.+   fin (LongLiteral l1 s1 : LongLiteral l2 s2 : xs) =+      fin $ LongLiteral (l1+l2) (s1++s2) : xs++   fin (LongLiteral l s : Literal c : xs) =+      fin $ LongLiteral (l+1) (s++[c]) : xs++   fin (LongLiteral 1 s : xs) = Literal (head s) : fin xs++   fin (Literal c : LongLiteral l s : xs) =+      fin $ LongLiteral (l+1) (c:s) : xs++   fin (x:xs) = x : fin xs++   go [] = []+   go (x@(CharRange _ _) : xs) =+      case optimizeCharRange x of+           x'@(CharRange _ _) -> x' : go xs+           x'                 -> go (x':xs)++   -- <a-a> -> a+   go (OpenRange (Just a) (Just b):xs)+      | a == b = LongLiteral (length a) a : go xs++   -- <a-b> -> [a-b]+   -- a and b are guaranteed non-null+   go (OpenRange (Just [a]) (Just [b]):xs)+      | b > a = go $ CharRange True [Right (a,b)] : xs++   go (x:xs) =+      case find ($ x) compressors of+           Just c  -> let (compressed,ys) = span c xs+                       in if null compressed+                             then x : go ys+                             else go (x : ys)+           Nothing -> x : go xs++   compressors = [isStar, isStarSlash, isAnyNumber]++   isLiteral   (Literal _)                 = True+   isLiteral   _                           = False+   isStar      AnyNonPathSeparator         = True+   isStar      _                           = False+   isStarSlash AnyDirectory                = True+   isStarSlash _                           = False+   isAnyNumber (OpenRange Nothing Nothing) = True+   isAnyNumber _                           = False++optimizeCharRange :: Token -> Token+optimizeCharRange (CharRange b_ rs) = fin b_ . go . sortCharRange $ rs+ where+   -- [/] is interesting, it actually matches nothing at all+   -- [.] can be Literalized though, just don't make it into an ExtSeparator so+   --     that it doesn't match a leading dot+   fin True [Left  c] | not (isPathSeparator c) = Literal c+   fin True [Right r] | r == (minBound,maxBound) = NonPathSeparator+   fin b x = CharRange b x++   go [] = []++   go (x@(Left c) : xs) =+      case xs of+           [] -> [x]+           y@(Left d) : ys+              -- [aaaaa] -> [a]+              | c == d      -> go$ Left c : ys+              | d == succ c ->+                 let (ls,rest)        = span isLeft xs -- start from y+                     (catable,others) = increasingSeq (map fromLeft ls)+                     range            = (c, head catable)++                  in -- three (or more) Lefts make a Right+                     if null catable || null (tail catable)+                        then x : y : go ys+                        -- [abcd] -> [a-d]+                        else go$ Right range : map Left others ++ rest++              | otherwise -> x : go xs++           Right r : ys ->+              case addToRange r c of+                   -- [da-c] -> [a-d]+                   Just r' -> go$ Right r' : ys+                   Nothing -> x : go xs++   go (x@(Right r) : xs) =+      case xs of+           [] -> [x]+           Left c : ys ->+              case addToRange r c of+                   -- [a-cd] -> [a-d]+                   Just r' -> go$ Right r' : ys+                   Nothing -> x : go xs++           Right r' : ys ->+              case overlap r r' of+                   -- [a-cb-d] -> [a-d]+                   Just o  -> go$ Right o : ys+                   Nothing -> x : go xs+optimizeCharRange _ = error "Glob.optimizeCharRange :: internal error"++sortCharRange :: [Either Char (Char,Char)] -> [Either Char (Char,Char)]+sortCharRange = sortBy cmp+ where+   cmp (Left   a)    (Left   b)    = compare a b+   cmp (Left   a)    (Right (b,_)) = compare a b+   cmp (Right (a,_)) (Left   b)    = compare a b+   cmp (Right (a,_)) (Right (b,_)) = compare a b
− System/FilePath/Glob/Compile.hs
@@ -1,128 +0,0 @@--- File created: 2008-10-10 13:29:13--module System.FilePath.Glob.Compile-   ( compile, tryCompile-   , tokenize-   ) where--import Control.Monad.Error ()-import Data.Char           (isDigit)-import System.FilePath     (isPathSeparator, isExtSeparator)--import System.FilePath.Glob.Base-import System.FilePath.Glob.Optimize (optimize)-import System.FilePath.Glob.Utils    (dropLeadingZeroes)---- |Like 'tryCompile', but calls 'error' if an error results.-compile :: String -> Pattern-compile = either error id . tryCompile---- |Compiles a glob pattern from its textual representation into a 'Pattern'--- object, giving an error message in a 'Left' if the pattern is erroneous.------ For the most part, a character matches itself. Recognized operators are as--- follows:------ [@?@]      Matches any character except path separators.--- --- [@*@]      Matches any number of characters except path separators,---            including the empty string.--- --- [@[..\]@]  Matches any of the enclosed characters. Ranges of characters can---            be specified by separating the endpoints with a \'-'. \'-' or ']'---            can be matched by including them as the first character(s) in the---            list.--- --- [@[^..\]@ or @[!..\]@] Like [..], but matches any character /not/ listed.--- --- [@\<m-n>@]  Matches any integer in the range m to n, inclusive. The range may---            be open-ended by leaving out either number: \"\<->\", for---            instance, matches any integer.--- --- [@**/@]    Matches any number of characters, including path separators,---            excluding the empty string.------ Note that path separators (typically @\'/\'@) have to be matched explicitly--- or using the @**/@ pattern. In addition, extension separators (typically--- @\'.\'@) have to be matched explicitly at the beginning of the pattern or--- after any path separator.------ If a system supports multiple path separators, any one of them will match--- any of them. For instance, on Windows, @\'/\'@ will match itself as well as--- @\'\\\'@.------ Erroneous patterns include:------ * An empty @[]@ or @[^]@ or @[!]@------ * A @[@ or @\<@ without a matching @]@ or @>@------ * A malformed @\<>@: e.g. nonnumeric characters or no hyphen-tryCompile :: String -> Either String Pattern-tryCompile = fmap optimize . tokenize--tokenize :: String -> Either String Pattern-tokenize = fmap Pattern . sequence . go- where-   err s = [Left s]--   go :: String -> [Either String Token]-   go [] = []-   go ('?':cs) = Right NonPathSeparator : go cs-   go ('*':cs) =-      case cs of-           '*':p:xs | isPathSeparator p -> Right AnyDirectory        : go xs-           _                            -> Right AnyNonPathSeparator : go cs-   go ('[':cs) =-      let (range, rest) = break (==']') cs-       in if null rest-             then err "compile :: unclosed [] in pattern"-             else if null range-                     then let (range', rest') = break (==']') (tail rest)-                           in if null rest'-                                 then err "compile :: empty [] in pattern"-                                 else charRange (']':range') : go (tail rest')-                     else charRange range : go (tail rest)-   go ('<':cs) =-      let (range, rest) = break (=='>') cs-       in if null rest-             then err "compile :: unclosed <> in pattern"-             else openRange range : go (tail rest)-   go (c:cs)-      | isPathSeparator c = Right PathSeparator : go cs-      | isExtSeparator  c = Right  ExtSeparator : go cs-      | otherwise         = Right (Literal c)   : go cs---- <a-b> where a > b can never match anything; this is not considered an error-openRange :: String -> Either String Token-openRange ['-']   = Right $ OpenRange Nothing Nothing-openRange ('-':s) =-   case span isDigit s of-        (b,"") -> Right $ OpenRange Nothing (openRangeNum b)-        _      -> Left $ "compile :: bad <>, expected number, got " ++ s-openRange s =-   case span isDigit s of-        (a,"-")    -> Right $ OpenRange (openRangeNum a) Nothing-        (a,'-':s') ->-           case span isDigit s' of-                (b,"") -> Right $ OpenRange (openRangeNum a) (openRangeNum b)-                _ -> Left $ "compile :: bad <>, expected number, got " ++ s'-        _ -> Left $ "compile :: bad <>, expected number followed by - in " ++ s--openRangeNum :: String -> Maybe String-openRangeNum = Just . dropLeadingZeroes--charRange :: String -> Either String Token-charRange [x] | x `elem` "^!" = Left ("compile :: empty [" ++ [x]-                                                           ++ "] in pattern")-charRange x =-   if head x `elem` "^!"-      then Right . CharRange False . f $ tail x-      else Right . CharRange True  . f $      x- where-   f (']':s) = Left ']' : go s-   f      s  =            go s--   go [] = []-   go (a:'-':b:cs) = (if a == b then Left a else Right (a,b)) : go cs-   go (c:cs)       = Left c : go cs
System/FilePath/Glob/Directory.hs view
@@ -1,55 +1,92 @@ -- File created: 2008-10-16 12:12:50 -module System.FilePath.Glob.Directory (globDir) where+module System.FilePath.Glob.Directory+   ( globDir, globDirWith+   , commonDirectory+   ) where +import Control.Arrow    (first, second) import Control.Monad    (forM) import qualified Data.DList as DL import Data.DList       (DList) import Data.List        ((\\))-import System.Directory (doesDirectoryExist, getDirectoryContents)-import System.FilePath  ((</>))+import System.Directory ( doesDirectoryExist, getDirectoryContents+                        , getCurrentDirectory+                        )+import System.FilePath  ((</>), extSeparator, isExtSeparator, pathSeparator) -import System.FilePath.Glob.Base-import System.FilePath.Glob.Match (match)-import System.FilePath.Glob.Utils-   (getRecursiveContents, nubOrd, pathParts, partitionDL)+import System.FilePath.Glob.Base  ( Pattern(..), Token(..)+                                  , MatchOptions, matchDefault+                                  )+import System.FilePath.Glob.Match (matchWith)+import System.FilePath.Glob.Utils ( getRecursiveContents+                                  , nubOrd+                                  , pathParts+                                  , partitionDL+                                  )  -- The Patterns in TypedPattern don't contain PathSeparator or AnyDirectory+--+-- We store the number of PathSeparators that Dir and AnyDir were followed by+-- so that "foo////*" can match "foo/bar" but return "foo////bar". It's the+-- exact number for convenience: (</>) doesn't add a path separator if one is+-- already there. This way, '\(Dir n _) -> replicate n pathSeparator </> "bar"'+-- results in the correct amount of slashes. data TypedPattern-   = Any Pattern    -- pattern-   | Dir Pattern    -- pattern/-   | AnyDir Pattern -- pattern**/+   = Any Pattern        -- pattern+   | Dir Int Pattern    -- pattern/+   | AnyDir Int Pattern -- pattern**/    deriving Show  -- |Matches each given 'Pattern' against the contents of the given 'FilePath', -- recursively. The result pair\'s first component contains the matched paths, -- grouped for each given 'Pattern', and the second contains all paths which--- were not matched by any 'Pattern'.+-- were not matched by any 'Pattern'. The results are not in any defined order. --+-- The given directory is prepended to all the matches: the returned paths are+-- all valid from the point of view of the current working directory.+-- -- If multiple 'Pattern's match a single 'FilePath', that path will be included -- in multiple groups. --+-- Two 'FilePath's which can be canonicalized to the same file (e.g. @\"foo\"@+-- and @\"./foo\"@) may appear separately if explicit matching on paths+-- beginning with @\".\"@ is done. Looking for @\".*/*\"@, for instance, will+-- cause @\"./foo\"@ to return as a match but @\"foo\"@ to not be matched.+-- -- This function is different from a simple 'filter' over all the contents of -- the directory: the matching is performed relative to the directory, so that -- for instance the following is true: -- -- > fmap (head.fst) (globDir [compile "*"] dir) == getDirectoryContents dir ----- If @dir@ is @\"foo\"@ the pattern should be @\"foo/*\"@ to get the same--- results with a plain 'filter'.+-- (With the exception that that glob won't match anything beginning with @.@.) --+-- If the given 'FilePath' is @[]@, 'getCurrentDirectory' will be used.+--+-- Note that in some cases results outside the given directory may be returned:+-- for instance the @.*@ pattern matches the @..@ directory.+-- -- Any results deeper than in the given directory are enumerated lazily, using -- 'unsafeInterleaveIO'. -- -- Directories without read permissions are returned as entries but their -- contents, of course, are not. globDir :: [Pattern] -> FilePath -> IO ([[FilePath]], [FilePath])-globDir []   dir = do-   c <- getRecursiveContents dir+globDir = globDirWith matchDefault++-- |Like 'globDir', but applies the given 'MatchOptions' instead of the+-- defaults when matching.+globDirWith :: MatchOptions -> [Pattern] -> FilePath+            -> IO ([[FilePath]], [FilePath])+globDirWith _ []   dir = do+   dir' <- if null dir then getCurrentDirectory else return dir+   c <- getRecursiveContents dir'    return ([], DL.toList c)-globDir pats dir = do-   results <- mapM (\p -> globDir' (separate p) dir) pats +globDirWith opts pats dir = do+   results <- mapM (\p -> globDir' opts (separate p) dir) pats+    let (matches, others) = unzip results        allMatches        = DL.toList . DL.concat $ matches        allOthers         = DL.toList . DL.concat $ others@@ -58,69 +95,121 @@           , nubOrd allOthers \\ allMatches           ) -globDir' :: [TypedPattern] -> FilePath -> IO (DList FilePath, DList FilePath)-globDir' []   dir = didn'tMatch dir True-globDir' pats dir = do-   raw <- getDirectoryContents dir--   let entries = raw \\ [".",".."]+globDir' :: MatchOptions -> [TypedPattern] -> FilePath+         -> IO (DList FilePath, DList FilePath)+globDir' opts pats@(_:_) dir = do+   dir' <- if null dir then getCurrentDirectory else return dir+   entries <- getDirectoryContents dir' `catch` const (return []) -   results <- forM entries $ \e -> matchTypedAndGo pats e (dir </> e)+   results <- forM entries $ \e -> matchTypedAndGo opts pats e (dir </> e)     let (matches, others) = unzip results     return (DL.concat matches, DL.concat others) -matchTypedAndGo :: [TypedPattern]+globDir' _ [] dir =+   -- We can only get here from matchTypedAndGo getting a [Dir _]: it means the+   -- original pattern had a trailing PathSeparator. Reproduce it here.+   return (DL.singleton (dir ++ [pathSeparator]), DL.empty)++matchTypedAndGo :: MatchOptions+                -> [TypedPattern]                 -> FilePath -> FilePath                 -> IO (DList FilePath, DList FilePath)  -- (Any p) is always the last element-matchTypedAndGo [Any p] path absPath =-   if match p path+matchTypedAndGo opts [Any p] path absPath =+   if matchWith opts p path       then return (DL.singleton absPath, DL.empty)-      else doesDirectoryExist absPath >>= didn'tMatch absPath+      else doesDirectoryExist absPath >>= didn'tMatch path absPath -matchTypedAndGo (Dir p:ps) path absPath = do+matchTypedAndGo opts (Dir n p:ps) path absPath = do    isDir <- doesDirectoryExist absPath-   if isDir && match p path-      then globDir' ps absPath-      else didn'tMatch absPath isDir+   if isDir && matchWith opts p path+      then globDir' opts ps (absPath ++ replicate n pathSeparator)+      else didn'tMatch path absPath isDir -matchTypedAndGo (AnyDir p:ps) path absPath = do-   isDir <- doesDirectoryExist absPath-   let m = match (unseparate ps)+matchTypedAndGo opts (AnyDir n p:ps) path absPath = do+   if path `elem` [".",".."]+      then didn'tMatch path absPath True+      else do+         isDir <- doesDirectoryExist absPath+         let m = matchWith opts (unseparate ps)+             unconditionalMatch =+                null (unPattern p) && not (isExtSeparator $ head path)+             p' = Pattern (unPattern p ++ [AnyNonPathSeparator]) -   case null (unPattern p) || match p path of-        True | isDir  -> fmap (partitionDL (any m . pathParts))-                              (getRecursiveContents absPath)-        True | m path -> return (DL.singleton absPath, DL.empty)-        _             -> didn'tMatch absPath isDir+         case unconditionalMatch || matchWith opts p' path of+              True | isDir  -> do+                 contents <- getRecursiveContents+                                (absPath ++ replicate n pathSeparator)+                 return $+                    -- foo**/ should match foo/ and nothing below it+                    -- relies on head contents == absPath+                    if null ps+                       then (DL.singleton $ DL.head contents, DL.tail contents)+                       else partitionDL (any m . pathParts) contents -matchTypedAndGo _ _ _ = error "Glob.matchTypedAndGo :: internal error"+              True | m path -> return (DL.singleton absPath, DL.empty)+              _             -> didn'tMatch path absPath isDir -didn'tMatch :: FilePath -> Bool -> IO (DList FilePath, DList FilePath)-didn'tMatch absPath isDir = (fmap $ (,) DL.empty) $+matchTypedAndGo _ _ _ _ = error "Glob.matchTypedAndGo :: internal error"++-- To be called when a pattern didn't match a path: given the path and whether+-- it was a directory, return all paths which didn't match (i.e. for a file,+-- just the file, and for a directory, everything inside it).+didn'tMatch :: FilePath -> FilePath -> Bool+            -> IO (DList FilePath, DList FilePath)+didn'tMatch path absPath isDir = (fmap $ (,) DL.empty) $    if isDir-      then getRecursiveContents absPath+      then if path `elem` [".",".."]+              then return DL.empty+              else getRecursiveContents absPath       else return$ DL.singleton absPath  separate :: Pattern -> [TypedPattern]-separate = go [] . unPattern+separate = go DL.empty . unPattern  where-   go [] []                              = []-   go gr []                              = [Any    $ f gr]-   -- ./foo should not be split into [. , foo], it's just foo-   go gr (ExtSeparator:PathSeparator:ps) = go gr ps-   go gr (             PathSeparator:ps) = (   Dir $ f gr) : go [] ps-   go gr (              AnyDirectory:ps) = (AnyDir $ f gr) : go [] ps-   go gr (                         p:ps) = go (p:gr) ps+   go gr [] | null (DL.toList gr) = []+   go gr []                       = [Any (pat gr)]+   go gr (PathSeparator:ps)       = slash gr Dir ps+   go gr ( AnyDirectory:ps)       = slash gr AnyDir ps+   go gr (            p:ps)       = go (gr `DL.snoc` p) ps -   f = Pattern . reverse+   pat = Pattern . DL.toList +   slash gr f ps = let (n,ps') = first length . span isSlash $ ps+                    in f (n+1) (pat gr) : go DL.empty ps'++   isSlash PathSeparator = True+   isSlash _             = False+ unseparate :: [TypedPattern] -> Pattern unseparate = Pattern . foldr f []  where-   f (AnyDir p) ts = unPattern p ++ AnyDirectory  : ts-   f (   Dir p) ts = unPattern p ++ PathSeparator : ts-   f (Any    p) ts = unPattern p ++ ts+   f (AnyDir n p) ts = u p ++ AnyDirectory  : replicate n PathSeparator ++ ts+   f (   Dir n p) ts = u p ++ PathSeparator : replicate n PathSeparator ++ ts+   f (Any      p) ts = u p ++ ts++   u = unPattern++-- |Factors out the directory component of a 'Pattern'. Useful in conjunction+-- with 'globDir'.+--+-- Preserves the number of path separators: @commonDirectory (compile+-- \"foo\/\/\/bar\")@ becomes @(\"foo\/\/\/\", compile \"bar\")@.+commonDirectory :: Pattern -> (FilePath, Pattern)+commonDirectory = second unseparate . splitP . separate+ where+   splitP pt@(Dir n p:ps) =+      case fromConst DL.empty (unPattern p) of+           Just d  -> first ((d ++ replicate n pathSeparator) </>) (splitP ps)+           Nothing -> ("", pt)++   splitP pt = ("", pt)++   fromConst d []                   = Just (DL.toList d)+   fromConst d (Literal c      :xs) = fromConst (d `DL.snoc` c) xs+   fromConst d (ExtSeparator   :xs) = fromConst (d `DL.snoc` extSeparator) xs+   fromConst d (LongLiteral _ s:xs) = fromConst (d `DL.append`DL.fromList s) xs+   fromConst _ _                    = Nothing
System/FilePath/Glob/Match.hs view
@@ -1,48 +1,91 @@ -- File created: 2008-10-10 13:29:03 -module System.FilePath.Glob.Match (match) where+module System.FilePath.Glob.Match (match, matchWith) where  import Control.Exception (assert)-import Data.Char         (isDigit)+import Data.Char         (isDigit, toLower, toUpper) import Data.Monoid       (mappend) import System.FilePath   (isPathSeparator, isExtSeparator) -import System.FilePath.Glob.Base+import System.FilePath.Glob.Base  ( Pattern(..), Token(..)+                                  , MatchOptions(..), matchDefault+                                  , tokToLower+                                  ) import System.FilePath.Glob.Utils (dropLeadingZeroes, inRange, pathParts)  -- |Matches the given 'Pattern' against the given 'FilePath', returning 'True' -- if the pattern matches and 'False' otherwise. match :: Pattern -> FilePath -> Bool-match = begMatch . unPattern+match = matchWith matchDefault +-- |Like 'match', but applies the given 'MatchOptions' instead of the defaults.+matchWith :: MatchOptions -> Pattern -> FilePath -> Bool+matchWith opts p f = begMatch opts (lcPat $ unPattern p) (lcPath f)+ where+   lcPath = if ignoreCase opts then map    toLower else id+   lcPat  = if ignoreCase opts then map tokToLower else id+ -- begMatch takes care of some things at the beginning of a pattern or after /: --    - . needs to be matched explicitly---    - ./foo is equivalent to foo-begMatch, match' :: [Token] -> FilePath -> Bool-begMatch _ "." = False-begMatch _ ".." = False-begMatch (ExtSeparator:PathSeparator:pat) s                  = begMatch pat s-begMatch pat (x:y:s) | isExtSeparator x && isPathSeparator y = begMatch pat s-begMatch pat s =-   if not (null s) && isExtSeparator (head s)+--    - ./foo is equivalent to foo (for any number of /)+--+-- .*/foo still needs to match ./foo though, and it won't match plain foo;+-- special case that one+--+-- and .**/foo should /not/ match ../foo; more special casing+--+-- (All of the above is modulo options, of course)+begMatch, match' :: MatchOptions -> [Token] -> FilePath -> Bool+begMatch _ (ExtSeparator:AnyDirectory:_) (x:y:_)+   | isExtSeparator x && isExtSeparator y = False++begMatch opts (ExtSeparator:PathSeparator:pat) s | ignoreDotSlash opts =+   begMatch opts (dropWhile isSlash pat) s+ where+   isSlash PathSeparator = True+   isSlash _             = False++begMatch opts pat (x:y:s)+   | dotSlash && dotStarSlash        = match' opts pat' s+   | ignoreDotSlash opts && dotSlash = begMatch opts pat s+ where+   dotSlash = isExtSeparator x && isPathSeparator y+   (dotStarSlash, pat') =+      case pat of+        ExtSeparator:AnyNonPathSeparator:PathSeparator:rest -> (True, rest)+        _                                                   -> (False, pat)++begMatch opts pat s =+   if not (null s) && isExtSeparator (head s) && not (matchDotsImplicitly opts)       then case pat of-                ExtSeparator:pat' -> match' pat' (tail s)+                ExtSeparator:pat' -> match' opts pat' (tail s)                 _                 -> False-      else match' pat s+      else match' opts pat s -match' []                        s  = null s-match' (AnyNonPathSeparator:s)   "" = null s-match' _                         "" = False-match' (Literal l       :xs) (c:cs) =                 l == c  && match'   xs cs-match' ( ExtSeparator   :xs) (c:cs) =       isExtSeparator c  && match'   xs cs-match' (PathSeparator   :xs) (c:cs) =      isPathSeparator c  && begMatch xs cs-match' (NonPathSeparator:xs) (c:cs) = not (isPathSeparator c) && match'   xs cs-match' (CharRange b rng :xs) (c:cs) =-   not (isPathSeparator c) &&-   any (either (== c) (`inRange` c)) rng == b &&-   match' xs cs+match' _ []                        s  = null s+match' _ (AnyNonPathSeparator:s)   "" = null s+match' _ _                         "" = False+match' o (Literal l       :xs) (c:cs) =           l == c  && match' o xs cs+match' o ( ExtSeparator   :xs) (c:cs) = isExtSeparator c  && match' o xs cs+match' o (NonPathSeparator:xs) (c:cs) =+   not (isPathSeparator c) && match' o xs cs -match' (OpenRange lo hi :xs) path =+match' o (PathSeparator   :xs) (c:cs) =+   isPathSeparator c && begMatch o xs (dropWhile isPathSeparator cs)++match' o (CharRange b rng :xs) (c:cs) =+   let rangeMatch r =+          either (== c) (`inRange` c) r ||+             -- See comment near Base.tokToLower for an explanation of why we+             -- do this+             if ignoreCase o+                then either (== toUpper c) (`inRange` toUpper c) r+                else False+    in not (isPathSeparator c) &&+       any rangeMatch rng == b &&+       match' o xs cs++match' o (OpenRange lo hi :xs) path =    let       (lzNum,cs) = span isDigit path       num        = dropLeadingZeroes lzNum@@ -59,24 +102,24 @@             -- We want to try matching x against each of 123, 12, and 1.             -- 12 and 1 are in numChoices already, but we need to add (num,"")             -- manually.-            any (\(n,rest) -> inOpenRange lo hi n && match' xs (rest ++ cs))+            any (\(n,rest) -> inOpenRange lo hi n && match' o xs (rest ++ cs))                 ((num,"") : numChoices) -match' again@(AnyNonPathSeparator:xs) path@(c:cs) =-   match' xs path || (if isPathSeparator c then False else match' again cs)+match' o again@(AnyNonPathSeparator:xs) path@(c:cs) =+   match' o xs path || (if isPathSeparator c then False else match' o again cs) -match' again@(AnyDirectory:xs) path =-   let parts   = pathParts path-       matches = any (match' xs) parts || any (match' again) (tail parts)-    in if null xs+match' o again@(AnyDirectory:xs) path =+   let parts   = pathParts (dropWhile isPathSeparator path)+       matches = any (match' o xs) parts || any (match' o again) (tail parts)+    in if null xs && not (matchDotsImplicitly o)           --  **/ shouldn't match foo/.bar, so check that remaining bits don't           -- start with .           then all (not.isExtSeparator.head) (init parts) && matches           else matches -match' (LongLiteral len s:xs) path =+match' o (LongLiteral len s:xs) path =    let (pre,cs) = splitAt len path-    in pre == s && match' xs cs+    in pre == s && match' o xs cs  -- Does the actual open range matching: finds whether the third parameter -- is between the first two or not.
− System/FilePath/Glob/Optimize.hs
@@ -1,138 +0,0 @@--- File created: 2008-10-10 13:29:17--module System.FilePath.Glob.Optimize (optimize) where--import Data.List       (find, sortBy)-import System.FilePath (isPathSeparator, isExtSeparator)--import System.FilePath.Glob.Base-import System.FilePath.Glob.Utils-   ( isLeft, fromLeft-   , increasingSeq-   , addToRange, overlap)--optimize :: Pattern -> Pattern-optimize = liftP (fin . go . pre)- where-   -- ./ at beginning -> nothing-   pre (ExtSeparator:PathSeparator:xs) = pre xs-   pre                             xs  = xs--   fin [] = []--   -- Literals to LongLiteral-   -- Has to be done here: we can't backtrack in go, but some cases might-   -- result in consecutive Literals being generated.-   -- E.g. "a[b]".-   fin (x:y:xs) | isLiteral x && isLiteral y =-      let (ls,rest) = span isLiteral xs-       in fin $ LongLiteral (length ls + 2)-                      (foldr (\(Literal a) -> (a:)) [] (x:y:ls))-                : rest--   -- concatenate LongLiterals-   -- Has to be done here because LongLiterals are generated above.-   ---   -- So one could say that we have one pass (go) which flattens everything as-   -- much as it can and one pass (fin) which concatenates what it can.-   fin (LongLiteral l1 s1 : LongLiteral l2 s2 : xs) =-      fin $ LongLiteral (l1+l2) (s1++s2) : xs--   fin (LongLiteral l s : Literal c : xs) =-      fin $ LongLiteral (l+1) (s++[c]) : xs--   fin (x:xs) = x : fin xs--   go [] = []-   go (x@(CharRange _ _) : xs) =-      case optimizeCharRange x of-           x'@(CharRange _ _) -> x' : go xs-           x'                 -> go (x':xs)--   -- /./ -> /-   go (PathSeparator:ExtSeparator:xs@(PathSeparator:_)) = go xs--   -- <a-a> -> a-   go (OpenRange (Just a) (Just b):xs)-      | a == b = LongLiteral (length a) a : go xs--   -- <a-b> -> [a-b]-   -- a and b are guaranteed non-null-   go (OpenRange (Just [a]) (Just [b]):xs)-      | b > a = go $ CharRange True [Right (a,b)] : xs--   go (x:xs) =-      case find ($ x) compressors of-           Just c  -> x : go (dropWhile c xs)-           Nothing -> x : go xs--   compressors = [isStar, isSlash, isStarSlash, isAnyNumber]--   isLiteral   (Literal _)                 = True-   isLiteral   _                           = False-   isStar      AnyNonPathSeparator         = True-   isStar      _                           = False-   isSlash     PathSeparator               = True-   isSlash     _                           = False-   isStarSlash AnyDirectory                = True-   isStarSlash _                           = False-   isAnyNumber (OpenRange Nothing Nothing) = True-   isAnyNumber _                           = False--optimizeCharRange :: Token -> Token-optimizeCharRange (CharRange b_ rs) = fin b_ . go . sortCharRange $ rs- where-   fin True [Left  c] | not (isPathSeparator c || isExtSeparator c) = Literal c-   fin True [Right r] | r == (minBound,maxBound) = NonPathSeparator-   fin b x = CharRange b x--   go [] = []--   go (x@(Left c) : xs) =-      case xs of-           [] -> [x]-           y@(Left d) : ys-              -- [aaaaa] -> [a]-              | c == d      -> go$ Left c : ys-              | d == succ c ->-                 let (ls,rest)        = span isLeft xs -- start from y-                     (catable,others) = increasingSeq (map fromLeft ls)-                     range            = (c, head catable)--                  in -- three (or more) Lefts make a Right-                     if null catable || null (tail catable)-                        then x : y : go ys-                        -- [abcd] -> [a-d]-                        else go$ Right range : map Left others ++ rest--              | otherwise -> x : go xs--           Right r : ys ->-              case addToRange r c of-                   -- [da-c] -> [a-d]-                   Just r' -> go$ Right r' : ys-                   Nothing -> x : go xs--   go (x@(Right r) : xs) =-      case xs of-           [] -> [x]-           Left c : ys ->-              case addToRange r c of-                   -- [a-cd] -> [a-d]-                   Just r' -> go$ Right r' : ys-                   Nothing -> x : go xs--           Right r' : ys ->-              case overlap r r' of-                   -- [a-cb-d] -> [a-d]-                   Just o  -> go$ Right o : ys-                   Nothing -> x : go xs-optimizeCharRange _ = error "Glob.optimizeCharRange :: internal error"--sortCharRange :: [Either Char (Char,Char)] -> [Either Char (Char,Char)]-sortCharRange = sortBy cmp- where-   cmp (Left   a)    (Left   b)    = compare a b-   cmp (Left   a)    (Right (b,_)) = compare a b-   cmp (Right (a,_)) (Left   b)    = compare a b-   cmp (Right (a,_)) (Right (b,_)) = compare a b
+ System/FilePath/Glob/Primitive.hs view
@@ -0,0 +1,57 @@+-- File created: 2009-01-17++-- |A number of primitives from which complete 'Pattern's may be constructed.+--+-- Using this together with the functions provided by the 'Monoid' instance of+-- 'Pattern' allows for direct manipulation of 'Pattern's beyond what can be+-- done with just the 'compile' family of functions. And of course you don't+-- have to go via 'String's if you use these.+module System.FilePath.Glob.Primitive+   ( literal+   , singleWildcard, wildcard, recursiveWildcard+   , charRange, numberRange+   ) where++import System.FilePath (isPathSeparator, isExtSeparator)++import System.FilePath.Glob.Base (Pattern(..), Token(..), optimize)++-- |A 'Pattern' which matches the given 'String' literally.+--+-- Handles any embedded path and extension separators.+literal :: String -> Pattern+literal = optimize . Pattern . map f+ where+   f c | isPathSeparator c = PathSeparator+       | isExtSeparator c  = ExtSeparator+       | otherwise         = Literal c++-- |Matches any single character except a path separator: corresponds to the+-- @?@ operator.+singleWildcard :: Pattern+singleWildcard = Pattern [NonPathSeparator]++-- |Matches any number of characters up to a path separator: corresponds to the+-- @*@ operator.+wildcard :: Pattern+wildcard = Pattern [AnyNonPathSeparator]++-- |Matches any number of characters including path separators: corresponds to+-- the @**/@ operator.+recursiveWildcard :: Pattern+recursiveWildcard = Pattern [AnyDirectory]++-- |Matches a single character if it is within the (inclusive) range in any+-- 'Right' or if it is equal to the character in any 'Left'. Corresponds to the+-- @[]@, @[^]@ and @[!]@ operators.+--+-- If the given 'Bool' is 'False', the result of the match is inverted: the+-- match succeeds if the character does /not/ match according to the above+-- rules.+charRange :: Bool -> [Either Char (Char,Char)] -> Pattern+charRange b rs = optimize $ Pattern [CharRange b rs]++-- |Matches a number in the given range, which may be open, half-open, or+-- closed. Corresponds to the @\<\>@ operator.+numberRange :: Maybe Integer -> Maybe Integer -> Pattern+numberRange a b = Pattern [OpenRange (fmap show a) (fmap show b)]
+ System/FilePath/Glob/Simplify.hs view
@@ -0,0 +1,42 @@+-- File created: 2009-01-30 14:54:14++module System.FilePath.Glob.Simplify (simplify) where++import System.FilePath.Glob.Base (Pattern(..), Token(..), liftP)++-- |Simplifies a 'Pattern' object: removes redundant @\"./\"@, for instance.+-- The resulting 'Pattern' matches the exact same input as the original one,+-- with some differences:+--+-- * The output of 'globDir' will differ: for example, globbing for @\"./\*\"@+--   gives @\"./foo\"@, but after simplification this'll be only @\"foo\"@.+--+-- * Decompiling the simplified 'Pattern' will obviously not give the original.+--+-- * The simplified 'Pattern' is a bit faster to match with and uses less+--   memory, since some redundant data is removed.+--+-- For the last of the above reasons, if you're performance-conscious and not+-- using 'globDir', you should always 'simplify' after calling 'compile'.+simplify :: Pattern -> Pattern+simplify = liftP (go . pre)+ where+   -- ./ at beginning -> nothing (any number of /'s)+   pre (ExtSeparator:PathSeparator:xs) = pre (dropWhile isSlash xs)+   pre                             xs  = xs++   go [] = []++   -- /./ -> /+   go (PathSeparator:ExtSeparator:xs@(PathSeparator:_)) = go xs++   go (x:xs) =+      if isSlash x+         then let (compressed,ys) = span isSlash xs+               in if null compressed+                     then x : go ys+                     else go (x : ys)+         else x : go xs++   isSlash PathSeparator = True+   isSlash _             = False
tests/Main.hs view
@@ -5,9 +5,12 @@ import System.Environment (getArgs) import Test.Framework +import qualified Tests.Compiler   as Compiler+import qualified Tests.Instances  as Instances import qualified Tests.Matcher    as Matcher import qualified Tests.Optimizer  as Optimizer import qualified Tests.Regression as Regression+import qualified Tests.Simplifier as Simplifier import qualified Tests.Utils      as Utils  main = do@@ -18,9 +21,12 @@       , args       ] -tests = concat+tests =    [ Regression.tests+   , Utils.tests+   , Compiler.tests    , Matcher.tests    , Optimizer.tests-   , Utils.tests+   , Simplifier.tests+   , Instances.tests    ]
tests/Tests/Base.hs view
@@ -1,24 +1,29 @@ -- File created: 2008-10-10 22:03:00 -module Tests.Base (PString(unPS), Path(unP), fromRight, isRight) where+module Tests.Base ( PString(unPS), Path(unP), COpts(unCOpts)+                  , fromRight, isRight+                  ) where +import System.FilePath (extSeparator, pathSeparators) import Test.QuickCheck -import System.FilePath (extSeparator, pathSeparators)+import System.FilePath.Glob.Base (CompOptions(..))  import Utils (fromRight, isRight) -newtype PString = PatString { unPS :: String } deriving Show-newtype Path    = Path      { unP  :: String } deriving Show+newtype PString = PatString { unPS    :: String } deriving Show+newtype Path    = Path      { unP     :: String } deriving Show+newtype COpts   = COpts     { unCOpts :: CompOptions } deriving Show -alpha = extSeparator : pathSeparators ++ "-^!" ++ ['a'..'z'] ++ ['0'..'9']+alpha0 = extSeparator : "-^!" ++ ['a'..'z'] ++ ['0'..'9']+alpha  = pathSeparators ++ alpha0  instance Arbitrary PString where    arbitrary = sized $ \size -> do       let xs =              (1, return "**/") :              map (\(a,b) -> (a*100,b))-             [ (40, plain)+             [ (40, plain alpha)              , (20, return "?")              , (20, charRange)              , (10, return "*")@@ -30,18 +35,23 @@  instance Arbitrary Path where    arbitrary = sized $ \size -> do-      s <- mapM (const plain) [1..size `mod` 16]+      s <- mapM (const $ plain alpha) [1..size `mod` 16]       return.Path $ concat s -plain = sized $ \size -> do-   s <- mapM (const $ elements alpha) [0..size `mod` 3]+instance Arbitrary COpts where+   arbitrary = do+      [a,b,c,d,e,f] <- vector 6+      return.COpts $ CompOptions a b c d e f False++plain from = sized $ \size -> do+   s <- mapM (const $ elements from) [0..size `mod` 3]    return s  charRange = do-   s <- plain+   s <- plain alpha0    if s `elem` ["^","!"]       then do-         s' <- plain+         s' <- plain alpha0          return$ "[" ++ s ++ s' ++ "]"       else          return$ "[" ++ s ++       "]"
+ tests/Tests/Compiler.hs view
@@ -0,0 +1,22 @@+-- File created: 2009-01-30 13:26:51++module Tests.Compiler (tests) where++import Test.Framework+import Test.Framework.Providers.QuickCheck++import System.FilePath.Glob.Base (tryCompileWith, compile, decompile)++import Tests.Base++tests = testGroup "Compiler"+   [ testProperty "compile-decompile-1" prop_compileDecompile1+   ]++-- compile . decompile should be the identity function+prop_compileDecompile1 o s =+   let opt   = unCOpts o+       epat1 = tryCompileWith opt (unPS s)+       pat1  = fromRight epat1+       pat2  = compile . decompile $ pat1+    in isRight epat1 && pat1 == pat2
+ tests/Tests/Instances.hs view
@@ -0,0 +1,59 @@+-- File created: 2009-01-30 15:01:02++module Tests.Instances (tests) where++import Data.Monoid (mempty, mappend)+import Test.Framework+import Test.Framework.Providers.QuickCheck+import Test.QuickCheck ((==>))++import System.FilePath.Glob.Base (tryCompileWith)+import System.FilePath.Glob.Match+import System.FilePath.Glob.Simplify++import Tests.Base++tests = testGroup "Instances"+   [ testProperty "monoid-law-1" prop_monoidLaw1+   , testProperty "monoid-law-2" prop_monoidLaw2+   , testProperty "monoid-law-3" prop_monoidLaw3+   , testProperty "monoid-4"     prop_monoid4+   ]++-- The monoid laws: associativity...+prop_monoidLaw1 opt x y z =+   let o       = unCOpts opt+       es      = map (tryCompileWith o . unPS) [x,y,z]+       [a,b,c] = map fromRight es+    in all isRight es && mappend a (mappend b c) == mappend (mappend a b) c++-- ... left identity ...+prop_monoidLaw2 opt x =+   let o = unCOpts opt+       e = tryCompileWith o (unPS x)+       a = fromRight e+    in isRight e && mappend mempty a == a++-- ... and right identity.+prop_monoidLaw3 opt x =+   let o = unCOpts opt+       e = tryCompileWith o (unPS x)+       a = fromRight e+    in isRight e && mappend a mempty == a++-- mappending two Patterns should be equivalent to appending the original+-- strings they came from and compiling that+--+-- (notice: relies on the fact that our Arbitrary instance doesn't generate+-- unclosed [] or <>; we only check for **/)+prop_monoid4 opt x y =+   let o     = unCOpts opt+       es    = map (tryCompileWith o . unPS) [x,y]+       [a,b] = map fromRight es+       cat1  = mappend a b+       cat2  = tryCompileWith o (unPS x ++ unPS y)+       last2 = take 2 . reverse . unPS $ x+       head2 = take 2 . unPS $ y+    in     (last2 /= "**" && take 1 head2 /= "/")+        && (take 1 last2 /= "*" && take 2 head2 /= "*/")+       ==> all isRight es && isRight cat2 && cat1 == fromRight cat2
tests/Tests/Matcher.hs view
@@ -5,22 +5,26 @@ import Control.Monad (ap) import Test.Framework import Test.Framework.Providers.QuickCheck+import Test.QuickCheck ((==>)) -import System.FilePath.Glob.Compile+import System.FilePath (isExtSeparator, isPathSeparator)+import System.FilePath.Glob.Base import System.FilePath.Glob.Match  import Tests.Base -tests =-   [ testGroup "Matcher"-       [ testProperty "match-1" prop_match1-       ]+tests = testGroup "Matcher"+   [ testProperty "match-1" prop_match1+   , testProperty "match-2" prop_match2+   , testProperty "match-3" prop_match3    ]  -- ./foo should be equivalent to foo in both path and pattern-prop_match1 p s =-   let ep   = tryCompile (unPS p)-       ep'  = tryCompile ("./" ++ unPS p)+-- ... but not for the pattern if it starts with /+prop_match1 o p_ s =+   let p    = dropWhile isPathSeparator (unPS p_)+       ep   = tryCompileWith (unCOpts o) p+       ep'  = tryCompileWith (unCOpts o) ("./" ++ p)        pat  = fromRight ep        pat' = fromRight ep'        pth  = unP s@@ -28,9 +32,19 @@     in and [ isRight ep, isRight ep'            , ( all (uncurry (==)) . (zip`ap`tail) $                   [ match pat  pth-                  , match pat  pth' +                  , match pat  pth'                   , match pat' pth                   , match pat' pth'                   ]-             ) || null (unPS p)+             ) || null p            ]++-- [/] shouldn't match anything+prop_match2 = not . match (compile "[/]")  . take 1 . unP++-- [!/] is like ?+prop_match3 p_ =+   let p = unP p_+       ~(x:_) = p+    in not (null p || isPathSeparator x || isExtSeparator x)+       ==> match (compile "[!/]") [x]
tests/Tests/Optimizer.hs view
@@ -5,28 +5,25 @@ import Test.Framework import Test.Framework.Providers.QuickCheck -import System.FilePath.Glob.Compile-import System.FilePath.Glob.Optimize+import System.FilePath.Glob.Base  (tokenize, optimize) import System.FilePath.Glob.Match  import Tests.Base -tests =-   [ testGroup "Optimizer"-       [ testProperty "optimize-1" prop_optimize1-       , testProperty "optimize-2" prop_optimize2-       ]+tests = testGroup "Optimizer"+   [ testProperty "optimize-1" prop_optimize1+   , testProperty "optimize-2" prop_optimize2    ]  -- Optimizing twice should give the same result as optimizing once-prop_optimize1 s =-   let pat = tokenize (unPS s)+prop_optimize1 o s =+   let pat = tokenize (unCOpts o) (unPS s)        xs = iterate optimize (fromRight pat)     in isRight pat && xs !! 1 == xs !! 2  -- Optimizing shouldn't affect whether a match succeeds-prop_optimize2 p s =-   let x   = tokenize (unPS p)+prop_optimize2 o p s =+   let x   = tokenize (unCOpts o) (unPS p)        pat = fromRight x        pth = unP s     in isRight x && match pat pth == match (optimize pat) pth
tests/Tests/Regression.hs view
@@ -6,20 +6,38 @@ import Test.Framework.Providers.HUnit import Test.HUnit.Base -import System.FilePath.Glob.Compile+import System.FilePath.Glob.Base import System.FilePath.Glob.Match -tests =-   [ testGroup "Regression" $-        flip map testCases $ \t@(b,p,s) ->-            testCase (nameTest t) . assertBool "failed" $+tests = testGroup "Regression"+   [ testGroup "Matching/compiling" .+        flip map matchCases $ \t@(b,p,s) ->+            tc (nameMatchTest t) $                match (compile p) s == b+   , testGroup "Specific options" .+        flip map matchWithCases $ \t@(b,co,mo,p,s) ->+           tc (nameMatchTest (b,p,s)) $+              matchWith mo (compileWith co p) s == b+   , testGroup "Decompilation" .+        flip map decompileCases $ \(n,orig,s) ->+           tc n $ decompile (compile orig) == s    ]+ where+   tc n = testCase n . assert -nameTest (True ,p,s) = show p ++ " matches " ++ show s-nameTest (False,p,s) = show p ++ " doesn't match " ++ show s+nameMatchTest (True ,p,s) = show p ++ " matches " ++ show s+nameMatchTest (False,p,s) = show p ++ " doesn't match " ++ show s -testCases =+decompileCases =+   [ ("range-compression-1", "[*]",   "[*]")+   , ("range-compression-2", "[.]",   "[.]")+   , ("range-compression-3", "**[/]", "*[/]")+   , ("range-compression-4", "x[.]",  "x[.]")+   , ("range-compression-5", "[^~-]", "[^~-]")+   , ("range-compression-6", "[^!-]", "[^!-]")+   ]++matchCases =    [ (True , "*"          , "")    , (True , "**"         , "")    , (True , "asdf"       , "asdf")@@ -69,11 +87,46 @@    , (False, "[b-a]"      , "a")    , (False, "<4-3>"      , "3")    , (True , "[]-b]"      , "]")-   , (True , "[]-b]"      , "-")+   , (False, "[]-b]"      , "-")    , (True , "[]-b]"      , "b")-   , (False, "[]-b]"      , "a")+   , (True , "[]-b]"      , "a")+   , (True , "[]-]"       , "]")+   , (True , "[]-]"       , "-")+   , (True , "[#-[]"      , "&")    , (False, "[^x]"       , "/")    , (False, "[/]"        , "/")    , (True , "a[^x]"      , "a.")    , (True , "a[.]"       , "a.")+   , (False, ".//a"       , "/a")+   , (True,  ".//a"       , "a")+   , (True,  ".//a"       , "./a")+   , (True , ".*/a"       , "./a")+   , (True , ".*/a"       , "../a")+   , (True , ".*/a"       , ".foo/a")+   , (True , ".**/a"      , ".foo/a")+   , (False, ".**/a"      , "../a")+   , (False, ".**/a"      , "./a")+   , (False, ".**/a"      , "a")+   , (True , ".**/a"      , ".foo/a")+   , (True , "f**/a"      , "foo/a")+   , (True , "f**/"       , "f/")+   , (True , "f**/"       , "f///")+   , (True , "f**/x"      , "f///x")+   , (True , "f/"         , "f///")+   , (True , "f/x"        , "f///x")+   , (True , "[]"         , "[]")+   , (True , "[!]"        , "[!]")+   , (True , "[^]"        , "[^]")+   , (True , "[abc"       , "[abc")+   , (True , "<abc"       , "<abc")+   , (True , "<1-"        , "<1-")+   , (True , "[^-~]"      , "x")+   , (False, "[^-~]"      , "X")+   , (False, "[^^-~]"     , "x")+   , (False, "[^-]"       , "-")+   ]++matchWithCases =+   [ (True , compDefault, matchDefault { ignoreCase = True }, "[@-[]", "a")+   , (True , compPosix  , matchDefault                      , "a[/]b", "a[/]b")    ]
+ tests/Tests/Simplifier.hs view
@@ -0,0 +1,30 @@+-- File created: 2009-01-24 13:02:48++module Tests.Simplifier (tests) where++import Test.Framework+import Test.Framework.Providers.QuickCheck++import System.FilePath.Glob.Base (tryCompileWith)+import System.FilePath.Glob.Match+import System.FilePath.Glob.Simplify++import Tests.Base++tests = testGroup "Simplifier"+   [ testProperty "simplify-1" prop_simplify1+   , testProperty "simplify-2" prop_simplify2+   ]++-- Simplifying twice should give the same result as simplifying once+prop_simplify1 o s =+   let pat = tryCompileWith (unCOpts o) (unPS s)+       xs = iterate simplify (fromRight pat)+    in isRight pat && xs !! 1 == xs !! 2++-- Simplifying shouldn't affect whether a match succeeds+prop_simplify2 p o s =+   let x   = tryCompileWith (unCOpts o) (unPS p)+       pat = fromRight x+       pth = unP s+    in isRight x && match pat pth == match (simplify pat) pth
tests/Tests/Utils.hs view
@@ -11,12 +11,10 @@  import Utils -tests =-   [ testGroup "Utils"-      [ testProperty "overlapperLosesNoInfo" prop_overlapperLosesNoInfo-      , testProperty "increasingSeq"         prop_increasingSeq-      , testProperty "addToRange"            prop_addToRange-      ]+tests = testGroup "Utils"+   [ testProperty "overlapperLosesNoInfo" prop_overlapperLosesNoInfo+   , testProperty "increasingSeq"         prop_increasingSeq+   , testProperty "addToRange"            prop_addToRange    ]  validateRange (a,b) = if b > a then (a,b) else (b,a)