@@ -19,8 +19,8 @@ module Streamly.Internal.FileSystem.Path.Common
1919 , validateFile
2020
2121 -- * Construction
22- , fromChunk
23- , unsafeFromChunk
22+ , fromArray
23+ , unsafeFromArray
2424 , fromChars
2525 , unsafeFromChars
2626
@@ -35,6 +35,7 @@ module Streamly.Internal.FileSystem.Path.Common
3535 , primarySeparator
3636 , isSeparator
3737 , dropTrailingSeparators
38+ , dropTrailingBy
3839 , hasTrailingSeparator
3940 , hasLeadingSeparator
4041
@@ -85,7 +86,7 @@ module Streamly.Internal.FileSystem.Path.Common
8586 , normalizeSeparators
8687 -- , normalize -- separators and /./ components (split/combine)
8788 , eqPathBytes
88- , EqCfg
89+ , EqCfg ( .. )
8990 , ignoreTrailingSeparators
9091 , ignoreCase
9192 , allowRelativeEquality
@@ -235,6 +236,9 @@ isSeparatorWord os = isSeparator os . wordToChar
235236-- @a@. On Windows "c:" and "c:/" are different paths, therefore, we do not
236237-- drop the trailing separator from "c:/" or for that matter a separator
237238-- preceded by a ':'.
239+ --
240+ -- Can't use any arbitrary predicate "p", the logic in this depends on assuming
241+ -- that it is a path separator.
238242{-# INLINE dropTrailingBy #-}
239243dropTrailingBy :: (Unbox a , Integral a ) =>
240244 OS -> (a -> Bool ) -> Array a -> Array a
@@ -245,13 +249,23 @@ dropTrailingBy os p arr =
245249 in if n == 0
246250 then arr
247251 else if n == len -- "////"
248- then fst $ Array. unsafeBreakAt 1 arr
249- -- "c:////"
252+ then
253+ -- Even though "//" is not allowed as a valid path.
254+ -- We still handle that case in this low level function.
255+ if os == Windows
256+ && n >= 2
257+ && Array. unsafeGetIndex 0 arr == Array. unsafeGetIndex 1 arr
258+ then fst $ Array. unsafeBreakAt 2 arr -- make it "//" share name
259+ else fst $ Array. unsafeBreakAt 1 arr
260+ -- "c:////" - keep one "/" after colon in ".*:///" otherwise it will
261+ -- change the meaning. "c:/" may also appear, in the middle e.g.
262+ -- in UNC paths.
250263 else if (os == Windows )
251264 && (Array. unsafeGetIndex (len - n - 1 ) arr == charToWord ' :' )
252265 then fst $ Array. unsafeBreakAt (len - n + 1 ) arr
253266 else arr1
254267
268+ -- XXX we cannot compact "//" to "/" on windows
255269{-# INLINE compactTrailingBy #-}
256270compactTrailingBy :: Unbox a => (a -> Bool ) -> Array a -> Array a
257271compactTrailingBy p arr =
@@ -1158,8 +1172,9 @@ validatePathWith allowRoot Windows path
11581172validatePath :: (MonadThrow m , Integral a , Unbox a ) => OS -> Array a -> m ()
11591173validatePath = validatePathWith True
11601174
1161- -- | Like validatePath but on Windows only full paths are allowed, path roots
1162- -- only are not allowed. Thus "//x/" is not valid.
1175+ -- | Like validatePath but on Windows the path must refer to a file system
1176+ -- object, share roots or prefixes not referring to a specific path are not
1177+ -- allowed. Thus "//x/" is not a valid path.
11631178{-# INLINE validatePath' #-}
11641179validatePath' :: (MonadThrow m , Integral a , Unbox a ) => OS -> Array a -> m ()
11651180validatePath' = validatePathWith False
@@ -1187,18 +1202,18 @@ isValidPath' os path =
11871202 Nothing -> False
11881203 Just _ -> True
11891204
1190- {-# INLINE unsafeFromChunk #-}
1191- unsafeFromChunk :: Array a -> Array a
1192- unsafeFromChunk = id
1205+ {-# INLINE unsafeFromArray #-}
1206+ unsafeFromArray :: Array a -> Array a
1207+ unsafeFromArray = id
11931208
1194- {-# INLINE fromChunk #-}
1195- fromChunk :: forall m a . (MonadThrow m , Unbox a , Integral a ) =>
1209+ {-# INLINE fromArray #-}
1210+ fromArray :: forall m a . (MonadThrow m , Unbox a , Integral a ) =>
11961211 OS -> Array a -> m (Array a )
1197- fromChunk os arr = validatePath os arr >> pure arr
1212+ fromArray os arr = validatePath os arr >> pure arr
11981213{-
11991214 let arr1 = Array.unsafeCast arr :: Array a
12001215 in validatePath os arr1 >> pure arr1
1201- fromChunk Windows arr =
1216+ fromArray Windows arr =
12021217 case Array.cast arr of
12031218 Nothing ->
12041219 throwM
@@ -1228,7 +1243,7 @@ fromChars :: (MonadThrow m, Unbox a, Integral a) =>
12281243 -> m (Array a )
12291244fromChars os encode s =
12301245 let arr = unsafeFromChars encode s
1231- in fromChunk os (Array. unsafeCast arr)
1246+ in fromArray os (Array. unsafeCast arr)
12321247
12331248{-# INLINE toChars #-}
12341249toChars :: (Monad m , Unbox a ) =>
@@ -1392,6 +1407,7 @@ eqPathBytes = Array.byteEq
13921407-- control the strictness.
13931408--
13941409-- The default configuration is as follows:
1410+ --
13951411-- >>> :{
13961412-- defaultMod = ignoreTrailingSeparators False
13971413-- . ignoreCase False
@@ -1411,33 +1427,36 @@ data EqCfg =
14111427 -- , noIgnoreRedundantDot -- "x\/.\/" \/= "x"
14121428 }
14131429
1414- -- | Default equality check configuration.
1415- --
1416- -- > :{
1417- -- > eqCfg = EqCfg
1418- -- > { ignoreTrailingSeparators = False
1419- -- > , ignoreCase = False
1420- -- > , allowRelativeEquality = False
1421- -- > }
1422- -- > :}
1423- --
1424- eqCfg :: EqCfg
1425- eqCfg = EqCfg
1426- { _ignoreTrailingSeparators = False
1427- , _ignoreCase = False
1428- , _allowRelativeEquality = False
1429- }
1430-
1430+ -- | When set to 'False', a path with a trailing slash and a path without are
1431+ -- treated as unequal e.g. "x" is not the same as "x\/". The latter is a
1432+ -- directory.
1433+ --
1434+ -- /Default/: False
14311435ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg
14321436ignoreTrailingSeparators val conf = conf { _ignoreTrailingSeparators = val }
14331437
1438+ -- | When set to 'False', comparison is case sensitive.
1439+ --
1440+ -- /Posix Default/: False
1441+ --
1442+ -- /Windows Default/: True
14341443ignoreCase :: Bool -> EqCfg -> EqCfg
14351444ignoreCase val conf = conf { _ignoreCase = val }
14361445
1446+ -- | When set to 'False':
1447+ --
1448+ -- * paths with a leading "." and without a leading "." e.g. ".\/x\/y" and
1449+ -- "x\/y" are treated as unequal. The first one is a dynamically rooted path
1450+ -- and the second one is a branch or free path segment.
1451+ --
1452+ -- * Two paths starting with a leading "." may not actually be equal even if
1453+ -- they are literally equal, depending on the meaning of ".". We return unequal
1454+ -- even though they may be equal sometimes.
1455+ --
1456+ -- /Default/: False
14371457allowRelativeEquality :: Bool -> EqCfg -> EqCfg
14381458allowRelativeEquality val conf = conf { _allowRelativeEquality = val }
14391459
1440-
14411460data PosixRoot = PosixRootAbs | PosixRootRel deriving Eq
14421461
14431462data WindowsRoot =
@@ -1547,8 +1566,8 @@ eqComponentsWith ignCase decoder os a b =
15471566{-# INLINE eqPath #-}
15481567eqPath :: (Unbox a , Integral a ) =>
15491568 (Stream Identity a -> Stream Identity Char )
1550- -> OS -> ( EqCfg -> EqCfg ) -> Array a -> Array a -> Bool
1551- eqPath decoder os configMod a b =
1569+ -> OS -> EqCfg -> Array a -> Array a -> Bool
1570+ eqPath decoder os EqCfg { .. } a b =
15521571 let (rootA, stemA) = splitRoot os a
15531572 (rootB, stemB) = splitRoot os b
15541573
@@ -1570,5 +1589,3 @@ eqPath decoder os configMod a b =
15701589 eqRelative
15711590 && eqTrailingSep
15721591 && eqComponentsWith _ignoreCase decoder os stemA stemB
1573- where
1574- EqCfg {.. } = configMod eqCfg
0 commit comments