-
Notifications
You must be signed in to change notification settings - Fork 739
Implement bash (with globstar) style globbing #2522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
43dd0de
9434591
8f2e745
2bb20fa
72d038d
186c320
75a21e8
183a0aa
87678f0
829ccf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| module Distribution.Utils.Glob | ||
| ( Glob(..) | ||
| , isRealGlob | ||
| , parseFileGlob | ||
| , isMatch | ||
| , realIsMatch | ||
| ) | ||
| where | ||
|
|
||
| import Distribution.Utils.Glob.Type | ||
| import Distribution.Utils.Glob.Parse | ||
| import Distribution.Utils.Glob.Match |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| module Distribution.Utils.Glob.Match where | ||
|
|
||
| import Control.Monad | ||
| ( (>=>) ) | ||
| import Data.Maybe | ||
| ( listToMaybe ) | ||
| import Data.List | ||
| ( stripPrefix ) | ||
| import Distribution.Utils.Safe | ||
| ( tailMay ) | ||
| import Distribution.Utils.Glob.Type | ||
|
|
||
| isMatch :: Glob -> FilePath -> Bool | ||
| isMatch (Glob realGlob) fp = realIsMatch realGlob fp | ||
| isMatch (NoGlob fp') fp = fp' == fp | ||
|
|
||
| realIsMatch :: RealGlob -> FilePath -> Bool | ||
| realIsMatch (RealGlob parts) fp = isMatch' True parts (toSegments fp) | ||
|
|
||
| toSegments :: FilePath -> [String] | ||
| toSegments = filter (not . null) . splitOn '/' | ||
|
|
||
| -- Not quite the same as the function from Data.List.Split, but this allows | ||
| -- for a simpler implementation | ||
| splitOn :: Eq a => a -> [a] -> [[a]] | ||
| splitOn _ [] = [] | ||
| splitOn splitter list = | ||
| let (next, rest) = span (/= splitter) list | ||
| in next : splitOn splitter (drop 1 rest) | ||
|
|
||
| -- | Given: | ||
| -- * A Bool which records whether we are at the beginning of the current | ||
| -- segment | ||
| -- * A list of GlobParts | ||
| -- * A list of path segments in a file path | ||
| -- Return whether the glob parts list matches the file path. | ||
| isMatch' :: Bool -> [GlobPart] -> [String] -> Bool | ||
| isMatch' _ (Literal l : parts) (seg : segs) = | ||
| case stripPrefix l seg of | ||
| Just seg' -> isMatch' False parts (seg' : segs) | ||
| Nothing -> False | ||
| isMatch' _ (PathSeparator : parts) (seg : segs) | ||
| | seg == "" = isMatch' True parts segs | ||
| | otherwise = False | ||
| isMatch' _ (CharList cs : parts) ((h:tl) : segs) = | ||
| if charListIsMatch cs h | ||
| then isMatch' False parts (tl : segs) | ||
| else False | ||
| isMatch' _ (CharListComplement cs : parts) ((h:tl) : segs) = | ||
| if charListIsMatch cs h | ||
| then False | ||
| else isMatch' False parts (tl : segs) | ||
| isMatch' startSegment (WildOne : parts) ((h:tl) : segs) | ||
| | startSegment && h == '.' = False | ||
| | otherwise = isMatch' False parts (tl : segs) | ||
| isMatch' startSegment (WildMany : parts) segs | ||
| | startSegment && (listToMaybe >=> listToMaybe) segs == Just '.' = False | ||
| | otherwise = | ||
| case segs of | ||
| first : rest -> | ||
| let candidates = map (:rest) (iterateWhile tailMay first) | ||
| in any (isMatch' False parts) candidates | ||
| [] -> | ||
| isMatch' startSegment parts segs | ||
| isMatch' startSegment (WildManyRecursive : parts) segs | ||
| | startSegment && (listToMaybe >=> listToMaybe) segs == Just '.' = False | ||
| | otherwise = | ||
| anyCandidates || handlePathSep | ||
| where | ||
| anyCandidates = | ||
| any (\(start, segs') -> isMatch' start parts segs') candidates | ||
| candidates = iterateWhile (drop1' . snd) (False, segs) | ||
| handlePathSep = | ||
| case parts of | ||
| PathSeparator : parts' -> isMatch' startSegment parts' segs | ||
| _ -> False | ||
|
|
||
| isMatch' startSegment (Choice gs : parts) segs = | ||
| any (\g -> isMatch' startSegment (g ++ parts) segs) gs | ||
| isMatch' _ [] [""] = True | ||
| isMatch' _ _ _ = False | ||
|
|
||
| charListIsMatch :: [CharListPart] -> Char -> Bool | ||
| charListIsMatch parts c = any (matches c) parts | ||
| where | ||
| matches x (CharLiteral y) = x == y | ||
| matches x (Range start end) = start <= x && x <= end | ||
|
|
||
| -- | Drop one character from a list of path segments, or if the first segment | ||
| -- is empty, move on to the next segment. | ||
| drop1' :: [String] -> Maybe (Bool, [String]) | ||
| drop1' [] = Nothing | ||
| drop1' ("" : segs) = Just (True, segs) | ||
| drop1' (seg : segs) = Just (False, drop 1 seg : segs) | ||
|
|
||
| -- | Generate a list of values obtained by repeatedly applying a function | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can just use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tried this and it's a bit awkward with In particular, I also can't see a way of rewriting the call sites of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, Rewriting the call sites is easy if you pass in a function of type
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite - It's just occurred to me that That does still leave one call site of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, the less code, the better.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, yeah, you're right, you need something like |
||
| -- to an initial value, until it stops returning Just. | ||
| iterateWhile :: (a -> Maybe a) -> a -> [a] | ||
| iterateWhile f x = x : rest | ||
| where | ||
| rest = case f x of | ||
| Just y -> iterateWhile f y | ||
| Nothing -> [] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which way it's "not quite the same"? Why not just copy the function from
Data.List.SplittoDistribution.Simple.Utils?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Data.List.Split.splitOnhas typeEq a => [a] -> [a] -> [[a]]and allows splitting on sublists rather than just individual items. Since we only need the latter here, I thought it would make more sense to just implement this here. I guess I could explain this a bit more in the comments?Copying the implementation from
Data.List.Splitwould involve copying 5 or 6 separate functions, and they all have quite a lot more generality than is needed here - I think it would make this code a bit confusing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this function is equivalent to
Data.List.Split.endBy- let's just call it by that name.