@@ -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
@@ -34,7 +34,9 @@ module Streamly.Internal.FileSystem.Path.Common
3434 -- * Separators
3535 , primarySeparator
3636 , isSeparator
37+ , isSeparatorWord
3738 , dropTrailingSeparators
39+ , dropTrailingBy
3840 , hasTrailingSeparator
3941 , hasLeadingSeparator
4042
@@ -85,7 +87,7 @@ module Streamly.Internal.FileSystem.Path.Common
8587 , normalizeSeparators
8688 -- , normalize -- separators and /./ components (split/combine)
8789 , eqPathBytes
88- , EqCfg
90+ , EqCfg ( .. )
8991 , ignoreTrailingSeparators
9092 , ignoreCase
9193 , allowRelativeEquality
@@ -165,6 +167,10 @@ data OS = Windows | Posix deriving Eq
165167-- XXX We can use Enum type class to include the Char type as well so that the
166168-- functions can work on Array Word8/Word16/Char but that may be slow.
167169
170+ -- XXX Windows is supported only on little endian machines so generally we do
171+ -- not need covnersion from LE to BE format unless we want to manipulate
172+ -- windows paths on big-endian machines.
173+
168174-- | Unsafe, may tructate to shorter word types, can only be used safely for
169175-- characters that fit in the given word size.
170176charToWord :: Integral a => Char -> a
@@ -235,6 +241,9 @@ isSeparatorWord os = isSeparator os . wordToChar
235241-- @a@. On Windows "c:" and "c:/" are different paths, therefore, we do not
236242-- drop the trailing separator from "c:/" or for that matter a separator
237243-- preceded by a ':'.
244+ --
245+ -- Can't use any arbitrary predicate "p", the logic in this depends on assuming
246+ -- that it is a path separator.
238247{-# INLINE dropTrailingBy #-}
239248dropTrailingBy :: (Unbox a , Integral a ) =>
240249 OS -> (a -> Bool ) -> Array a -> Array a
@@ -245,13 +254,23 @@ dropTrailingBy os p arr =
245254 in if n == 0
246255 then arr
247256 else if n == len -- "////"
248- then fst $ Array. unsafeBreakAt 1 arr
249- -- "c:////"
257+ then
258+ -- Even though "//" is not allowed as a valid path.
259+ -- We still handle that case in this low level function.
260+ if os == Windows
261+ && n >= 2
262+ && Array. unsafeGetIndex 0 arr == Array. unsafeGetIndex 1 arr
263+ then fst $ Array. unsafeBreakAt 2 arr -- make it "//" share name
264+ else fst $ Array. unsafeBreakAt 1 arr
265+ -- "c:////" - keep one "/" after colon in ".*:///" otherwise it will
266+ -- change the meaning. "c:/" may also appear, in the middle e.g.
267+ -- in UNC paths.
250268 else if (os == Windows )
251269 && (Array. unsafeGetIndex (len - n - 1 ) arr == charToWord ' :' )
252270 then fst $ Array. unsafeBreakAt (len - n + 1 ) arr
253271 else arr1
254272
273+ -- XXX we cannot compact "//" to "/" on windows
255274{-# INLINE compactTrailingBy #-}
256275compactTrailingBy :: Unbox a => (a -> Bool ) -> Array a -> Array a
257276compactTrailingBy p arr =
@@ -1158,8 +1177,9 @@ validatePathWith allowRoot Windows path
11581177validatePath :: (MonadThrow m , Integral a , Unbox a ) => OS -> Array a -> m ()
11591178validatePath = validatePathWith True
11601179
1161- -- | Like validatePath but on Windows only full paths are allowed, path roots
1162- -- only are not allowed. Thus "//x/" is not valid.
1180+ -- | Like validatePath but on Windows the path must refer to a file system
1181+ -- object, share roots or prefixes not referring to a specific path are not
1182+ -- allowed. Thus "//x/" is not a valid path.
11631183{-# INLINE validatePath' #-}
11641184validatePath' :: (MonadThrow m , Integral a , Unbox a ) => OS -> Array a -> m ()
11651185validatePath' = validatePathWith False
@@ -1187,18 +1207,18 @@ isValidPath' os path =
11871207 Nothing -> False
11881208 Just _ -> True
11891209
1190- {-# INLINE unsafeFromChunk #-}
1191- unsafeFromChunk :: Array a -> Array a
1192- unsafeFromChunk = id
1210+ {-# INLINE unsafeFromArray #-}
1211+ unsafeFromArray :: Array a -> Array a
1212+ unsafeFromArray = id
11931213
1194- {-# INLINE fromChunk #-}
1195- fromChunk :: forall m a . (MonadThrow m , Unbox a , Integral a ) =>
1214+ {-# INLINE fromArray #-}
1215+ fromArray :: forall m a . (MonadThrow m , Unbox a , Integral a ) =>
11961216 OS -> Array a -> m (Array a )
1197- fromChunk os arr = validatePath os arr >> pure arr
1217+ fromArray os arr = validatePath os arr >> pure arr
11981218{-
11991219 let arr1 = Array.unsafeCast arr :: Array a
12001220 in validatePath os arr1 >> pure arr1
1201- fromChunk Windows arr =
1221+ fromArray Windows arr =
12021222 case Array.cast arr of
12031223 Nothing ->
12041224 throwM
@@ -1228,7 +1248,7 @@ fromChars :: (MonadThrow m, Unbox a, Integral a) =>
12281248 -> m (Array a )
12291249fromChars os encode s =
12301250 let arr = unsafeFromChars encode s
1231- in fromChunk os (Array. unsafeCast arr)
1251+ in fromArray os (Array. unsafeCast arr)
12321252
12331253{-# INLINE toChars #-}
12341254toChars :: (Monad m , Unbox a ) =>
@@ -1392,6 +1412,7 @@ eqPathBytes = Array.byteEq
13921412-- control the strictness.
13931413--
13941414-- The default configuration is as follows:
1415+ --
13951416-- >>> :{
13961417-- defaultMod = ignoreTrailingSeparators False
13971418-- . ignoreCase False
@@ -1411,33 +1432,36 @@ data EqCfg =
14111432 -- , noIgnoreRedundantDot -- "x\/.\/" \/= "x"
14121433 }
14131434
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-
1435+ -- | When set to 'False', a path with a trailing slash and a path without are
1436+ -- treated as unequal e.g. "x" is not the same as "x\/". The latter is a
1437+ -- directory.
1438+ --
1439+ -- /Default/: False
14311440ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg
14321441ignoreTrailingSeparators val conf = conf { _ignoreTrailingSeparators = val }
14331442
1443+ -- | When set to 'False', comparison is case sensitive.
1444+ --
1445+ -- /Posix Default/: False
1446+ --
1447+ -- /Windows Default/: True
14341448ignoreCase :: Bool -> EqCfg -> EqCfg
14351449ignoreCase val conf = conf { _ignoreCase = val }
14361450
1451+ -- | When set to 'False':
1452+ --
1453+ -- * paths with a leading "." and without a leading "." e.g. ".\/x\/y" and
1454+ -- "x\/y" are treated as unequal. The first one is a dynamically rooted path
1455+ -- and the second one is a branch or free path segment.
1456+ --
1457+ -- * Two paths starting with a leading "." may not actually be equal even if
1458+ -- they are literally equal, depending on the meaning of ".". We return unequal
1459+ -- even though they may be equal sometimes.
1460+ --
1461+ -- /Default/: False
14371462allowRelativeEquality :: Bool -> EqCfg -> EqCfg
14381463allowRelativeEquality val conf = conf { _allowRelativeEquality = val }
14391464
1440-
14411465data PosixRoot = PosixRootAbs | PosixRootRel deriving Eq
14421466
14431467data WindowsRoot =
@@ -1547,8 +1571,8 @@ eqComponentsWith ignCase decoder os a b =
15471571{-# INLINE eqPath #-}
15481572eqPath :: (Unbox a , Integral a ) =>
15491573 (Stream Identity a -> Stream Identity Char )
1550- -> OS -> ( EqCfg -> EqCfg ) -> Array a -> Array a -> Bool
1551- eqPath decoder os configMod a b =
1574+ -> OS -> EqCfg -> Array a -> Array a -> Bool
1575+ eqPath decoder os EqCfg { .. } a b =
15521576 let (rootA, stemA) = splitRoot os a
15531577 (rootB, stemB) = splitRoot os b
15541578
@@ -1570,5 +1594,3 @@ eqPath decoder os configMod a b =
15701594 eqRelative
15711595 && eqTrailingSep
15721596 && eqComponentsWith _ignoreCase decoder os stemA stemB
1573- where
1574- EqCfg {.. } = configMod eqCfg
0 commit comments