diff --git a/core/src/Streamly/Internal/FileSystem/Path/Common.hs b/core/src/Streamly/Internal/FileSystem/Path/Common.hs index b248f80240..c5e61d5bdb 100644 --- a/core/src/Streamly/Internal/FileSystem/Path/Common.hs +++ b/core/src/Streamly/Internal/FileSystem/Path/Common.hs @@ -718,7 +718,7 @@ splitPath = splitPathUsing True -- /Unimplemented/ {-# INLINE splitHead #-} splitHead :: -- (Unbox a, Integral a) => - OS -> Array a -> (Array a, Array a) + OS -> Array a -> (Array a, Maybe (Array a)) splitHead _os _arr = undefined -- | Split the last non-empty path component. @@ -726,7 +726,7 @@ splitHead _os _arr = undefined -- /Unimplemented/ {-# INLINE splitTail #-} splitTail :: -- (Unbox a, Integral a) => - OS -> Array a -> (Array a, Array a) + OS -> Array a -> (Maybe (Array a), Array a) splitTail _os _arr = undefined ------------------------------------------------------------------------------ @@ -764,7 +764,8 @@ validateFile os arr = do Posix -> pure () {-# INLINE splitFile #-} -splitFile :: (Unbox a, Integral a) => OS -> Array a -> (Array a, Array a) +splitFile :: (Unbox a, Integral a) => + OS -> Array a -> Maybe (Maybe (Array a), Array a) splitFile os arr = let p x = if os == Windows @@ -789,9 +790,9 @@ splitFile os arr = && fileSecond == charToWord '.') then if baseLen <= 0 - then (Array.empty, arr) - else (Array.unsafeSliceOffLen 0 baseLen base, file) -- "/" - else (arr, Array.empty) + then Just (Nothing, arr) + else Just (Just $ Array.unsafeSliceOffLen 0 baseLen base, file) -- "/" + else Nothing -- | Split a multi-component path into (dir, last component). If the path has a -- single component and it is a root then return (path, "") otherwise return @@ -868,7 +869,7 @@ splitDir _os _arr = undefined -- | Like split extension but we can specify the extension char to be used. {-# INLINE splitExtensionBy #-} splitExtensionBy :: (Unbox a, Integral a) => - a -> OS -> Array a -> (Array a, Array a) + a -> OS -> Array a -> Maybe (Array a, Array a) splitExtensionBy c os arr = let p x = x == c || isSeparatorWord os x -- XXX Use Array.revBreakEndBy_ @@ -899,11 +900,11 @@ splitExtensionBy c os arr = -- On Windows if base is 'c:.' or a UNC path ending in '/c:.' then -- it is a dot file, no extension. && not (os == Windows && baseLast == charToWord ':') - then res - else (arr, Array.empty) + then Just res + else Nothing {-# INLINE splitExtension #-} -splitExtension :: (Unbox a, Integral a) => OS -> Array a -> (Array a, Array a) +splitExtension :: (Unbox a, Integral a) => OS -> Array a -> Maybe (Array a, Array a) splitExtension = splitExtensionBy extensionWord {- diff --git a/core/src/Streamly/Internal/FileSystem/PosixPath.hs b/core/src/Streamly/Internal/FileSystem/PosixPath.hs index 50983c4cb1..4477f25116 100644 --- a/core/src/Streamly/Internal/FileSystem/PosixPath.hs +++ b/core/src/Streamly/Internal/FileSystem/PosixPath.hs @@ -149,7 +149,12 @@ module Streamly.Internal.FileSystem.OS_PATH , splitPath , splitPath_ , splitFile + + , splitFirst + , splitLast + , splitExtension + , dropExtension , addExtension -- * Equality @@ -192,7 +197,7 @@ import Streamly.Internal.Data.Path {- $setup >>> :m >>> :set -XQuasiQuotes ->>> import Data.Maybe (fromJust) +>>> import Data.Maybe (fromJust, isNothing, isJust) >>> import qualified Streamly.Data.Stream as Stream For APIs that have not been released yet. @@ -673,46 +678,50 @@ unsafeJoinPaths = undefined #ifndef IS_WINDOWS -- | If a path is rooted then separate the root and the remaining path, --- otherwise root is returned as empty. If the path is rooted then the non-root --- part is guaranteed to not start with a separator. +-- otherwise return 'Nothing'. If the path is rooted then the non-root +-- part is guaranteed to NOT start with a separator. -- -- Some filepath package equivalent idioms: -- -- >>> splitDrive = Path.splitRoot -- >>> joinDrive = Path.unsafeAppend --- >>> takeDrive = fst . Path.splitRoot --- >>> dropDrive = snd . Path.splitRoot +-- >>> takeDrive = fmap fst . Path.splitRoot +-- >>> dropDrive x = Path.splitRoot x >>= snd +-- >>> hasDrive = isJust . Path.splitRoot +-- >>> isDrive = isNothing . dropDrive -- --- >> hasDrive = not . null . takeDrive -- TODO --- >> isDrive = null . dropDrive -- TODO --- --- >>> toList (a,b) = (Path.toString a, Path.toString b) --- >>> split = toList . Path.splitRoot . pack +-- >>> toList (a,b) = (Path.toString a, fmap Path.toString b) +-- >>> split = fmap toList . Path.splitRoot . pack -- -- >>> split "/" --- ("/","") +-- Just ("/",Nothing) -- -- >>> split "." --- (".","") +-- Just (".",Nothing) -- -- >>> split "./" --- ("./","") +-- Just ("./",Nothing) -- -- >>> split "/home" --- ("/","home") +-- Just ("/",Just "home") -- -- >>> split "//" --- ("//","") +-- Just ("//",Nothing) -- -- >>> split "./home" --- ("./","home") +-- Just ("./",Just "home") -- -- >>> split "home" --- ("","home") +-- Nothing -- -splitRoot :: OS_PATH -> (OS_PATH, OS_PATH) -splitRoot (OS_PATH a) = - bimap OS_PATH OS_PATH $ Common.splitRoot Common.OS_NAME a +splitRoot :: OS_PATH -> Maybe (OS_PATH, Maybe OS_PATH) +splitRoot (OS_PATH x) = + let (a,b) = Common.splitRoot Common.OS_NAME x + in if Array.null a + then Nothing + else if Array.null b + then Just (OS_PATH a, Nothing) + else Just (OS_PATH a, Just (OS_PATH b)) -- | Split the path components keeping separators between path components -- attached to the dir part. Redundant separators are removed, only the first @@ -820,74 +829,94 @@ splitPath_ :: Monad m => OS_PATH -> Stream m OS_PATH splitPath_ (OS_PATH a) = fmap OS_PATH $ Common.splitPath_ Common.OS_NAME a #endif --- | Split a multi-component path into (dir, file) if its last component can be --- a file i.e.: +-- | If the path does not look like a directory then return @Just (Maybe dir, +-- file)@ otherwise return 'Nothing'. The path is not a directory if: -- -- * the path does not end with a separator -- * the path does not end with a . or .. component -- --- Split a single component into ("", path) if it can be a file i.e. it is not --- a path root, "." or "..". --- --- If the path cannot be a file then (path, "") is returned. +-- Splits a single component path into @Just (Nothing, path)@ if it does not +-- look like a dir. -- -- Some filepath package equivalent idioms: -- --- >>> takeFileName = snd . Path.splitFile -- Posix basename --- >>> takeBaseName = fst . Path.splitExtension . snd . Path.splitFile --- >>> dropFileName = fst . Path.splitFile --- >>> takeDirectory = fst . Path.splitFile --- >>> replaceFileName p x = Path.append (takeDirectory p) x --- >>> replaceDirectory p x = Path.append x (takeFileName p) +-- >>> takeFileName = fmap snd . Path.splitFile +-- >>> takeBaseName = fmap Path.dropExtension . takeFileName +-- >>> dropFileName x = Path.splitFile x >>= fst +-- >>> takeDirectory x = Path.splitFile x >>= fst +-- >>> replaceFileName p x = fmap (flip Path.append x) (takeDirectory p) +-- >>> replaceDirectory p x = fmap (flip Path.append x) (takeFileName p) -- --- >>> toList (a,b) = (Path.toString a, Path.toString b) --- >>> split = toList . Path.splitFile . pack +-- >>> toList (a,b) = (fmap Path.toString a, Path.toString b) +-- >>> split = fmap toList . Path.splitFile . pack -- -- >>> split "/" --- ("/","") +-- Nothing -- -- >>> split "." --- (".","") +-- Nothing -- -- >>> split "/." --- ("/.","") +-- Nothing -- -- >>> split ".." --- ("..","") +-- Nothing -- -- >>> split "//" --- ("//","") +-- Nothing -- -- >>> split "/home" --- ("/","home") +-- Just (Just "/","home") -- -- >>> split "./home" --- ("./","home") +-- Just (Just "./","home") -- -- >>> split "home" --- ("","home") +-- Just (Nothing,"home") -- -- >>> split "x/" --- ("x/","") +-- Nothing -- -- >>> split "x/y" --- ("x/","y") +-- Just (Just "x/","y") -- -- >>> split "x//y" --- ("x//","y") +-- Just (Just "x//","y") -- -- >>> split "x/./y" --- ("x/./","y") -splitFile :: OS_PATH -> (OS_PATH, OS_PATH) +-- Just (Just "x/./","y") +splitFile :: OS_PATH -> Maybe (Maybe OS_PATH, OS_PATH) splitFile (OS_PATH a) = - bimap OS_PATH OS_PATH $ Common.splitFile Common.OS_NAME a + fmap (bimap (fmap OS_PATH) OS_PATH) $ Common.splitFile Common.OS_NAME a + +-- | Split the path into the first component and rest of the path. Treats the +-- entire root or share name, if present, as the first component. +-- +-- /Unimplemented/ +splitFirst :: OS_PATH -> (OS_PATH, Maybe OS_PATH) +splitFirst (OS_PATH a) = + bimap OS_PATH (fmap OS_PATH) $ Common.splitHead Common.OS_NAME a + +-- | Split the path into the last component and rest of the path. Treats the +-- entire root or share name, if present, as the first component. +-- +-- >>> basename = snd . Path.splitLast -- Posix basename +-- >>> dirname = fst . Path.splitLast -- Posix dirname +-- +-- /Unimplemented/ +splitLast :: OS_PATH -> (Maybe OS_PATH, OS_PATH) +splitLast (OS_PATH a) = + bimap (fmap OS_PATH) OS_PATH $ Common.splitTail Common.OS_NAME a #ifndef IS_WINDOWS -- Note: In the cases of "x.y." and "x.y.." we return no extension rather -- than ".y." or ".y.." as extensions. That is they considered to have no -- extension. --- | A file name is considered to have an extension if the file name can be +-- | Returns @Just(filename, extension)@ if an extension is present otherwise +-- returns 'Nothing'. +-- +-- A file name is considered to have an extension if the file name can be -- split into a non-empty filename followed by the extension separator "." -- followed by a non-empty extension with at least one character in addition to -- the extension separator. @@ -899,95 +928,98 @@ splitFile (OS_PATH a) = -- -- Other extension related operations can be implemented using this API: -- --- >>> takeExtension = snd . Path.splitExtension --- >>> dropExtension = fst . Path.splitExtension +-- >>> takeExtension = fmap snd . Path.splitExtension +-- >>> hasExtension = isJust . Path.splitExtension -- --- >> hasExtension = not . null . takeExtension -- TODO --- --- If you want a @splitExtensions@, you can splitExtension until the extension --- returned is empty. @dropExtensions@, @isExtensionOf@ can be implemented --- similarly. +-- If you want a @splitExtensions@, you can use splitExtension until the +-- extension returned is Nothing. @dropExtensions@, @isExtensionOf@ can be +-- implemented similarly. -- -- >>> toList (a,b) = (Path.toString a, Path.toString b) --- >>> split = toList . Path.splitExtension . pack +-- >>> split = fmap toList . Path.splitExtension . pack -- -- >>> split "/" --- ("/","") +-- Nothing -- -- >>> split "." --- (".","") +-- Nothing -- -- >>> split ".." --- ("..","") +-- Nothing -- -- >>> split "x" --- ("x","") +-- Nothing -- -- >>> split "/x" --- ("/x","") +-- Nothing -- -- >>> split "x/" --- ("x/","") +-- Nothing -- -- >>> split "./x" --- ("./x","") +-- Nothing -- -- >>> split "x/." --- ("x/.","") +-- Nothing -- -- >>> split "x/y." --- ("x/y.","") +-- Nothing -- -- >>> split "/x.y" --- ("/x",".y") +-- Just ("/x",".y") -- -- >>> split "/x.y." --- ("/x.y.","") +-- Nothing -- -- >>> split "/x.y.." --- ("/x.y..","") +-- Nothing -- -- >>> split "x/.y" --- ("x/.y","") +-- Nothing -- -- >>> split ".x" --- (".x","") +-- Nothing -- -- >>> split "x." --- ("x.","") +-- Nothing -- -- >>> split ".x.y" --- (".x",".y") +-- Just (".x",".y") -- -- >>> split "x/y.z" --- ("x/y",".z") +-- Just ("x/y",".z") -- -- >>> split "x.y.z" --- ("x.y",".z") +-- Just ("x.y",".z") -- -- >>> split "x..y" --- ("x.",".y") +-- Just ("x.",".y") -- -- >>> split "..." --- ("...","") +-- Nothing -- -- >>> split "..x" --- (".",".x") +-- Just (".",".x") -- -- >>> split "...x" --- ("..",".x") +-- Just ("..",".x") -- -- >>> split "x/y.z/" --- ("x/y.z/","") +-- Nothing -- -- >>> split "x/y" --- ("x/y","") +-- Nothing -- -splitExtension :: OS_PATH -> (OS_PATH, OS_PATH) +splitExtension :: OS_PATH -> Maybe (OS_PATH, OS_PATH) splitExtension (OS_PATH a) = - bimap OS_PATH OS_PATH $ Common.splitExtension Common.OS_NAME a + fmap (bimap OS_PATH OS_PATH) $ Common.splitExtension Common.OS_NAME a #endif +-- | Drop the extension of a file if it has one. +dropExtension :: OS_PATH -> OS_PATH +dropExtension orig@(OS_PATH a) = + maybe orig (OS_PATH . fst) $ Common.splitExtension Common.OS_NAME a + -- | Add an extension to a file path. If a non-empty extension does not start -- with a leading dot then a dot is inserted, otherwise the extension is -- concatenated with the path. diff --git a/core/src/Streamly/Internal/FileSystem/WindowsPath.hs b/core/src/Streamly/Internal/FileSystem/WindowsPath.hs index 55a31bf335..139da10619 100644 --- a/core/src/Streamly/Internal/FileSystem/WindowsPath.hs +++ b/core/src/Streamly/Internal/FileSystem/WindowsPath.hs @@ -385,24 +385,29 @@ eqPath (OS_PATH a) (OS_PATH b) = -- See "Streamly.Internal.FileSystem.PosixPath" module for common examples. We -- provide some Windows specific examples here. -- --- >>> toList (a,b) = (Path.toString a, Path.toString b) --- >>> split = toList . Path.splitRoot . pack +-- >>> toList (a,b) = (Path.toString a, fmap Path.toString b) +-- >>> split = fmap toList . Path.splitRoot . pack -- -- >>> split "c:" --- ("c:","") +-- Just ("c:",Nothing) -- -- >>> split "c:/" --- ("c:/","") +-- Just ("c:/",Nothing) -- -- >>> split "//x/" --- ("//x/","") +-- Just ("//x/",Nothing) -- -- >>> split "//x/y" --- ("//x/","y") --- -splitRoot :: OS_PATH -> (OS_PATH, OS_PATH) -splitRoot (OS_PATH a) = - bimap OS_PATH OS_PATH $ Common.splitRoot Common.OS_NAME a +-- Just ("//x/",Just "y") +-- +splitRoot :: OS_PATH -> Maybe (OS_PATH, Maybe OS_PATH) +splitRoot (OS_PATH x) = + let (a,b) = Common.splitRoot Common.OS_NAME x + in if Array.null a + then Nothing + else if Array.null b + then Just (OS_PATH a, Nothing) + else Just (OS_PATH a, Just (OS_PATH b)) -- | Split a path into components separated by the path separator. "." -- components in the path are ignored except when in the leading position. @@ -474,14 +479,14 @@ splitPath (OS_PATH a) = fmap OS_PATH $ Common.splitPath Common.OS_NAME a -- "prn." as a filename without an extension. -- -- >>> toList (a,b) = (Path.toString a, Path.toString b) --- >>> split = toList . Path.splitExtension . pack +-- >>> split = fmap toList . Path.splitExtension . pack -- -- >>> split "x:y" --- ("x:y","") +-- Nothing -- -- >>> split "x:.y" --- ("x:.y","") +-- Nothing -- -splitExtension :: OS_PATH -> (OS_PATH, OS_PATH) +splitExtension :: OS_PATH -> Maybe (OS_PATH, OS_PATH) splitExtension (OS_PATH a) = - bimap OS_PATH OS_PATH $ Common.splitExtension Common.OS_NAME a + fmap (bimap OS_PATH OS_PATH) $ Common.splitExtension Common.OS_NAME a