Skip to content

Commit 1d4eeea

Browse files
Rename some and fix docs, doctests of Path APIs
1 parent 1be8b88 commit 1d4eeea

File tree

10 files changed

+426
-226
lines changed

10 files changed

+426
-226
lines changed

core/src/DocTestFileSystemPath.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{- $setup
2+
>>> :m
3+
>>> :set -XQuasiQuotes
4+
>>> import Control.Exception (SomeException, evaluate, try)
5+
>>> import Data.Either (Either, isLeft)
6+
>>> import Data.Maybe (fromJust, isJust, isNothing)
7+
>>> import Streamly.FileSystem.Path (Path, path)
8+
>>> import qualified Streamly.Data.Array as Array
9+
>>> import qualified Streamly.Data.Stream as Stream
10+
>>> import qualified Streamly.FileSystem.Path as Path
11+
>>> import qualified Streamly.Unicode.Stream as Unicode
12+
13+
For APIs that have not been released yet.
14+
15+
>>> import qualified Streamly.Internal.FileSystem.Path as Path
16+
17+
Utilities:
18+
19+
>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))
20+
-}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{- $setup
2+
>>> :m
3+
>>> :set -XQuasiQuotes
4+
>>> import Control.Exception (SomeException, evaluate, try)
5+
>>> import Data.Either (Either, isLeft)
6+
>>> import Data.Maybe (isNothing, isJust)
7+
>>> import qualified Streamly.Data.Array as Array
8+
>>> import qualified Streamly.Data.Stream as Stream
9+
>>> import qualified Streamly.Unicode.Stream as Unicode
10+
11+
For APIs that have not been released yet.
12+
13+
>>> import Streamly.Internal.FileSystem.PosixPath (PosixPath, path)
14+
>>> import qualified Streamly.Internal.FileSystem.PosixPath as Path
15+
16+
Utilities:
17+
18+
>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))
19+
-}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{- $setup
2+
>>> :m
3+
>>> :set -XQuasiQuotes
4+
>>> import Control.Exception (SomeException, evaluate, try)
5+
>>> import Data.Either (Either, isLeft)
6+
>>> import Data.Maybe (fromJust, isNothing, isJust)
7+
>>> import Data.Word (Word16)
8+
>>> import Streamly.Data.Array (Array)
9+
>>> import qualified Streamly.Data.Array as Array
10+
>>> import qualified Streamly.Data.Stream as Stream
11+
>>> import qualified Streamly.Unicode.Stream as Unicode
12+
13+
For APIs that have not been released yet.
14+
15+
>>> import Streamly.Internal.FileSystem.WindowsPath (WindowsPath, path)
16+
>>> import qualified Streamly.Internal.FileSystem.WindowsPath as Path
17+
18+
Utilities:
19+
20+
>>> fails x = isLeft <$> (try (evaluate x) :: IO (Either SomeException String))
21+
-}

core/src/Streamly/FileSystem/Path.hs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE CPP #-}
12
-- |
23
-- Module : Streamly.FileSystem.Path
34
-- Copyright : (c) 2023 Composewell Technologies
@@ -68,11 +69,19 @@
6869

6970
module Streamly.FileSystem.Path
7071
(
72+
-- * Setup
73+
-- | To execute the code examples provided in this module in ghci, please
74+
-- run the following commands first.
75+
--
76+
-- $setup
77+
7178
-- * Type
7279
Path
7380

7481
-- * Construction
75-
, fromChunk
82+
, isValidPath
83+
, validatePath
84+
, fromArray
7685
, fromString
7786

7887
-- * Statically Verified String Literals
@@ -84,7 +93,7 @@ module Streamly.FileSystem.Path
8493
, pathE
8594

8695
-- * Elimination
87-
, toChunk
96+
, toArray
8897
, toChars
8998
, toString
9099
, asOsCString
@@ -93,10 +102,11 @@ module Streamly.FileSystem.Path
93102
, isRooted
94103
, isBranch
95104

96-
-- * Separators
97-
, dropTrailingSeparators
98-
, hasTrailingSeparator
99-
, addTrailingSeparator
105+
-- These are unstable APIs, see comments in the internal module.
106+
-- -- * Separators
107+
-- , dropTrailingSeparators
108+
-- , hasTrailingSeparator
109+
-- , addTrailingSeparator
100110

101111
-- * Joining
102112
, unsafeExtend
@@ -123,14 +133,11 @@ module Streamly.FileSystem.Path
123133

124134
-- * Equality
125135
, EqCfg
126-
, eqPath
127-
128-
#ifndef IS_WINDOWS
129-
-- ** Config options (Posix)
130136
, ignoreTrailingSeparators
131137
, ignoreCase
132138
, allowRelativeEquality
133-
#endif
139+
140+
, eqPath
134141
)
135142
where
136143

@@ -186,3 +193,5 @@ where
186193
-}
187194

188195
import Streamly.Internal.FileSystem.Path
196+
197+
#include "DocTestFileSystemPath.hs"

core/src/Streamly/Internal/FileSystem/Path.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE CPP #-}
12
-- |
23
-- Module : Streamly.Internal.FileSystem.Path
34
-- Copyright : (c) 2023 Composewell Technologies

core/src/Streamly/Internal/FileSystem/Path/Common.hs

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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 #-}
239243
dropTrailingBy :: (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 #-}
256270
compactTrailingBy :: Unbox a => (a -> Bool) -> Array a -> Array a
257271
compactTrailingBy p arr =
@@ -1158,8 +1172,9 @@ validatePathWith allowRoot Windows path
11581172
validatePath :: (MonadThrow m, Integral a, Unbox a) => OS -> Array a -> m ()
11591173
validatePath = 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' #-}
11641179
validatePath' :: (MonadThrow m, Integral a, Unbox a) => OS -> Array a -> m ()
11651180
validatePath' = 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)
12291244
fromChars 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 #-}
12341249
toChars :: (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
14311435
ignoreTrailingSeparators :: Bool -> EqCfg -> EqCfg
14321436
ignoreTrailingSeparators 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
14341443
ignoreCase :: Bool -> EqCfg -> EqCfg
14351444
ignoreCase 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
14371457
allowRelativeEquality :: Bool -> EqCfg -> EqCfg
14381458
allowRelativeEquality val conf = conf { _allowRelativeEquality = val }
14391459

1440-
14411460
data PosixRoot = PosixRootAbs | PosixRootRel deriving Eq
14421461

14431462
data WindowsRoot =
@@ -1547,8 +1566,8 @@ eqComponentsWith ignCase decoder os a b =
15471566
{-# INLINE eqPath #-}
15481567
eqPath :: (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

core/src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ openDirStreamCString s = do
237237
-- {-# INLINE openDirStream #-}
238238
openDirStream :: PosixPath -> IO DirStream
239239
openDirStream p =
240-
Array.asCStringUnsafe (Path.toChunk p) $ \s -> do
240+
Array.asCStringUnsafe (Path.toArray p) $ \s -> do
241241
-- openDirStreamCString s
242242
dirp <- throwErrnoPathIfNullRetry "openDirStream" p $ c_opendir s
243243
return (DirStream dirp)
@@ -296,7 +296,7 @@ statEntryType conf parent dname = do
296296
-- XXX We can create a pinned array right here since the next call pins
297297
-- it anyway.
298298
path <- appendCString parent dname
299-
Array.asCStringUnsafe (Path.toChunk path) $ \cStr -> do
299+
Array.asCStringUnsafe (Path.toArray path) $ \cStr -> do
300300
res <- stat (_followSymlinks conf) cStr
301301
case res of
302302
Right mode -> pure $
@@ -368,7 +368,7 @@ readDirStreamEither confMod (curdir, (DirStream dirp)) = loop
368368
-- mkPath :: IsPath (Rel (a Path)) => Array Word8 -> Rel (a Path)
369369
-- {-# INLINE mkPath #-}
370370
mkPath :: Array Word8 -> PosixPath
371-
mkPath = Path.unsafeFromChunk
371+
mkPath = Path.unsafeFromArray
372372

373373
loop = do
374374
resetErrno
@@ -920,7 +920,7 @@ readEitherByteChunksAt confMod (ppath, alldirs) =
920920
dname pfd dirp curdir xs dirs ndirs mbarr pos)
921921
EntryIsDir -> do
922922
arr <- Array.fromCString (castPtr dname)
923-
let path = Path.unsafeFromChunk arr
923+
let path = Path.unsafeFromArray arr
924924
let dirs1 = path : dirs
925925
ndirs1 = ndirs + 1
926926
r <- copyToBuf mbarr pos curdir dname

0 commit comments

Comments
 (0)