16
16
module Distribution.Simple.Glob (
17
17
matchFileGlob ,
18
18
matchDirFileGlob ,
19
+ matchDirFileGlob' ,
19
20
fileGlobMatches ,
20
21
parseFileGlob ,
21
22
explainGlobSyntaxError ,
22
23
GlobSyntaxError (.. ),
23
- GlobPat ,
24
+ Glob ,
24
25
) where
25
26
26
27
import Prelude ()
@@ -30,6 +31,7 @@ import Distribution.Simple.Utils
30
31
import Distribution.Verbosity
31
32
import Distribution.Version
32
33
34
+ import System.Directory (getDirectoryContents , doesFileExist )
33
35
import System.FilePath (joinPath , splitExtensions , splitDirectories , takeExtensions , (</>) )
34
36
35
37
-- Note throughout that we use splitDirectories, not splitPath. On
@@ -84,30 +86,35 @@ explainGlobSyntaxError filepath VersionDoesNotSupportGlob =
84
86
85
87
data IsRecursive = Recursive | NonRecursive
86
88
87
- data GlobPat = PatStem String GlobPat
88
- -- ^ A single subdirectory component + remainder.
89
- | PatMatch IsRecursive String
90
- -- ^ First argument: Is this a @**/*.ext@ pattern?
91
- -- Second argument: the extensions to accept.
92
- | PatLit FilePath
93
- -- ^ Literal file name.
89
+ data Glob
90
+ = GlobStem String Glob
91
+ -- ^ A single subdirectory component + remainder.
92
+ | GlobFinal GlobFinal
94
93
95
- fileGlobMatches :: GlobPat -> FilePath -> Bool
94
+ data GlobFinal
95
+ = FinalMatch IsRecursive String
96
+ -- ^ First argument: Is this a @**/*.ext@ pattern?
97
+ -- Second argument: the extensions to accept.
98
+ | FinalLit FilePath
99
+ -- ^ Literal file name.
100
+
101
+ fileGlobMatches :: Glob -> FilePath -> Bool
96
102
fileGlobMatches pat = fileGlobMatchesSegments pat . splitDirectories
97
103
98
- fileGlobMatchesSegments :: GlobPat -> [FilePath ] -> Bool
104
+ fileGlobMatchesSegments :: Glob -> [FilePath ] -> Bool
99
105
fileGlobMatchesSegments _ [] = False
100
106
fileGlobMatchesSegments pat (seg : segs) = case pat of
101
- PatStem dir pat' ->
107
+ GlobStem dir pat' ->
102
108
dir == seg && fileGlobMatchesSegments pat' segs
103
- PatMatch Recursive ext ->
104
- ext == takeExtensions (last $ seg: segs)
105
- PatMatch NonRecursive ext ->
106
- null segs && ext == takeExtensions seg
107
- PatLit filename ->
108
- null segs && filename == seg
109
-
110
- parseFileGlob :: Version -> FilePath -> Either GlobSyntaxError GlobPat
109
+ GlobFinal final -> case final of
110
+ FinalMatch Recursive ext ->
111
+ ext == takeExtensions (last $ seg: segs)
112
+ FinalMatch NonRecursive ext ->
113
+ null segs && ext == takeExtensions seg
114
+ FinalLit filename ->
115
+ null segs && filename == seg
116
+
117
+ parseFileGlob :: Version -> FilePath -> Either GlobSyntaxError Glob
111
118
parseFileGlob version filepath = case reverse (splitDirectories filepath) of
112
119
[] ->
113
120
Left EmptyGlob
@@ -118,31 +125,43 @@ parseFileGlob version filepath = case reverse (splitDirectories filepath) of
118
125
| null ext -> Left NoExtensionOnStar
119
126
| otherwise -> Right ext
120
127
_ -> Left LiteralFileNameGlobStar
121
- foldM addStem (PatMatch Recursive ext) segments
128
+ foldM addStem (GlobFinal $ FinalMatch Recursive ext) segments
122
129
| otherwise -> Left VersionDoesNotSupportGlobStar
123
130
(filename : segments) -> do
124
131
pat <- case splitExtensions filename of
125
132
(" *" , ext) | not allowGlob -> Left VersionDoesNotSupportGlob
126
133
| ' *' `elem` ext -> Left StarInExtension
127
134
| null ext -> Left NoExtensionOnStar
128
- | otherwise -> Right (PatMatch NonRecursive ext)
135
+ | otherwise -> Right (FinalMatch NonRecursive ext)
129
136
(_, ext) | ' *' `elem` ext -> Left StarInExtension
130
137
| ' *' `elem` filename -> Left StarInFileName
131
- | otherwise -> Right (PatLit filename)
132
- foldM addStem pat segments
138
+ | otherwise -> Right (FinalLit filename)
139
+ foldM addStem ( GlobFinal pat) segments
133
140
where
134
141
allowGlob = version >= mkVersion [1 ,6 ]
135
142
allowGlobStar = version >= mkVersion [3 ,0 ]
136
143
addStem pat seg
137
144
| ' *' `elem` seg = Left StarInDirectory
138
- | otherwise = Right (PatStem seg pat)
145
+ | otherwise = Right (GlobStem seg pat)
139
146
140
147
matchFileGlob :: Verbosity -> Version -> FilePath -> IO [FilePath ]
141
148
matchFileGlob verbosity version = matchDirFileGlob verbosity version " ."
142
149
143
- -- The returned values do not include the supplied @dir@ prefix.
150
+ -- | Like 'matchDirFileGlob'', but will 'die'' when the glob matches
151
+ -- no files.
144
152
matchDirFileGlob :: Verbosity -> Version -> FilePath -> FilePath -> IO [FilePath ]
145
- matchDirFileGlob verbosity version rawDir filepath = case parseFileGlob version filepath of
153
+ matchDirFileGlob verbosity version dir filepath = do
154
+ matches <- matchDirFileGlob' verbosity version dir filepath
155
+ when (null matches) $ die' verbosity $
156
+ " filepath wildcard '" ++ filepath
157
+ ++ " ' does not match any files."
158
+ return matches
159
+
160
+ -- | Match files against a glob, starting in a directory.
161
+ --
162
+ -- The returned values do not include the supplied @dir@ prefix.
163
+ matchDirFileGlob' :: Verbosity -> Version -> FilePath -> FilePath -> IO [FilePath ]
164
+ matchDirFileGlob' verbosity version rawDir filepath = case parseFileGlob version filepath of
146
165
Left err -> die' verbosity $ explainGlobSyntaxError filepath err
147
166
Right pat -> do
148
167
-- The default data-dir is null. Our callers -should- be
@@ -159,17 +178,22 @@ matchDirFileGlob verbosity version rawDir filepath = case parseFileGlob version
159
178
-- ".". Walking the tree starting there involves going into .git/
160
179
-- and dist-newstyle/, which is a lot of work for no reward, so
161
180
-- extract the constant prefix from the pattern and start walking
162
- -- there. If the pattern is **/*.blah, then of course we'll have
163
- -- to walk the whole thing anyway, but that's what the user asked
164
- -- for!
165
- let (prefixSegments, pat' ) = splitConstantPrefix pat
181
+ -- there, and only walk as much as we need to: recursively if **,
182
+ -- the whole directory if *, and just the specific file if it's a
183
+ -- literal.
184
+ let (prefixSegments, final ) = splitConstantPrefix pat
166
185
joinedPrefix = joinPath prefixSegments
167
- files <- getDirectoryContentsRecursive (dir </> joinedPrefix)
168
- case filter (fileGlobMatches pat') files of
169
- [] -> die' verbosity $
170
- " filepath wildcard '" ++ filepath
171
- ++ " ' does not match any files."
172
- matches -> return $ fmap (joinedPrefix </> ) matches
186
+ files <- case final of
187
+ FinalMatch recursive exts -> do
188
+ let prefix = dir </> joinedPrefix
189
+ candidates <- case recursive of
190
+ Recursive -> getDirectoryContentsRecursive prefix
191
+ NonRecursive -> filterM (doesFileExist . (prefix </> )) =<< getDirectoryContents prefix
192
+ return $ filter ((==) exts . takeExtensions) candidates
193
+ FinalLit fn -> do
194
+ exists <- doesFileExist (dir </> joinedPrefix </> fn)
195
+ return [ fn | exists ]
196
+ return $ fmap (joinedPrefix </> ) files
173
197
174
198
unfoldr' :: (a -> Either r (b , a )) -> a -> ([b ], r )
175
199
unfoldr' f a = case f a of
@@ -178,10 +202,10 @@ unfoldr' f a = case f a of
178
202
(bs, r) -> (b : bs, r)
179
203
180
204
-- | Extract the (possibly null) constant prefix from the pattern.
181
- -- This has the property that, if @(pref, pat' ) = splitConstantPrefix pat@,
182
- -- then @pat === foldr PatStem pat' pref@.
183
- splitConstantPrefix :: GlobPat -> ([FilePath ], GlobPat )
205
+ -- This has the property that, if @(pref, final ) = splitConstantPrefix pat@,
206
+ -- then @pat === foldr GlobStem (GlobFinal final) pref@.
207
+ splitConstantPrefix :: Glob -> ([FilePath ], GlobFinal )
184
208
splitConstantPrefix = unfoldr' step
185
209
where
186
- step (PatStem seg pat) = Right (seg, pat)
187
- step pat = Left pat
210
+ step (GlobStem seg pat) = Right (seg, pat)
211
+ step ( GlobFinal pat) = Left pat
0 commit comments