@@ -14,11 +14,13 @@ module Distribution.Solver.Types.ProjectConfigPath
14
14
, docProjectConfigPath
15
15
, docProjectConfigFiles
16
16
, cyclicalImportMsg
17
+ , untrimmedUriImportMsg
17
18
, docProjectConfigPathFailReason
18
19
19
20
-- * Checks and Normalization
20
21
, isCyclicConfigPath
21
22
, isTopLevelConfigPath
23
+ , isUntrimmedUriConfigPath
22
24
, canonicalizeConfigPath
23
25
) where
24
26
@@ -34,6 +36,7 @@ import System.FilePath
34
36
import qualified Data.List.NonEmpty as NE
35
37
import Distribution.Solver.Modular.Version (VR )
36
38
import Distribution.Pretty (prettyShow )
39
+ import Distribution.Utils.String (trim )
37
40
import Text.PrettyPrint
38
41
import Distribution.Simple.Utils (ordNub )
39
42
@@ -98,9 +101,13 @@ instance Structured ProjectConfigPath
98
101
-- >>> render . docProjectConfigPath $ ProjectConfigPath $ "D.config" :| ["C.config", "B.config", "A.project"]
99
102
-- "D.config\n imported by: C.config\n imported by: B.config\n imported by: A.project"
100
103
docProjectConfigPath :: ProjectConfigPath -> Doc
101
- docProjectConfigPath (ProjectConfigPath (p :| [] )) = text p
102
- docProjectConfigPath (ProjectConfigPath (p :| ps)) = vcat $
103
- text p : [ text " " <+> text " imported by:" <+> text l | l <- ps ]
104
+ docProjectConfigPath (ProjectConfigPath (p :| [] )) = quoteUntrimmed p
105
+ docProjectConfigPath (ProjectConfigPath (p :| ps)) = vcat $ quoteUntrimmed p :
106
+ [ text " " <+> text " imported by:" <+> quoteUntrimmed l | l <- ps ]
107
+
108
+ -- | If the path has leading or trailing spaces then show it quoted.
109
+ quoteUntrimmed :: FilePath -> Doc
110
+ quoteUntrimmed s = if trim s /= s then quotes (text s) else text s
104
111
105
112
-- | Renders the paths as a list without showing which path imports another,
106
113
-- like this;
@@ -150,6 +157,14 @@ cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) =
150
157
, nest 2 (docProjectConfigPath path)
151
158
]
152
159
160
+ -- | A message for an import that has leading or trailing spaces.
161
+ untrimmedUriImportMsg :: Doc -> ProjectConfigPath -> Doc
162
+ untrimmedUriImportMsg intro path =
163
+ vcat
164
+ [ intro <+> text " import has leading or trailing whitespace" <> semi
165
+ , nest 2 (docProjectConfigPath path)
166
+ ]
167
+
153
168
docProjectConfigPathFailReason :: VR -> ProjectConfigPath -> Doc
154
169
docProjectConfigPathFailReason vr pcp
155
170
| ProjectConfigPath (p :| [] ) <- pcp =
@@ -178,6 +193,11 @@ nullProjectConfigPath = ProjectConfigPath $ "unused" :| []
178
193
isCyclicConfigPath :: ProjectConfigPath -> Bool
179
194
isCyclicConfigPath (ProjectConfigPath p) = length p /= length (NE. nub p)
180
195
196
+ -- | Check if the last segment of the path (root or importee) is a URI that has
197
+ -- leading or trailing spaces.
198
+ isUntrimmedUriConfigPath :: ProjectConfigPath -> Bool
199
+ isUntrimmedUriConfigPath (ProjectConfigPath (p :| _)) = let p' = trim p in p' /= p && isURI p'
200
+
181
201
-- | Check if the project config path is top-level, meaning it was not included by
182
202
-- some other project config.
183
203
isTopLevelConfigPath :: ProjectConfigPath -> Bool
@@ -196,7 +216,7 @@ unconsProjectConfigPath ps = fmap ProjectConfigPath <$> NE.uncons (coerce ps)
196
216
makeRelativeConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath
197
217
makeRelativeConfigPath dir (ProjectConfigPath p) =
198
218
ProjectConfigPath
199
- $ (\ segment -> (if isURI segment then segment else makeRelative dir segment))
219
+ $ (\ segment@ (trim -> trimSegment) -> (if isURI trimSegment then trimSegment else makeRelative dir segment))
200
220
<$> p
201
221
202
222
-- | Normalizes and canonicalizes a path removing '.' and '..' indirections.
@@ -273,11 +293,25 @@ makeRelativeConfigPath dir (ProjectConfigPath p) =
273
293
-- return $ expected == render (docProjectConfigPath p) ++ "\n"
274
294
-- :}
275
295
-- True
296
+ --
297
+ -- "A string is a valid URL potentially surrounded by spaces if, after stripping leading and trailing whitespace from it, it is a valid URL."
298
+ -- [W3C/HTML5/URLs](https://www.w3.org/TR/2010/WD-html5-20100624/urls.html)
299
+ --
300
+ -- Trailing spaces for @ProjectConfigPath@ URLs are trimmed.
301
+ --
302
+ -- >>> p <- canonicalizeConfigPath "" (ProjectConfigPath $ ("https://www.stackage.org/nightly-2024-12-05/cabal.config ") :| [])
303
+ -- >>> render $ docProjectConfigPath p
304
+ -- "https://www.stackage.org/nightly-2024-12-05/cabal.config"
305
+ --
306
+ -- >>> let d = testDir
307
+ -- >>> p <- canonicalizeConfigPath d (ProjectConfigPath $ ("https://www.stackage.org/nightly-2024-12-05/cabal.config ") :| [d </> "cabal.project"])
308
+ -- >>> render $ docProjectConfigPath p
309
+ -- "https://www.stackage.org/nightly-2024-12-05/cabal.config\n imported by: cabal.project"
276
310
canonicalizeConfigPath :: FilePath -> ProjectConfigPath -> IO ProjectConfigPath
277
311
canonicalizeConfigPath d (ProjectConfigPath p) = do
278
- xs <- sequence $ NE. scanr (\ importee -> (>>= \ importer ->
279
- if isURI importee
280
- then pure importee
312
+ xs <- sequence $ NE. scanr (\ importee@ (trim -> trimImportee) -> (>>= \ importer@ (trim -> trimImporter) ->
313
+ if isURI trimImportee || isURI trimImporter
314
+ then pure trimImportee
281
315
else canonicalizePath $ d </> takeDirectory importer </> importee))
282
316
(pure " ." ) p
283
317
return . makeRelativeConfigPath d . ProjectConfigPath . NE. fromList $ NE. init xs
0 commit comments