From e11289eb6a25b848b96b85c0e4ac495e3d55a79b Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sat, 14 Oct 2023 18:57:19 +0800 Subject: [PATCH] Add more bytestring like functions --- System/OsString.hs | 146 +++ System/OsString/Common.hs | 873 ++++++++++++++++++ System/OsString/Internal.hs | 545 +++++++++++ changelog.md | 4 + filepath.cabal | 5 +- tests/abstract-filepath/OsPathSpec.hs | 4 +- tests/bytestring-tests/Main.hs | 5 +- tests/bytestring-tests/Properties/Common.hs | 256 ++++- tests/bytestring-tests/Properties/OsString.hs | 7 + .../Properties/PosixString.hs | 7 + .../Properties/ShortByteString.hs | 4 + .../Properties/ShortByteString/Word16.hs | 3 + .../Properties/WindowsString.hs | 7 + 13 files changed, 1834 insertions(+), 32 deletions(-) create mode 100644 tests/bytestring-tests/Properties/OsString.hs create mode 100644 tests/bytestring-tests/Properties/PosixString.hs create mode 100644 tests/bytestring-tests/Properties/WindowsString.hs diff --git a/System/OsString.hs b/System/OsString.hs index c11a4bdf..8de8e6fc 100644 --- a/System/OsString.hs +++ b/System/OsString.hs @@ -24,6 +24,8 @@ module System.OsString , encodeWith , encodeFS , osstr + , empty + , singleton , pack -- * OsString deconstruction @@ -40,6 +42,87 @@ module System.OsString -- * Word deconstruction , toChar + + -- * Basic interface + , snoc + , cons + , last + , tail + , uncons + , head + , init + , unsnoc + , null + , length + + -- * Transforming OsString + , map + , reverse + , intercalate + + -- * Reducing OsStrings (folds) + , foldl + , foldl' + , foldl1 + , foldl1' + , foldr + , foldr' + , foldr1 + , foldr1' + + -- * Special folds + , all + , any + , concat + + -- * Generating and unfolding OsStrings + , replicate + , unfoldr + , unfoldrN + + -- * Substrings + -- ** Breaking strings + , take + , takeEnd + , takeWhileEnd + , takeWhile + , drop + , dropEnd + , dropWhileEnd + , dropWhile + , break + , breakEnd + , span + , spanEnd + , splitAt + , split + , splitWith + , stripSuffix + , stripPrefix + + -- * Predicates + , isInfixOf + , isPrefixOf + , isSuffixOf + -- ** Search for arbitrary susbstrings + , breakSubstring + + -- * Searching OsStrings + -- ** Searching by equality + , elem + , find + , filter + , partition + + -- * Indexing OsStrings + , index + , indexMaybe + , (!?) + , elemIndex + , elemIndices + , count + , findIndex + , findIndices ) where @@ -51,10 +134,73 @@ import System.OsString.Internal , encodeFS , osstr , pack + , empty + , singleton , decodeUtf , decodeWith , decodeFS , unpack + , snoc + , cons + , last + , tail + , uncons + , head + , init + , unsnoc + , null + , length + , map + , reverse + , intercalate + , foldl + , foldl' + , foldl1 + , foldl1' + , foldr + , foldr' + , foldr1 + , foldr1' + , all + , any + , concat + , replicate + , unfoldr + , unfoldrN + , take + , takeEnd + , takeWhileEnd + , takeWhile + , drop + , dropEnd + , dropWhileEnd + , dropWhile + , break + , breakEnd + , span + , spanEnd + , splitAt + , split + , splitWith + , stripSuffix + , stripPrefix + , isInfixOf + , isPrefixOf + , isSuffixOf + , breakSubstring + , elem + , find + , filter + , partition + , index + , indexMaybe + , (!?) + , elemIndex + , elemIndices + , count + , findIndex + , findIndices ) import System.OsString.Internal.Types ( OsString, OsChar ) +import Prelude hiding (last, tail, head, init, null, length, map, reverse, foldl, foldr, foldl1, foldr1, all, any, concat, replicate, take, takeWhile, drop, dropWhile, break, span, splitAt, elem, filter) diff --git a/System/OsString/Common.hs b/System/OsString/Common.hs index 80eb69b5..cd42b279 100644 --- a/System/OsString/Common.hs +++ b/System/OsString/Common.hs @@ -1,6 +1,7 @@ {- HLINT ignore "Unused LANGUAGE pragma" -} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RankNTypes #-} -- This template expects CPP definitions for: -- MODULE_NAME = Posix | Windows -- IS_WINDOWS = False | True @@ -28,6 +29,8 @@ module System.OsString.MODULE_NAME , encodeFS , fromBytes , pstr + , singleton + , empty , pack -- * String deconstruction @@ -41,6 +44,87 @@ module System.OsString.MODULE_NAME -- * Word deconstruction , toChar + + -- * Basic interface + , snoc + , cons + , last + , tail + , uncons + , head + , init + , unsnoc + , null + , length + + -- * Transforming OsString + , map + , reverse + , intercalate + + -- * Reducing OsStrings (folds) + , foldl + , foldl' + , foldl1 + , foldl1' + , foldr + , foldr' + , foldr1 + , foldr1' + + -- ** Special folds + , all + , any + , concat + + -- ** Generating and unfolding OsStrings + , replicate + , unfoldr + , unfoldrN + + -- * Substrings + -- ** Breaking strings + , take + , takeEnd + , takeWhileEnd + , takeWhile + , drop + , dropEnd + , dropWhileEnd + , dropWhile + , break + , breakEnd + , span + , spanEnd + , splitAt + , split + , splitWith + , stripSuffix + , stripPrefix + + -- * Predicates + , isInfixOf + , isPrefixOf + , isSuffixOf + -- ** Search for arbitrary susbstrings + , breakSubstring + + -- * Searching OsStrings + -- ** Searching by equality + , elem + , find + , filter + , partition + + -- * Indexing OsStrings + , index + , indexMaybe + , (!?) + , elemIndex + , elemIndices + , count + , findIndex + , findIndices ) where @@ -87,6 +171,9 @@ import System.IO import GHC.IO.Encoding.UTF8 ( mkUTF8 ) import qualified System.OsPath.Data.ByteString.Short as BS #endif +import GHC.Stack (HasCallStack) +import Prelude hiding (last, tail, head, init, null, length, map, reverse, foldl, foldr, foldl1, foldr1, all, any, concat, replicate, take, takeWhile, drop, dropWhile, break, span, splitAt, elem, filter) +import Data.Bifunctor ( bimap ) @@ -295,6 +382,16 @@ pack = WindowsString . BS16.pack . fmap (\(WindowsChar w) -> w) pack = PosixString . BS.pack . fmap (\(PosixChar w) -> w) #endif +singleton :: PLATFORM_WORD -> PLATFORM_STRING +#ifdef WINDOWS +singleton = WindowsString . BS16.singleton . getWindowsChar +#else +singleton = PosixString . BS.singleton . getPosixChar +#endif + +empty :: PLATFORM_STRING +empty = mempty + #ifdef WINDOWS -- | Truncates to 2 octets. @@ -313,3 +410,779 @@ toChar (WindowsChar w) = chr $ fromIntegral w #else toChar (PosixChar w) = chr $ fromIntegral w #endif + +-- | /O(n)/ Append a byte to the end of a 'OsString' +-- +-- @since 1.4.200.0 +snoc :: PLATFORM_STRING -> PLATFORM_WORD -> PLATFORM_STRING +#ifdef WINDOWS +snoc (WindowsString s) (WindowsChar w) = WindowsString (BS16.snoc s w) +#else +snoc (PosixString s) (PosixChar w) = PosixString (BS.snoc s w) +#endif + +-- | /O(n)/ 'cons' is analogous to (:) for lists. +-- +-- @since 1.4.200.0 +cons :: PLATFORM_WORD -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +cons (WindowsChar w) (WindowsString s) = WindowsString (BS16.cons w s) +#else +cons (PosixChar w) (PosixString s) = PosixString (BS.cons w s) +#endif + + +-- | /O(1)/ Extract the last element of a OsString, which must be finite and non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'unsnoc' instead. +-- +-- @since 1.4.200.0 +last :: HasCallStack => PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +last (WindowsString s) = WindowsChar (BS16.last s) +#else +last (PosixString s) = PosixChar (BS.last s) +#endif + +-- | /O(n)/ Extract the elements after the head of a OsString, which must be non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'uncons' instead. +-- +-- @since 1.4.200.0 +tail :: HasCallStack => PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +tail (WindowsString s) = WindowsString (BS16.tail s) +#else +tail (PosixString s) = PosixString (BS.tail s) +#endif + +-- | /O(n)/ Extract the 'head' and 'tail' of a OsString, returning 'Nothing' +-- if it is empty. +-- +-- @since 1.4.200.0 +uncons :: PLATFORM_STRING -> Maybe (PLATFORM_WORD, PLATFORM_STRING) +#ifdef WINDOWS +uncons (WindowsString s) = (bimap WindowsChar WindowsString) <$> (BS16.uncons s) +#else +uncons (PosixString s) = (bimap PosixChar PosixString) <$> (BS.uncons s) +#endif + +-- | /O(1)/ Extract the first element of a OsString, which must be non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'uncons' instead. +-- +-- @since 1.4.200.0 +head :: HasCallStack => PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +head (WindowsString s) = WindowsChar (BS16.head s) +#else +head (PosixString s) = PosixChar (BS.head s) +#endif + +-- | /O(n)/ Return all the elements of a 'OsString' except the last one. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'unsnoc' instead. +-- +-- @since 1.4.200.0 +init :: HasCallStack => PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +init (WindowsString s) = WindowsString (BS16.init s) +#else +init (PosixString s) = PosixString (BS.init s) +#endif + +-- | /O(n)/ Extract the 'init' and 'last' of a OsString, returning 'Nothing' +-- if it is empty. +-- +-- @since 1.4.200.0 +unsnoc :: PLATFORM_STRING -> Maybe (PLATFORM_STRING, PLATFORM_WORD) +#ifdef WINDOWS +unsnoc (WindowsString s) = (bimap WindowsString WindowsChar) <$> (BS16.unsnoc s) +#else +unsnoc (PosixString s) = (bimap PosixString PosixChar) <$> (BS.unsnoc s) +#endif + +-- | /O(1)/. The empty 'OsString'. +-- +-- @since 1.4.200.0 +null :: PLATFORM_STRING -> Bool +#ifdef WINDOWS +null (WindowsString s) = BS16.null s +#else +null (PosixString s) = BS.null s +#endif + +-- | /O(1)/ The length of a 'OsString'. +-- +-- @since 1.4.200.0 +length :: PLATFORM_STRING -> Int +#ifdef WINDOWS +length (WindowsString s) = BS16.length s +#else +length (PosixString s) = BS.length s +#endif + +-- | /O(n)/ 'map' @f xs@ is the OsString obtained by applying @f@ to each +-- element of @xs@. +-- +-- @since 1.4.200.0 +map :: (PLATFORM_WORD -> PLATFORM_WORD) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +map f (WindowsString s) = WindowsString (BS16.map (getWindowsChar . f . WindowsChar) s) +#else +map f (PosixString s) = PosixString (BS.map (getPosixChar . f . PosixChar) s) +#endif + +-- | /O(n)/ 'reverse' @xs@ efficiently returns the elements of @xs@ in reverse order. +-- +-- @since 1.4.200.0 +reverse :: PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +reverse (WindowsString s) = WindowsString (BS16.reverse s) +#else +reverse (PosixString s) = PosixString (BS.reverse s) +#endif + +-- | /O(n)/ The 'intercalate' function takes a 'OsString' and a list of +-- 'OsString's and concatenates the list after interspersing the first +-- argument between each element of the list. +-- +-- @since 1.4.200.0 +intercalate :: PLATFORM_STRING -> [PLATFORM_STRING] -> PLATFORM_STRING +#ifdef WINDOWS +intercalate (WindowsString s) xs = WindowsString (BS16.intercalate s (fmap getWindowsString xs)) +#else +intercalate (PosixString s) xs = PosixString (BS.intercalate s (fmap getPosixString xs)) +#endif + +-- | 'foldl', applied to a binary operator, a starting value (typically +-- the left-identity of the operator), and a OsString, reduces the +-- OsString using the binary operator, from left to right. +-- +-- @since 1.4.200.0 +foldl :: (a -> PLATFORM_WORD -> a) -> a -> PLATFORM_STRING -> a +#ifdef WINDOWS +foldl f a (WindowsString s) = BS16.foldl (\a' c -> f a' (WindowsChar c)) a s +#else +foldl f a (PosixString s) = BS.foldl (\a' c -> f a' (PosixChar c)) a s +#endif + +-- | 'foldl'' is like 'foldl', but strict in the accumulator. +-- +-- @since 1.4.200.0 +foldl' + :: (a -> PLATFORM_WORD -> a) -> a -> PLATFORM_STRING -> a +#ifdef WINDOWS +foldl' f a (WindowsString s) = BS16.foldl' (\a' c -> f a' (WindowsChar c)) a s +#else +foldl' f a (PosixString s) = BS.foldl' (\a' c -> f a' (PosixChar c)) a s +#endif + +-- | 'foldl1' is a variant of 'foldl' that has no starting value +-- argument, and thus must be applied to non-empty 'OsString's. +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldl1 :: (PLATFORM_WORD -> PLATFORM_WORD -> PLATFORM_WORD) -> PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +foldl1 f (WindowsString s) = WindowsChar $ BS16.foldl1 (\a' c -> getWindowsChar $ f (WindowsChar a') (WindowsChar c)) s +#else +foldl1 f (PosixString s) = PosixChar $ BS.foldl1 (\a' c -> getPosixChar $ f (PosixChar a') (PosixChar c)) s +#endif + +-- | 'foldl1'' is like 'foldl1', but strict in the accumulator. +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldl1' + :: (PLATFORM_WORD -> PLATFORM_WORD -> PLATFORM_WORD) -> PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +foldl1' f (WindowsString s) = WindowsChar $ BS16.foldl1' (\a' c -> getWindowsChar $ f (WindowsChar a') (WindowsChar c)) s +#else +foldl1' f (PosixString s) = PosixChar $ BS.foldl1' (\a' c -> getPosixChar $ f (PosixChar a') (PosixChar c)) s +#endif + +-- | 'foldr', applied to a binary operator, a starting value +-- (typically the right-identity of the operator), and a OsString, +-- reduces the OsString using the binary operator, from right to left. +-- +-- @since 1.4.200.0 +foldr :: (PLATFORM_WORD -> a -> a) -> a -> PLATFORM_STRING -> a +#ifdef WINDOWS +foldr f a (WindowsString s) = BS16.foldr (\c a' -> f (WindowsChar c) a') a s +#else +foldr f a (PosixString s) = BS.foldr (\c a' -> f (PosixChar c) a') a s +#endif + +-- | 'foldr'' is like 'foldr', but strict in the accumulator. +-- +-- @since 1.4.200.0 +foldr' + :: (PLATFORM_WORD -> a -> a) -> a -> PLATFORM_STRING -> a +#ifdef WINDOWS +foldr' f a (WindowsString s) = BS16.foldr' (\c a' -> f (WindowsChar c) a') a s +#else +foldr' f a (PosixString s) = BS.foldr' (\c a' -> f (PosixChar c) a') a s +#endif + +-- | 'foldr1' is a variant of 'foldr' that has no starting value argument, +-- and thus must be applied to non-empty 'OsString's +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldr1 :: (PLATFORM_WORD -> PLATFORM_WORD -> PLATFORM_WORD) -> PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +foldr1 f (WindowsString s) = WindowsChar $ BS16.foldr1 (\c a' -> getWindowsChar $ f (WindowsChar c) (WindowsChar a')) s +#else +foldr1 f (PosixString s) = PosixChar $ BS.foldr1 (\c a' -> getPosixChar $ f (PosixChar c) (PosixChar a')) s +#endif + +-- | 'foldr1'' is a variant of 'foldr1', but is strict in the +-- accumulator. +-- +-- +-- @since 1.4.200.0 +-- @since 1.4.200.0 +foldr1' + :: (PLATFORM_WORD -> PLATFORM_WORD -> PLATFORM_WORD) -> PLATFORM_STRING -> PLATFORM_WORD +#ifdef WINDOWS +foldr1' f (WindowsString s) = WindowsChar $ BS16.foldr1' (\c a' -> getWindowsChar $ f (WindowsChar c) (WindowsChar a')) s +#else +foldr1' f (PosixString s) = PosixChar $ BS.foldr1' (\c a' -> getPosixChar $ f (PosixChar c) (PosixChar a')) s +#endif + +-- | /O(n)/ Applied to a predicate and a 'OsString', 'all' determines +-- if all elements of the 'OsString' satisfy the predicate. +-- +-- @since 1.4.200.0 +all :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +all f (WindowsString s) = BS16.all (f . WindowsChar) s +#else +all f (PosixString s) = BS.all (f . PosixChar) s +#endif + +-- | /O(n)/ Applied to a predicate and a 'OsString', 'any' determines if +-- any element of the 'OsString' satisfies the predicate. +-- +-- @since 1.4.200.0 +any :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +any f (WindowsString s) = BS16.any (f . WindowsChar) s +#else +any f (PosixString s) = BS.any (f . PosixChar) s +#endif + +-- /O(n)/ Concatenate a list of OsStrings. +-- +-- @since 1.4.200.0 +concat :: [PLATFORM_STRING] -> PLATFORM_STRING +concat = mconcat + +-- | /O(n)/ 'replicate' @n x@ is a OsString of length @n@ with @x@ +-- the value of every element. The following holds: +-- +-- > replicate w c = unfoldr w (\u -> Just (u,u)) c +-- +-- @since 1.4.200.0 +replicate :: Int -> PLATFORM_WORD -> PLATFORM_STRING +#ifdef WINDOWS +replicate i (WindowsChar w) = WindowsString $ BS16.replicate i w +#else +replicate i (PosixChar w) = PosixString $ BS.replicate i w +#endif + +-- | /O(n)/, where /n/ is the length of the result. The 'unfoldr' +-- function is analogous to the List \'unfoldr\'. 'unfoldr' builds a +-- OsString from a seed value. The function takes the element and +-- returns 'Nothing' if it is done producing the OsString or returns +-- 'Just' @(a,b)@, in which case, @a@ is the next byte in the string, +-- and @b@ is the seed value for further production. +-- +-- This function is not efficient/safe. It will build a list of @[Word8]@ +-- and run the generator until it returns `Nothing`, otherwise recurse infinitely, +-- then finally create a 'OsString'. +-- +-- If you know the maximum length, consider using 'unfoldrN'. +-- +-- Examples: +-- +-- > unfoldr (\x -> if x <= 5 then Just (x, x + 1) else Nothing) 0 +-- > == pack [0, 1, 2, 3, 4, 5] +-- +-- @since 1.4.200.0 +unfoldr :: (a -> Maybe (PLATFORM_WORD, a)) -> a -> PLATFORM_STRING +#ifdef WINDOWS +unfoldr f a = WindowsString $ BS16.unfoldr (fmap (first getWindowsChar) . f) a +#else +unfoldr f a = PosixString $ BS.unfoldr (fmap (first getPosixChar) . f) a +#endif + +-- | /O(n)/ Like 'unfoldr', 'unfoldrN' builds a OsString from a seed +-- value. However, the length of the result is limited by the first +-- argument to 'unfoldrN'. This function is more efficient than 'unfoldr' +-- when the maximum length of the result is known. +-- +-- The following equation relates 'unfoldrN' and 'unfoldr': +-- +-- > fst (unfoldrN n f s) == take n (unfoldr f s) +-- +-- @since 1.4.200.0 +unfoldrN :: forall a. Int -> (a -> Maybe (PLATFORM_WORD, a)) -> a -> (PLATFORM_STRING, Maybe a) +#ifdef WINDOWS +unfoldrN n f a = first WindowsString $ BS16.unfoldrN n (fmap (first getWindowsChar) . f) a +#else +unfoldrN n f a = first PosixString $ BS.unfoldrN n (fmap (first getPosixChar) . f) a +#endif + +-- | /O(n)/ 'take' @n@, applied to a OsString @xs@, returns the prefix +-- of @xs@ of length @n@, or @xs@ itself if @n > 'length' xs@. +-- +-- @since 1.4.200.0 +take :: Int -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +take n (WindowsString s) = WindowsString $ BS16.take n s +#else +take n (PosixString s) = PosixString $ BS.take n s +#endif + +-- | /O(n)/ @'takeEnd' n xs@ is equivalent to @'drop' ('length' xs - n) xs@. +-- Takes @n@ elements from end of bytestring. +-- +-- >>> takeEnd 3 "abcdefg" +-- "efg" +-- >>> takeEnd 0 "abcdefg" +-- "" +-- >>> takeEnd 4 "abc" +-- "abc" +-- +-- @since 1.4.200.0 +takeEnd :: Int -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +takeEnd n (WindowsString s) = WindowsString $ BS16.takeEnd n s +#else +takeEnd n (PosixString s) = PosixString $ BS.takeEnd n s +#endif + +-- | Returns the longest (possibly empty) suffix of elements +-- satisfying the predicate. +-- +-- @'takeWhileEnd' p@ is equivalent to @'reverse' . 'takeWhile' p . 'reverse'@. +-- +-- @since 1.4.200.0 +takeWhileEnd :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +takeWhileEnd f (WindowsString s) = WindowsString $ BS16.takeWhileEnd (f . WindowsChar) s +#else +takeWhileEnd f (PosixString s) = PosixString $ BS.takeWhileEnd (f . PosixChar) s +#endif + +-- | Similar to 'Prelude.takeWhile', +-- returns the longest (possibly empty) prefix of elements +-- satisfying the predicate. +-- +-- @since 1.4.200.0 +takeWhile :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +takeWhile f (WindowsString s) = WindowsString $ BS16.takeWhile (f . WindowsChar) s +#else +takeWhile f (PosixString s) = PosixString $ BS.takeWhile (f . PosixChar) s +#endif + +-- | /O(n)/ 'drop' @n@ @xs@ returns the suffix of @xs@ after the first n elements, or 'empty' if @n > 'length' xs@. +-- +-- @since 1.4.200.0 +drop :: Int -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +drop n (WindowsString s) = WindowsString $ BS16.drop n s +#else +drop n (PosixString s) = PosixString $ BS.drop n s +#endif + +-- | /O(n)/ @'dropEnd' n xs@ is equivalent to @'take' ('length' xs - n) xs@. +-- Drops @n@ elements from end of bytestring. +-- +-- >>> dropEnd 3 "abcdefg" +-- "abcd" +-- >>> dropEnd 0 "abcdefg" +-- "abcdefg" +-- >>> dropEnd 4 "abc" +-- "" +-- +-- @since 1.4.200.0 +dropEnd :: Int -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +dropEnd n (WindowsString s) = WindowsString $ BS16.dropEnd n s +#else +dropEnd n (PosixString s) = PosixString $ BS.dropEnd n s +#endif + +-- | Similar to 'Prelude.dropWhile', +-- drops the longest (possibly empty) prefix of elements +-- satisfying the predicate and returns the remainder. +-- +-- @since 1.4.200.0 +dropWhile :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +dropWhile f (WindowsString s) = WindowsString $ BS16.dropWhile (f . WindowsChar) s +#else +dropWhile f (PosixString s) = PosixString $ BS.dropWhile (f . PosixChar) s +#endif + +-- | Similar to 'Prelude.dropWhileEnd', +-- drops the longest (possibly empty) suffix of elements +-- satisfying the predicate and returns the remainder. +-- +-- @'dropWhileEnd' p@ is equivalent to @'reverse' . 'dropWhile' p . 'reverse'@. +-- +-- @since 1.4.200.0 +dropWhileEnd :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +dropWhileEnd f (WindowsString s) = WindowsString $ BS16.dropWhileEnd (f . WindowsChar) s +#else +dropWhileEnd f (PosixString s) = PosixString $ BS.dropWhileEnd (f . PosixChar) s +#endif + +-- | Returns the longest (possibly empty) suffix of elements which __do not__ +-- satisfy the predicate and the remainder of the string. +-- +-- 'breakEnd' @p@ is equivalent to @'spanEnd' (not . p)@ and to @('takeWhileEnd' (not . p) &&& 'dropWhileEnd' (not . p))@. +-- +-- @since 1.4.200.0 +breakEnd :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +breakEnd f (WindowsString s) = bimap WindowsString WindowsString $ BS16.breakEnd (f . WindowsChar) s +#else +breakEnd f (PosixString s) = bimap PosixString PosixString $ BS.breakEnd (f . PosixChar) s +#endif + +-- | Similar to 'Prelude.break', +-- returns the longest (possibly empty) prefix of elements which __do not__ +-- satisfy the predicate and the remainder of the string. +-- +-- 'break' @p@ is equivalent to @'span' (not . p)@ and to @('takeWhile' (not . p) &&& 'dropWhile' (not . p))@. +-- +-- @since 1.4.200.0 +break :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +break f (WindowsString s) = bimap WindowsString WindowsString $ BS16.break (f . WindowsChar) s +#else +break f (PosixString s) = bimap PosixString PosixString $ BS.break (f . PosixChar) s +#endif + +-- | Similar to 'Prelude.span', +-- returns the longest (possibly empty) prefix of elements +-- satisfying the predicate and the remainder of the string. +-- +-- 'span' @p@ is equivalent to @'break' (not . p)@ and to @('takeWhile' p &&& 'dropWhile' p)@. +-- +-- @since 1.4.200.0 +span :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +span f (WindowsString s) = bimap WindowsString WindowsString $ BS16.span (f . WindowsChar) s +#else +span f (PosixString s) = bimap PosixString PosixString $ BS.span (f . PosixChar) s +#endif + +-- | Returns the longest (possibly empty) suffix of elements +-- satisfying the predicate and the remainder of the string. +-- +-- 'spanEnd' @p@ is equivalent to @'breakEnd' (not . p)@ and to @('takeWhileEnd' p &&& 'dropWhileEnd' p)@. +-- +-- We have +-- +-- > spanEnd (not . isSpace) "x y z" == ("x y ", "z") +-- +-- and +-- +-- > spanEnd (not . isSpace) sbs +-- > == +-- > let (x, y) = span (not . isSpace) (reverse sbs) in (reverse y, reverse x) +-- +-- @since 1.4.200.0 +spanEnd :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +spanEnd f (WindowsString s) = bimap WindowsString WindowsString $ BS16.spanEnd (f . WindowsChar) s +#else +spanEnd f (PosixString s) = bimap PosixString PosixString $ BS.spanEnd (f . PosixChar) s +#endif + +-- | /O(n)/ 'splitAt' @n sbs@ is equivalent to @('take' n sbs, 'drop' n sbs)@. +-- +-- @since 1.4.200.0 +splitAt :: Int -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +splitAt n (WindowsString s) = bimap WindowsString WindowsString $ BS16.splitAt n s +#else +splitAt n (PosixString s) = bimap PosixString PosixString $ BS.splitAt n s +#endif + +-- | /O(n)/ Break a 'OsString' into pieces separated by the byte +-- argument, consuming the delimiter. I.e. +-- +-- > split 10 "a\nb\nd\ne" == ["a","b","d","e"] -- fromEnum '\n' == 10 +-- > split 97 "aXaXaXa" == ["","X","X","X",""] -- fromEnum 'a' == 97 +-- > split 120 "x" == ["",""] -- fromEnum 'x' == 120 +-- > split undefined "" == [] -- and not [""] +-- +-- and +-- +-- > intercalate [c] . split c == id +-- > split == splitWith . (==) +-- +-- @since 1.4.200.0 +split :: PLATFORM_WORD -> PLATFORM_STRING -> [PLATFORM_STRING] +#ifdef WINDOWS +split (WindowsChar w) (WindowsString s) = WindowsString <$> BS16.split w s +#else +split (PosixChar w) (PosixString s) = PosixString <$> BS.split w s +#endif + +-- | /O(n)/ Splits a 'OsString' into components delimited by +-- separators, where the predicate returns True for a separator element. +-- The resulting components do not contain the separators. Two adjacent +-- separators result in an empty component in the output. eg. +-- +-- > splitWith (==97) "aabbaca" == ["","","bb","c",""] -- fromEnum 'a' == 97 +-- > splitWith undefined "" == [] -- and not [""] +-- +-- @since 1.4.200.0 +splitWith :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> [PLATFORM_STRING] +#ifdef WINDOWS +splitWith f (WindowsString s) = WindowsString <$> BS16.splitWith (f . WindowsChar) s +#else +splitWith f (PosixString s) = PosixString <$> BS.splitWith (f . PosixChar) s +#endif + +-- | /O(n)/ The 'stripSuffix' function takes two OsStrings and returns 'Just' +-- the remainder of the second iff the first is its suffix, and otherwise +-- 'Nothing'. +-- +-- @since 1.4.200.0 +stripSuffix :: PLATFORM_STRING -> PLATFORM_STRING -> Maybe PLATFORM_STRING +#ifdef WINDOWS +stripSuffix (WindowsString a) (WindowsString b) = WindowsString <$> BS16.stripSuffix a b +#else +stripSuffix (PosixString a) (PosixString b) = PosixString <$> BS.stripSuffix a b +#endif + +-- | /O(n)/ The 'stripPrefix' function takes two OsStrings and returns 'Just' +-- the remainder of the second iff the first is its prefix, and otherwise +-- 'Nothing'. +-- +-- @since 1.4.200.0 +stripPrefix :: PLATFORM_STRING -> PLATFORM_STRING -> Maybe PLATFORM_STRING +#ifdef WINDOWS +stripPrefix (WindowsString a) (WindowsString b) = WindowsString <$> BS16.stripPrefix a b +#else +stripPrefix (PosixString a) (PosixString b) = PosixString <$> BS.stripPrefix a b +#endif + + +-- | Check whether one string is a substring of another. +-- +-- @since 1.4.200.0 +isInfixOf :: PLATFORM_STRING -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +isInfixOf (WindowsString a) (WindowsString b) = BS16.isInfixOf a b +#else +isInfixOf (PosixString a) (PosixString b) = BS.isInfixOf a b +#endif + +-- |/O(n)/ The 'isPrefixOf' function takes two OsStrings and returns 'True' +-- +-- @since 1.4.200.0 +isPrefixOf :: PLATFORM_STRING -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +isPrefixOf (WindowsString a) (WindowsString b) = BS16.isPrefixOf a b +#else +isPrefixOf (PosixString a) (PosixString b) = BS.isPrefixOf a b +#endif + +-- | /O(n)/ The 'isSuffixOf' function takes two OsStrings and returns 'True' +-- iff the first is a suffix of the second. +-- +-- The following holds: +-- +-- > isSuffixOf x y == reverse x `isPrefixOf` reverse y +-- +-- @since 1.4.200.0 +isSuffixOf :: PLATFORM_STRING -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +isSuffixOf (WindowsString a) (WindowsString b) = BS16.isSuffixOf a b +#else +isSuffixOf (PosixString a) (PosixString b) = BS.isSuffixOf a b +#endif + + +-- | Break a string on a substring, returning a pair of the part of the +-- string prior to the match, and the rest of the string. +-- +-- The following relationships hold: +-- +-- > break (== c) l == breakSubstring (singleton c) l +-- +-- For example, to tokenise a string, dropping delimiters: +-- +-- > tokenise x y = h : if null t then [] else tokenise x (drop (length x) t) +-- > where (h,t) = breakSubstring x y +-- +-- To skip to the first occurrence of a string: +-- +-- > snd (breakSubstring x y) +-- +-- To take the parts of a string before a delimiter: +-- +-- > fst (breakSubstring x y) +-- +-- Note that calling `breakSubstring x` does some preprocessing work, so +-- you should avoid unnecessarily duplicating breakSubstring calls with the same +-- pattern. +-- +-- @since 1.4.200.0 +breakSubstring :: PLATFORM_STRING -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +breakSubstring (WindowsString a) (WindowsString b) = bimap WindowsString WindowsString $ BS16.breakSubstring a b +#else +breakSubstring (PosixString a) (PosixString b) = bimap PosixString PosixString $ BS.breakSubstring a b +#endif + +-- | /O(n)/ 'elem' is the 'OsString' membership predicate. +-- +-- @since 1.4.200.0 +elem :: PLATFORM_WORD -> PLATFORM_STRING -> Bool +#ifdef WINDOWS +elem (WindowsChar w) (WindowsString s) = BS16.elem w s +#else +elem (PosixChar w) (PosixString s) = BS.elem w s +#endif + +-- | /O(n)/ The 'find' function takes a predicate and a OsString, +-- and returns the first element in matching the predicate, or 'Nothing' +-- if there is no such element. +-- +-- > find f p = case findIndex f p of Just n -> Just (p ! n) ; _ -> Nothing +-- +-- @since 1.4.200.0 +find :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> Maybe PLATFORM_WORD +#ifdef WINDOWS +find f (WindowsString s) = WindowsChar <$> BS16.find (f . WindowsChar) s +#else +find f (PosixString s) = PosixChar <$> BS.find (f . PosixChar) s +#endif + +-- | /O(n)/ 'filter', applied to a predicate and a OsString, +-- returns a OsString containing those characters that satisfy the +-- predicate. +-- +-- @since 1.4.200.0 +filter :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> PLATFORM_STRING +#ifdef WINDOWS +filter f (WindowsString s) = WindowsString $ BS16.filter (f . WindowsChar) s +#else +filter f (PosixString s) = PosixString $ BS.filter (f . PosixChar) s +#endif + +-- | /O(n)/ The 'partition' function takes a predicate a OsString and returns +-- the pair of OsStrings with elements which do and do not satisfy the +-- predicate, respectively; i.e., +-- +-- > partition p bs == (filter p sbs, filter (not . p) sbs) +-- +-- @since 1.4.200.0 +partition :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> (PLATFORM_STRING, PLATFORM_STRING) +#ifdef WINDOWS +partition f (WindowsString s) = bimap WindowsString WindowsString $ BS16.partition (f . WindowsChar) s +#else +partition f (PosixString s) = bimap PosixString PosixString $ BS.partition (f . PosixChar) s +#endif + +-- | /O(1)/ 'OsString' index (subscript) operator, starting from 0. +-- +-- @since 1.4.200.0 +index :: HasCallStack => PLATFORM_STRING -> Int -> PLATFORM_WORD +#ifdef WINDOWS +index (WindowsString s) n = WindowsChar $ BS16.index s n +#else +index (PosixString s) n = PosixChar $ BS.index s n +#endif + +-- | /O(1)/ 'OsString' index, starting from 0, that returns 'Just' if: +-- +-- > 0 <= n < length bs +-- +-- @since 1.4.200.0 +indexMaybe :: PLATFORM_STRING -> Int -> Maybe PLATFORM_WORD +#ifdef WINDOWS +indexMaybe (WindowsString s) n = WindowsChar <$> BS16.indexMaybe s n +#else +indexMaybe (PosixString s) n = PosixChar <$> BS.indexMaybe s n +#endif + +-- | /O(1)/ 'OsString' index, starting from 0, that returns 'Just' if: +-- +-- > 0 <= n < length bs +-- +-- @since 1.4.200.0 +(!?) :: PLATFORM_STRING -> Int -> Maybe PLATFORM_WORD +(!?) = indexMaybe + +-- | /O(n)/ The 'elemIndex' function returns the index of the first +-- element in the given 'OsString' which is equal to the query +-- element, or 'Nothing' if there is no such element. +-- +-- @since 1.4.200.0 +elemIndex :: PLATFORM_WORD -> PLATFORM_STRING -> Maybe Int +#ifdef WINDOWS +elemIndex (WindowsChar w) (WindowsString s) = BS16.elemIndex w s +#else +elemIndex (PosixChar w) (PosixString s) = BS.elemIndex w s +#endif + +-- | /O(n)/ The 'elemIndices' function extends 'elemIndex', by returning +-- the indices of all elements equal to the query element, in ascending order. +-- +-- @since 1.4.200.0 +elemIndices :: PLATFORM_WORD -> PLATFORM_STRING -> [Int] +#ifdef WINDOWS +elemIndices (WindowsChar w) (WindowsString s) = BS16.elemIndices w s +#else +elemIndices (PosixChar w) (PosixString s) = BS.elemIndices w s +#endif + +-- | count returns the number of times its argument appears in the OsString +-- +-- @since 1.4.200.0 +count :: PLATFORM_WORD -> PLATFORM_STRING -> Int +#ifdef WINDOWS +count (WindowsChar w) (WindowsString s) = BS16.count w s +#else +count (PosixChar w) (PosixString s) = BS.count w s +#endif + +-- | /O(n)/ The 'findIndex' function takes a predicate and a 'OsString' and +-- returns the index of the first element in the OsString +-- satisfying the predicate. +-- +-- @since 1.4.200.0 +findIndex :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> Maybe Int +#ifdef WINDOWS +findIndex f (WindowsString s) = BS16.findIndex (f . WindowsChar) s +#else +findIndex f (PosixString s) = BS.findIndex (f . PosixChar) s +#endif + +-- | /O(n)/ The 'findIndices' function extends 'findIndex', by returning the +-- indices of all elements satisfying the predicate, in ascending order. +-- +-- @since 1.4.200.0 +findIndices :: (PLATFORM_WORD -> Bool) -> PLATFORM_STRING -> [Int] +#ifdef WINDOWS +findIndices f (WindowsString s) = BS16.findIndices (f . WindowsChar) s +#else +findIndices f (PosixString s) = BS.findIndices (f . PosixChar) s +#endif diff --git a/System/OsString/Internal.hs b/System/OsString/Internal.hs index f72fdcb7..7023bd10 100644 --- a/System/OsString/Internal.hs +++ b/System/OsString/Internal.hs @@ -27,6 +27,8 @@ import qualified System.OsString.Windows as PF import GHC.IO.Encoding.UTF8 ( mkUTF8 ) import qualified System.OsString.Posix as PF #endif +import GHC.Stack (HasCallStack) +import Data.Bifunctor @@ -159,6 +161,12 @@ unpack (OsString x) = OsChar <$> PF.unpack x pack :: [OsChar] -> OsString pack = OsString . PF.pack . fmap (\(OsChar x) -> x) +empty :: OsString +empty = mempty + +singleton :: OsChar -> OsString +singleton = OsString . PF.singleton . getOsChar + -- | Truncates on unix to 1 and on Windows to 2 octets. unsafeFromChar :: Char -> OsChar @@ -172,3 +180,540 @@ toChar (OsChar (WindowsChar w)) = chr $ fromIntegral w toChar (OsChar (PosixChar w)) = chr $ fromIntegral w #endif +-- | /O(n)/ Append a byte to the end of a 'OsString' +-- +-- @since 1.4.200.0 +snoc :: OsString -> OsChar -> OsString +snoc (OsString s) (OsChar w) = OsString (PF.snoc s w) + +-- | /O(n)/ 'cons' is analogous to (:) for lists. +-- +-- @since 1.4.200.0 +cons :: OsChar -> OsString -> OsString +cons (OsChar w) (OsString s) = OsString (PF.cons w s) + +-- | /O(1)/ Extract the last element of a OsString, which must be finite and non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'unsnoc' instead. +-- +-- @since 1.4.200.0 +last :: HasCallStack => OsString -> OsChar +last (OsString s) = OsChar (PF.last s) + +-- | /O(n)/ Extract the elements after the head of a OsString, which must be non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'uncons' instead. +-- +-- @since 1.4.200.0 +tail :: HasCallStack => OsString -> OsString +tail (OsString s) = OsString (PF.tail s) + +-- | /O(n)/ Extract the 'head' and 'tail' of a OsString, returning 'Nothing' +-- if it is empty. +-- +-- @since 1.4.200.0 +uncons :: OsString -> Maybe (OsChar, OsString) +uncons (OsString s) = bimap OsChar OsString <$> PF.uncons s + +-- | /O(1)/ Extract the first element of a OsString, which must be non-empty. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'uncons' instead. +-- +-- @since 1.4.200.0 +head :: HasCallStack => OsString -> OsChar +head (OsString s) = OsChar (PF.head s) + +-- | /O(n)/ Return all the elements of a 'OsString' except the last one. +-- An exception will be thrown in the case of an empty OsString. +-- +-- This is a partial function, consider using 'unsnoc' instead. +-- +-- @since 1.4.200.0 +init :: HasCallStack => OsString -> OsString +init (OsString s) = OsString (PF.init s) + +-- | /O(n)/ Extract the 'init' and 'last' of a OsString, returning 'Nothing' +-- if it is empty. +-- +-- @since 1.4.200.0 +unsnoc :: OsString -> Maybe (OsString, OsChar) +unsnoc (OsString s) = bimap OsString OsChar <$> PF.unsnoc s + +-- | /O(1)/ Test whether a 'OsString' is empty. +-- +-- @since 1.4.200.0 +null :: OsString -> Bool +null (OsString s) = PF.null s + +-- | /O(1)/ The length of a 'OsString'. +-- +-- @since 1.4.200.0 +length :: OsString -> Int +length (OsString s) = PF.length s + +-- | /O(n)/ 'map' @f xs@ is the OsString obtained by applying @f@ to each +-- element of @xs@. +-- +-- @since 1.4.200.0 +map :: (OsChar -> OsChar) -> OsString -> OsString +map f (OsString s) = OsString (PF.map (getOsChar . f . OsChar) s) + +-- | /O(n)/ 'reverse' @xs@ efficiently returns the elements of @xs@ in reverse order. +-- +-- @since 1.4.200.0 +reverse :: OsString -> OsString +reverse (OsString s) = OsString (PF.reverse s) + +-- | /O(n)/ The 'intercalate' function takes a 'OsString' and a list of +-- 'OsString's and concatenates the list after interspersing the first +-- argument between each element of the list. +-- +-- @since 1.4.200.0 +intercalate :: OsString -> [OsString] -> OsString +intercalate (OsString s) xs = OsString (PF.intercalate s (fmap getOsString xs)) + +-- | 'foldl', applied to a binary operator, a starting value (typically +-- the left-identity of the operator), and a OsString, reduces the +-- OsString using the binary operator, from left to right. +-- +-- @since 1.4.200.0 +foldl :: (a -> OsChar -> a) -> a -> OsString -> a +foldl f a (OsString s) = PF.foldl (\a' c -> f a' (OsChar c)) a s + +-- | 'foldl'' is like 'foldl', but strict in the accumulator. +-- +-- @since 1.4.200.0 +foldl' :: (a -> OsChar -> a) -> a -> OsString -> a +foldl' f a (OsString s) = PF.foldl' (\a' c -> f a' (OsChar c)) a s + +-- | 'foldl1' is a variant of 'foldl' that has no starting value +-- argument, and thus must be applied to non-empty 'OsString's. +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldl1 :: (OsChar -> OsChar -> OsChar) -> OsString -> OsChar +foldl1 f (OsString s) = OsChar $ PF.foldl1 (\a' c -> getOsChar $ f (OsChar a') (OsChar c)) s + +-- | 'foldl1'' is like 'foldl1', but strict in the accumulator. +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldl1' :: (OsChar -> OsChar -> OsChar) -> OsString -> OsChar +foldl1' f (OsString s) = OsChar $ PF.foldl1' (\a' c -> getOsChar $ f (OsChar a') (OsChar c)) s + + +-- | 'foldr', applied to a binary operator, a starting value +-- (typically the right-identity of the operator), and a OsString, +-- reduces the OsString using the binary operator, from right to left. +-- +-- @since 1.4.200.0 +foldr :: (OsChar -> a -> a) -> a -> OsString -> a +foldr f a (OsString s) = PF.foldr (\c a' -> f (OsChar c) a') a s + +-- | 'foldr'' is like 'foldr', but strict in the accumulator. +-- +-- @since 1.4.200.0 +foldr' :: (OsChar -> a -> a) -> a -> OsString -> a +foldr' f a (OsString s) = PF.foldr' (\c a' -> f (OsChar c) a') a s + +-- | 'foldr1' is a variant of 'foldr' that has no starting value argument, +-- and thus must be applied to non-empty 'OsString's +-- An exception will be thrown in the case of an empty OsString. +-- +-- @since 1.4.200.0 +foldr1 :: (OsChar -> OsChar -> OsChar) -> OsString -> OsChar +foldr1 f (OsString s) = OsChar $ PF.foldr1 (\c a' -> getOsChar $ f (OsChar c) (OsChar a')) s + +-- | 'foldr1'' is a variant of 'foldr1', but is strict in the +-- accumulator. +-- +-- @since 1.4.200.0 +foldr1' :: (OsChar -> OsChar -> OsChar) -> OsString -> OsChar +foldr1' f (OsString s) = OsChar $ PF.foldr1' (\c a' -> getOsChar $ f (OsChar c) (OsChar a')) s + +-- | /O(n)/ Applied to a predicate and a 'OsString', 'all' determines +-- if all elements of the 'OsString' satisfy the predicate. +-- +-- @since 1.4.200.0 +all :: (OsChar -> Bool) -> OsString -> Bool +all f (OsString s) = PF.all (f . OsChar) s + +-- | /O(n)/ Applied to a predicate and a 'OsString', 'any' determines if +-- any element of the 'OsString' satisfies the predicate. +-- +-- @since 1.4.200.0 +any :: (OsChar -> Bool) -> OsString -> Bool +any f (OsString s) = PF.any (f . OsChar) s + +-- /O(n)/ Concatenate a list of OsStrings. +-- +-- @since 1.4.200.0 +concat :: [OsString] -> OsString +concat = mconcat + +-- | /O(n)/ 'replicate' @n x@ is a OsString of length @n@ with @x@ +-- the value of every element. The following holds: +-- +-- > replicate w c = unfoldr w (\u -> Just (u,u)) c +-- +-- @since 1.4.200.0 +replicate :: Int -> OsChar -> OsString +replicate i (OsChar w) = OsString $ PF.replicate i w + +-- | /O(n)/, where /n/ is the length of the result. The 'unfoldr' +-- function is analogous to the List \'unfoldr\'. 'unfoldr' builds a +-- OsString from a seed value. The function takes the element and +-- returns 'Nothing' if it is done producing the OsString or returns +-- 'Just' @(a,b)@, in which case, @a@ is the next byte in the string, +-- and @b@ is the seed value for further production. +-- +-- This function is not efficient/safe. It will build a list of @[Word8]@ +-- and run the generator until it returns `Nothing`, otherwise recurse infinitely, +-- then finally create a 'OsString'. +-- +-- If you know the maximum length, consider using 'unfoldrN'. +-- +-- Examples: +-- +-- > unfoldr (\x -> if x <= 5 then Just (x, x + 1) else Nothing) 0 +-- > == pack [0, 1, 2, 3, 4, 5] +-- +-- @since 1.4.200.0 +unfoldr :: (a -> Maybe (OsChar, a)) -> a -> OsString +unfoldr f a = OsString $ PF.unfoldr (fmap (first getOsChar) . f) a + +-- | /O(n)/ Like 'unfoldr', 'unfoldrN' builds a OsString from a seed +-- value. However, the length of the result is limited by the first +-- argument to 'unfoldrN'. This function is more efficient than 'unfoldr' +-- when the maximum length of the result is known. +-- +-- The following equation relates 'unfoldrN' and 'unfoldr': +-- +-- > fst (unfoldrN n f s) == take n (unfoldr f s) +-- +-- @since 1.4.200.0 +unfoldrN :: forall a. Int -> (a -> Maybe (OsChar, a)) -> a -> (OsString, Maybe a) +unfoldrN n f a = first OsString $ PF.unfoldrN n (fmap (first getOsChar) . f) a + +-- | /O(n)/ 'take' @n@, applied to a OsString @xs@, returns the prefix +-- of @xs@ of length @n@, or @xs@ itself if @n > 'length' xs@. +-- +-- @since 1.4.200.0 +take :: Int -> OsString -> OsString +take n (OsString s) = OsString $ PF.take n s + +-- | /O(n)/ @'takeEnd' n xs@ is equivalent to @'drop' ('length' xs - n) xs@. +-- Takes @n@ elements from end of bytestring. +-- +-- >>> takeEnd 3 "abcdefg" +-- "efg" +-- >>> takeEnd 0 "abcdefg" +-- "" +-- >>> takeEnd 4 "abc" +-- "abc" +-- +-- @since 1.4.200.0 +takeEnd :: Int -> OsString -> OsString +takeEnd n (OsString s) = OsString $ PF.takeEnd n s + +-- | Returns the longest (possibly empty) suffix of elements +-- satisfying the predicate. +-- +-- @'takeWhileEnd' p@ is equivalent to @'reverse' . 'takeWhile' p . 'reverse'@. +-- +-- @since 1.4.200.0 +takeWhileEnd :: (OsChar -> Bool) -> OsString -> OsString +takeWhileEnd f (OsString s) = OsString $ PF.takeWhileEnd (f . OsChar) s + +-- | Similar to 'Prelude.takeWhile', +-- returns the longest (possibly empty) prefix of elements +-- satisfying the predicate. +-- +-- @since 1.4.200.0 +takeWhile :: (OsChar -> Bool) -> OsString -> OsString +takeWhile f (OsString s) = OsString $ PF.takeWhile (f . OsChar) s + +-- | /O(n)/ 'drop' @n@ @xs@ returns the suffix of @xs@ after the first n elements, or 'empty' if @n > 'length' xs@. +-- +-- @since 1.4.200.0 +drop :: Int -> OsString -> OsString +drop n (OsString s) = OsString $ PF.drop n s + +-- | /O(n)/ @'dropEnd' n xs@ is equivalent to @'take' ('length' xs - n) xs@. +-- Drops @n@ elements from end of bytestring. +-- +-- >>> dropEnd 3 "abcdefg" +-- "abcd" +-- >>> dropEnd 0 "abcdefg" +-- "abcdefg" +-- >>> dropEnd 4 "abc" +-- "" +-- +-- @since 1.4.200.0 +dropEnd :: Int -> OsString -> OsString +dropEnd n (OsString s) = OsString $ PF.dropEnd n s + +-- | Similar to 'Prelude.dropWhile', +-- drops the longest (possibly empty) prefix of elements +-- satisfying the predicate and returns the remainder. +-- +-- @since 1.4.200.0 +dropWhile :: (OsChar -> Bool) -> OsString -> OsString +dropWhile f (OsString s) = OsString $ PF.dropWhile (f . OsChar) s + +-- | Similar to 'Prelude.dropWhileEnd', +-- drops the longest (possibly empty) suffix of elements +-- satisfying the predicate and returns the remainder. +-- +-- @'dropWhileEnd' p@ is equivalent to @'reverse' . 'dropWhile' p . 'reverse'@. +-- +-- @since 1.4.200.0 +dropWhileEnd :: (OsChar -> Bool) -> OsString -> OsString +dropWhileEnd f (OsString s) = OsString $ PF.dropWhileEnd (f . OsChar) s + +-- | Returns the longest (possibly empty) suffix of elements which __do not__ +-- satisfy the predicate and the remainder of the string. +-- +-- 'breakEnd' @p@ is equivalent to @'spanEnd' (not . p)@ and to @('takeWhileEnd' (not . p) &&& 'dropWhileEnd' (not . p))@. +-- +-- @since 1.4.200.0 +breakEnd :: (OsChar -> Bool) -> OsString -> (OsString, OsString) +breakEnd f (OsString s) = bimap OsString OsString $ PF.breakEnd (f . OsChar) s + +-- | Similar to 'Prelude.break', +-- returns the longest (possibly empty) prefix of elements which __do not__ +-- satisfy the predicate and the remainder of the string. +-- +-- 'break' @p@ is equivalent to @'span' (not . p)@ and to @('takeWhile' (not . p) &&& 'dropWhile' (not . p))@. +-- +-- @since 1.4.200.0 +break :: (OsChar -> Bool) -> OsString -> (OsString, OsString) +break f (OsString s) = bimap OsString OsString $ PF.break (f . OsChar) s + +-- | Similar to 'Prelude.span', +-- returns the longest (possibly empty) prefix of elements +-- satisfying the predicate and the remainder of the string. +-- +-- 'span' @p@ is equivalent to @'break' (not . p)@ and to @('takeWhile' p &&& 'dropWhile' p)@. +-- +-- @since 1.4.200.0 +span :: (OsChar -> Bool) -> OsString -> (OsString, OsString) +span f (OsString s) = bimap OsString OsString $ PF.span (f . OsChar) s + +-- | Returns the longest (possibly empty) suffix of elements +-- satisfying the predicate and the remainder of the string. +-- +-- 'spanEnd' @p@ is equivalent to @'breakEnd' (not . p)@ and to @('takeWhileEnd' p &&& 'dropWhileEnd' p)@. +-- +-- We have +-- +-- > spanEnd (not . isSpace) "x y z" == ("x y ", "z") +-- +-- and +-- +-- > spanEnd (not . isSpace) sbs +-- > == +-- > let (x, y) = span (not . isSpace) (reverse sbs) in (reverse y, reverse x) +-- +-- @since 1.4.200.0 +spanEnd :: (OsChar -> Bool) -> OsString -> (OsString, OsString) +spanEnd f (OsString s) = bimap OsString OsString $ PF.spanEnd (f . OsChar) s + +-- | /O(n)/ 'splitAt' @n sbs@ is equivalent to @('take' n sbs, 'drop' n sbs)@. +-- +-- @since 1.4.200.0 +splitAt :: Int -> OsString -> (OsString, OsString) +splitAt n (OsString s) = bimap OsString OsString $ PF.splitAt n s + +-- | /O(n)/ Break a 'OsString' into pieces separated by the byte +-- argument, consuming the delimiter. I.e. +-- +-- > split 10 "a\nb\nd\ne" == ["a","b","d","e"] -- fromEnum '\n' == 10 +-- > split 97 "aXaXaXa" == ["","X","X","X",""] -- fromEnum 'a' == 97 +-- > split 120 "x" == ["",""] -- fromEnum 'x' == 120 +-- > split undefined "" == [] -- and not [""] +-- +-- and +-- +-- > intercalate [c] . split c == id +-- > split == splitWith . (==) +-- +-- @since 1.4.200.0 +split :: OsChar -> OsString -> [OsString] +split (OsChar w) (OsString s) = OsString <$> PF.split w s + +-- | /O(n)/ Splits a 'OsString' into components delimited by +-- separators, where the predicate returns True for a separator element. +-- The resulting components do not contain the separators. Two adjacent +-- separators result in an empty component in the output. eg. +-- +-- > splitWith (==97) "aabbaca" == ["","","bb","c",""] -- fromEnum 'a' == 97 +-- > splitWith undefined "" == [] -- and not [""] +-- +-- @since 1.4.200.0 +splitWith :: (OsChar -> Bool) -> OsString -> [OsString] +splitWith f (OsString s) = OsString <$> PF.splitWith (f . OsChar) s + +-- | /O(n)/ The 'stripSuffix' function takes two OsStrings and returns 'Just' +-- the remainder of the second iff the first is its suffix, and otherwise +-- 'Nothing'. +-- +-- @since 1.4.200.0 +stripSuffix :: OsString -> OsString -> Maybe OsString +stripSuffix (OsString a) (OsString b) = OsString <$> PF.stripSuffix a b + +-- | /O(n)/ The 'stripPrefix' function takes two OsStrings and returns 'Just' +-- the remainder of the second iff the first is its prefix, and otherwise +-- 'Nothing'. +-- +-- @since 1.4.200.0 +stripPrefix :: OsString -> OsString -> Maybe OsString +stripPrefix (OsString a) (OsString b) = OsString <$> PF.stripPrefix a b + + +-- | Check whether one string is a substring of another. +-- +-- @since 1.4.200.0 +isInfixOf :: OsString -> OsString -> Bool +isInfixOf (OsString a) (OsString b) = PF.isInfixOf a b + +-- |/O(n)/ The 'isPrefixOf' function takes two OsStrings and returns 'True' +-- +-- @since 1.4.200.0 +isPrefixOf :: OsString -> OsString -> Bool +isPrefixOf (OsString a) (OsString b) = PF.isPrefixOf a b + +-- | /O(n)/ The 'isSuffixOf' function takes two OsStrings and returns 'True' +-- iff the first is a suffix of the second. +-- +-- The following holds: +-- +-- > isSuffixOf x y == reverse x `isPrefixOf` reverse y +-- +-- @since 1.4.200.0 +isSuffixOf :: OsString -> OsString -> Bool +isSuffixOf (OsString a) (OsString b) = PF.isSuffixOf a b + +-- | Break a string on a substring, returning a pair of the part of the +-- string prior to the match, and the rest of the string. +-- +-- The following relationships hold: +-- +-- > break (== c) l == breakSubstring (singleton c) l +-- +-- For example, to tokenise a string, dropping delimiters: +-- +-- > tokenise x y = h : if null t then [] else tokenise x (drop (length x) t) +-- > where (h,t) = breakSubstring x y +-- +-- To skip to the first occurrence of a string: +-- +-- > snd (breakSubstring x y) +-- +-- To take the parts of a string before a delimiter: +-- +-- > fst (breakSubstring x y) +-- +-- Note that calling `breakSubstring x` does some preprocessing work, so +-- you should avoid unnecessarily duplicating breakSubstring calls with the same +-- pattern. +-- +-- @since 1.4.200.0 +breakSubstring :: OsString -> OsString -> (OsString, OsString) +breakSubstring (OsString a) (OsString b) = bimap OsString OsString $ PF.breakSubstring a b + +-- | /O(n)/ 'elem' is the 'OsString' membership predicate. +-- +-- @since 1.4.200.0 +elem :: OsChar -> OsString -> Bool +elem (OsChar w) (OsString s) = PF.elem w s + +-- | /O(n)/ The 'find' function takes a predicate and a OsString, +-- and returns the first element in matching the predicate, or 'Nothing' +-- if there is no such element. +-- +-- > find f p = case findIndex f p of Just n -> Just (p ! n) ; _ -> Nothing +-- +-- @since 1.4.200.0 +find :: (OsChar -> Bool) -> OsString -> Maybe OsChar +find f (OsString s) = OsChar <$> PF.find (f . OsChar) s + +-- | /O(n)/ 'filter', applied to a predicate and a OsString, +-- returns a OsString containing those characters that satisfy the +-- predicate. +-- +-- @since 1.4.200.0 +filter :: (OsChar -> Bool) -> OsString -> OsString +filter f (OsString s) = OsString $ PF.filter (f . OsChar) s + +-- | /O(n)/ The 'partition' function takes a predicate a OsString and returns +-- the pair of OsStrings with elements which do and do not satisfy the +-- predicate, respectively; i.e., +-- +-- > partition p bs == (filter p sbs, filter (not . p) sbs) +-- +-- @since 1.4.200.0 +partition :: (OsChar -> Bool) -> OsString -> (OsString, OsString) +partition f (OsString s) = bimap OsString OsString $ PF.partition (f . OsChar) s + +-- | /O(1)/ 'OsString' index (subscript) operator, starting from 0. +-- +-- @since 1.4.200.0 +index :: HasCallStack => OsString -> Int -> OsChar +index (OsString s) n = OsChar $ PF.index s n + +-- | /O(1)/ 'OsString' index, starting from 0, that returns 'Just' if: +-- +-- > 0 <= n < length bs +-- +-- @since 1.4.200.0 +indexMaybe :: OsString -> Int -> Maybe OsChar +indexMaybe (OsString s) n = OsChar <$> PF.indexMaybe s n + +-- | /O(1)/ 'OsString' index, starting from 0, that returns 'Just' if: +-- +-- > 0 <= n < length bs +-- +-- @since 1.4.200.0 +(!?) :: OsString -> Int -> Maybe OsChar +(!?) = indexMaybe + +-- | /O(n)/ The 'elemIndex' function returns the index of the first +-- element in the given 'OsString' which is equal to the query +-- element, or 'Nothing' if there is no such element. +-- +-- @since 1.4.200.0 +elemIndex :: OsChar -> OsString -> Maybe Int +elemIndex (OsChar w) (OsString s) = PF.elemIndex w s + +-- | /O(n)/ The 'elemIndices' function extends 'elemIndex', by returning +-- the indices of all elements equal to the query element, in ascending order. +-- +-- @since 1.4.200.0 +elemIndices :: OsChar -> OsString -> [Int] +elemIndices (OsChar w) (OsString s) = PF.elemIndices w s + +-- | count returns the number of times its argument appears in the OsString +-- +-- @since 1.4.200.0 +count :: OsChar -> OsString -> Int +count (OsChar w) (OsString s) = PF.count w s + +-- | /O(n)/ The 'findIndex' function takes a predicate and a 'OsString' and +-- returns the index of the first element in the OsString +-- satisfying the predicate. +-- +-- @since 1.4.200.0 +findIndex :: (OsChar -> Bool) -> OsString -> Maybe Int +findIndex f (OsString s) = PF.findIndex (f . OsChar) s + +-- | /O(n)/ The 'findIndices' function extends 'findIndex', by returning the +-- indices of all elements satisfying the predicate, in ascending order. +-- +-- @since 1.4.200.0 +findIndices :: (OsChar -> Bool) -> OsString -> [Int] +findIndices f (OsString s) = PF.findIndices (f . OsChar) s + diff --git a/changelog.md b/changelog.md index a5bca439..9d4bb126 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ _Note: below all `FilePath` values are unquoted, so `\\` really means two backslashes._ +## 1.4.200.0 *??? 2023* + +* Introduce bytestring-like functions (substrings, predicates, searching, etc.) to `System.OsString`, `System.OsString.Windows` and `System.OsString.Posix` + ## 1.4.100.4 *Jul 2023* * Fix isInfixOf and breakSubString in Word16, wrt [#195](https://github.com/haskell/filepath/issues/195) diff --git a/filepath.cabal b/filepath.cabal index c3b76370..ae4eba4f 100644 --- a/filepath.cabal +++ b/filepath.cabal @@ -1,6 +1,6 @@ cabal-version: 2.2 name: filepath -version: 1.4.100.4 +version: 1.4.200.0 -- NOTE: Don't forget to update ./changelog.md license: BSD-3-Clause @@ -158,6 +158,9 @@ test-suite bytestring-tests hs-source-dirs: tests tests/bytestring-tests other-modules: Properties.ShortByteString + Properties.WindowsString + Properties.PosixString + Properties.OsString Properties.ShortByteString.Word16 TestUtil diff --git a/tests/abstract-filepath/OsPathSpec.hs b/tests/abstract-filepath/OsPathSpec.hs index bee6fb57..8f334516 100644 --- a/tests/abstract-filepath/OsPathSpec.hs +++ b/tests/abstract-filepath/OsPathSpec.hs @@ -14,8 +14,8 @@ import System.OsPath.Windows as Windows import System.OsPath.Encoding import qualified System.OsString.Internal.Types as OS import System.OsPath.Data.ByteString.Short ( toShort ) -import System.OsString.Posix as PosixS -import System.OsString.Windows as WindowsS +import System.OsString.Posix as PosixS hiding (map) +import System.OsString.Windows as WindowsS hiding (map) import Control.Exception import Data.ByteString ( ByteString ) diff --git a/tests/bytestring-tests/Main.hs b/tests/bytestring-tests/Main.hs index a37e79a9..ae8015a7 100644 --- a/tests/bytestring-tests/Main.hs +++ b/tests/bytestring-tests/Main.hs @@ -2,9 +2,12 @@ module Main (main) where +import qualified Properties.OsString as PropOs +import qualified Properties.PosixString as PropPos +import qualified Properties.WindowsString as PropWin import qualified Properties.ShortByteString as PropSBS import qualified Properties.ShortByteString.Word16 as PropSBSW16 import TestUtil main :: IO () -main = runTests (PropSBS.tests ++ PropSBSW16.tests) +main = runTests (PropSBS.tests ++ PropSBSW16.tests ++ PropWin.tests ++ PropPos.tests ++ PropOs.tests) diff --git a/tests/bytestring-tests/Properties/Common.hs b/tests/bytestring-tests/Properties/Common.hs index c5ef566a..77554be3 100644 --- a/tests/bytestring-tests/Properties/Common.hs +++ b/tests/bytestring-tests/Properties/Common.hs @@ -5,6 +5,10 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -Wno-orphans #-} -- We are happy to sacrifice optimizations in exchange for faster compilation, @@ -15,20 +19,46 @@ -fmax-simplifier-iterations=1 -fsimplifier-phases=0 -fno-call-arity -fno-case-merge -fno-cmm-elim-common-blocks -fno-cmm-sink -fno-cpr-anal -fno-cse -fno-do-eta-reduction -fno-float-in -fno-full-laziness - -fno-loopification -fno-specialise -fno-strictness #-} + -fno-loopification -fno-specialise -fno-strictness -Wno-unused-imports -Wno-unused-top-binds #-} + +#ifdef OSWORD +module Properties.OsString (tests) where +import System.OsString.Internal.Types (OsString(..), OsChar(..), getOsChar) +import qualified System.OsString as B +import qualified System.OsString as BS +import qualified System.OsPath.Data.ByteString.Short.Internal as BSI (_nul, isSpace) + +#else #ifdef WORD16 +#ifdef WIN +module Properties.WindowsString (tests) where +import qualified System.OsString.Windows as B +import qualified System.OsString.Windows as BS +#else module Properties.ShortByteString.Word16 (tests) where import System.OsPath.Data.ByteString.Short.Internal (_nul, isSpace) import qualified System.OsPath.Data.ByteString.Short.Word16 as B import qualified System.OsPath.Data.ByteString.Short as BS +#endif +#else +#ifdef POSIX +module Properties.PosixString (tests) where +import qualified System.OsString.Posix as B +import qualified System.OsString.Posix as BS #else module Properties.ShortByteString (tests) where import qualified System.OsPath.Data.ByteString.Short as B -import qualified Data.Char as C #endif +#endif +#endif + import Data.ByteString.Short (ShortByteString) +import qualified Data.Char as C +import qualified System.OsPath.Data.ByteString.Short.Word16 as B16 +import qualified System.OsPath.Data.ByteString.Short as B8 + import Data.Word import Control.Arrow @@ -40,7 +70,157 @@ import Test.QuickCheck import Test.QuickCheck.Monadic ( monadicIO, run ) import Text.Show.Functions () +import System.OsString.Internal.Types (WindowsString(..), WindowsChar(..), getWindowsChar, PosixChar(..), PosixString(..), getPosixChar, OsString(..), OsChar(..), getOsChar) +import qualified System.OsString.Posix as PBS +import qualified System.OsString.Windows as WBS +import qualified System.OsString as OBS +import qualified System.OsPath.Data.ByteString.Short.Internal as BSI (_nul, isSpace) + + +instance Arbitrary PosixString where + arbitrary = do + bs <- sized sizedByteString' + n <- choose (0, 2) + return (PBS.drop n bs) -- to give us some with non-0 offset + where + sizedByteString' :: Int -> Gen PosixString + sizedByteString' n = do m <- choose(0, n) + fmap (PosixString . B8.pack) $ vectorOf m arbitrary + +instance Arbitrary PosixChar where + arbitrary = fmap PosixChar (arbitrary @Word8) + +instance CoArbitrary PosixChar where + coarbitrary s = coarbitrary (PBS.toChar s) + +instance CoArbitrary PosixString where + coarbitrary s = coarbitrary (PBS.unpack s) + +deriving instance Num PosixChar + +deriving instance Bounded PosixChar + +instance Arbitrary WindowsString where + arbitrary = do + bs <- sized sizedByteString' + n <- choose (0, 2) + return (WBS.drop n bs) -- to give us some with non-0 offset + where + sizedByteString' :: Int -> Gen WindowsString + sizedByteString' n = do m <- choose(0, n) + fmap (WindowsString . B16.pack) $ vectorOf m arbitrary + +instance Arbitrary WindowsChar where + arbitrary = fmap WindowsChar (arbitrary @Word16) + +instance CoArbitrary WindowsChar where + coarbitrary s = coarbitrary (WBS.toChar s) + +instance CoArbitrary WindowsString where + coarbitrary s = coarbitrary (WBS.unpack s) + +deriving instance Num WindowsChar + +deriving instance Bounded WindowsChar + +isSpaceWin :: WindowsChar -> Bool +isSpaceWin = BSI.isSpace . getWindowsChar + +numWordWin :: WindowsString -> Int +numWordWin = B16.numWord16 . getWindowsString + + +swapWWin :: WindowsChar -> WindowsChar +swapWWin = WindowsChar . byteSwap16 . getWindowsChar + +isSpacePosix :: PosixChar -> Bool +isSpacePosix = C.isSpace . word8ToChar . getPosixChar + +numWordPosix :: PosixString -> Int +numWordPosix = B8.length . getPosixString + + +swapWPosix :: PosixChar -> PosixChar +swapWPosix = id + +#ifdef OSWORD +isSpace :: OsChar -> Bool +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) +isSpace = isSpaceWin . getOsChar +#else +isSpace = isSpacePosix . getOsChar +#endif + +numWord :: OsString -> Int +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) +numWord = numWordWin . getOsString +#else +numWord = numWordPosix . getOsString +#endif + +toElem :: OsChar -> OsChar +toElem = id + +swapW :: OsChar -> OsChar +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) +swapW = OsChar . swapWWin . getOsChar +#else +swapW = OsChar . swapWPosix . getOsChar +#endif + +instance Arbitrary OsString where + arbitrary = OsString <$> arbitrary + +instance Arbitrary OsChar where + arbitrary = OsChar <$> arbitrary + +instance CoArbitrary OsChar where + coarbitrary s = coarbitrary (OBS.toChar s) + +instance CoArbitrary OsString where + coarbitrary s = coarbitrary (OBS.unpack s) + +deriving instance Num OsChar +deriving instance Bounded OsChar + +instance Arbitrary ShortByteString where +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) + arbitrary = getWindowsString <$> arbitrary +#else + arbitrary = getPosixString <$> arbitrary +#endif + +#else + #ifdef WORD16 + +instance Arbitrary ShortByteString where + arbitrary = do + bs <- sized sizedByteString + n <- choose (0, 2) + return (B16.drop n bs) -- to give us some with non-0 offset + where + sizedByteString :: Int -> Gen ShortByteString + sizedByteString n = do m <- choose(0, n) + fmap B16.pack $ vectorOf m arbitrary + +instance CoArbitrary ShortByteString where + coarbitrary s = coarbitrary (B16.unpack s) +#ifdef WIN + +isSpace :: WindowsChar -> Bool +isSpace = isSpaceWin + +numWord :: WindowsString -> Int +numWord = numWordWin + +toElem :: WindowsChar -> WindowsChar +toElem = id + +swapW :: WindowsChar -> WindowsChar +swapW = swapWWin + +#else numWord :: ShortByteString -> Int numWord = B.numWord16 @@ -50,18 +230,22 @@ toElem = id swapW :: Word16 -> Word16 swapW = byteSwap16 -sizedByteString :: Int -> Gen ShortByteString -sizedByteString n = do m <- choose(0, n) - fmap B.pack $ vectorOf m arbitrary -instance Arbitrary ShortByteString where - arbitrary = do - bs <- sized sizedByteString - n <- choose (0, 2) - return (B.drop n bs) -- to give us some with non-0 offset +#endif +#else +#ifdef POSIX -instance CoArbitrary ShortByteString where - coarbitrary s = coarbitrary (B.unpack s) +isSpace :: PosixChar -> Bool +isSpace = isSpacePosix + +numWord :: PosixString -> Int +numWord = numWordPosix + +toElem :: PosixChar -> PosixChar +toElem = id + +swapW :: PosixChar -> PosixChar +swapW = swapWPosix #else _nul :: Word8 @@ -70,12 +254,9 @@ _nul = 0x00 isSpace :: Word8 -> Bool isSpace = C.isSpace . word8ToChar --- | Total conversion to char. -word8ToChar :: Word8 -> Char -word8ToChar = C.chr . fromIntegral numWord :: ShortByteString -> Int -numWord = B.length +numWord = B8.length toElem :: Word8 -> Word8 toElem = id @@ -84,20 +265,23 @@ swapW :: Word8 -> Word8 swapW = id -sizedByteString :: Int -> Gen ShortByteString -sizedByteString n = do m <- choose(0, n) - fmap B.pack $ vectorOf m arbitrary + +#endif instance Arbitrary ShortByteString where arbitrary = do - bs <- sized sizedByteString + bs <- sized sizedByteString' n <- choose (0, 2) - return (B.drop n bs) -- to give us some with non-0 offset - shrink = map B.pack . shrink . B.unpack + return (B8.drop n bs) -- to give us some with non-0 offset + where + sizedByteString' :: Int -> Gen ShortByteString + sizedByteString' n = do m <- choose(0, n) + fmap B8.pack $ vectorOf m arbitrary + shrink = map B8.pack . shrink . B8.unpack instance CoArbitrary ShortByteString where - coarbitrary s = coarbitrary (B.unpack s) - + coarbitrary s = coarbitrary (B8.unpack s) +#endif #endif @@ -132,7 +316,7 @@ tests = , ("compare LT empty", property $ \x -> not (B.null x) ==> compare B.empty x == LT) , ("compare GT concat", - property $ \x y -> not (B.null y) ==> compare (x <> y) x == GT) + property $ \x y -> not (B.null y) ==> compare (x `mappend` y) x == GT) , ("compare char" , property $ \(toElem -> c) (toElem -> d) -> compare (swapW c) (swapW d) == compare (B.singleton c) (B.singleton d)) , ("compare unsigned", @@ -150,6 +334,16 @@ tests = once $ B.unpack mempty === []) #ifdef WORD16 +#ifdef WIN + , ("isInfixOf works correctly under UTF16", + once $ + let foo = WindowsString $ B8.pack [0xbb, 0x03] + foo' = WindowsString $ B8.pack [0xd2, 0xbb] + bar = WindowsString $ B8.pack [0xd2, 0xbb, 0x03, 0xad] + bar' = WindowsString $ B8.pack [0xd2, 0xbb, 0x03, 0xad, 0xd2, 0xbb, 0x03, 0xad, 0xbb, 0x03, 0x00, 0x00] + in [B.isInfixOf foo bar, B.isInfixOf foo' bar, B.isInfixOf foo bar'] === [False, True, True] + ) +#else , ("isInfixOf works correctly under UTF16", once $ let foo = BS.pack [0xbb, 0x03] @@ -158,6 +352,7 @@ tests = bar' = BS.pack [0xd2, 0xbb, 0x03, 0xad, 0xd2, 0xbb, 0x03, 0xad, 0xbb, 0x03, 0x00, 0x00] in [B.isInfixOf foo bar, B.isInfixOf foo' bar, B.isInfixOf foo bar'] === [False, True, True] ) +#endif #endif , ("break breakSubstring", property $ \(toElem -> c) x -> B.break (== c) x === B.breakSubstring (B.singleton c) x @@ -193,7 +388,7 @@ tests = , ("mappend" , property $ \x y -> B.unpack (mappend x y) === B.unpack x `mappend` B.unpack y) , ("<>" , - property $ \x y -> B.unpack (x <> y) === B.unpack x <> B.unpack y) + property $ \x y -> B.unpack (x `mappend` y) === B.unpack x `mappend` B.unpack y) , ("stimes" , property $ \(Positive n) x -> stimes (n :: Int) (x :: ShortByteString) === mtimesDefault n x) @@ -407,14 +602,15 @@ tests = -- property $ \n f (toElem -> a) -> B.unpack (B.take (fromIntegral n) (B.unfoldr (fmap (first toElem) . f) a)) === -- take n (unfoldr (fmap (first toElem) . f) a)) -- -#ifdef WORD16 +#if defined(WORD16) && !defined(WIN) && !defined(OSWORD) && !defined(POSIX) , ("useAsCWString str packCWString == str" , property $ \x -> not (B.any (== _nul) x) ==> monadicIO $ run (B.useAsCWString x B.packCWString >>= \x' -> pure (x == x'))) , ("useAsCWStringLen str packCWStringLen == str" , property $ \x -> not (B.any (== _nul) x) ==> monadicIO $ run (B.useAsCWStringLen x B.packCWStringLen >>= \x' -> pure (x == x'))) -#else +#endif +#if !defined(WORD16) && !defined(WIN) && !defined(OSWORD) && !defined(POSIX) , ("useAsCString str packCString == str" , property $ \x -> not (B.any (== _nul) x) ==> monadicIO $ run (B.useAsCString x B.packCString >>= \x' -> pure (x == x'))) @@ -439,3 +635,7 @@ splitWith f ys = go [] ys unsnoc :: [a] -> Maybe ([a], a) unsnoc [] = Nothing unsnoc xs = Just (init xs, last xs) + +-- | Total conversion to char. +word8ToChar :: Word8 -> Char +word8ToChar = C.chr . fromIntegral diff --git a/tests/bytestring-tests/Properties/OsString.hs b/tests/bytestring-tests/Properties/OsString.hs new file mode 100644 index 00000000..e81348b7 --- /dev/null +++ b/tests/bytestring-tests/Properties/OsString.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE CPP #-} +#undef WORD16 +#undef POSIX +#undef WIN +#define OSWORD +#include "Common.hs" + diff --git a/tests/bytestring-tests/Properties/PosixString.hs b/tests/bytestring-tests/Properties/PosixString.hs new file mode 100644 index 00000000..e0b9d981 --- /dev/null +++ b/tests/bytestring-tests/Properties/PosixString.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE CPP #-} +#undef WORD16 +#define POSIX +#undef WIN +#undef OSWORD +#include "Common.hs" + diff --git a/tests/bytestring-tests/Properties/ShortByteString.hs b/tests/bytestring-tests/Properties/ShortByteString.hs index 3040dfb8..97c91090 100644 --- a/tests/bytestring-tests/Properties/ShortByteString.hs +++ b/tests/bytestring-tests/Properties/ShortByteString.hs @@ -1,3 +1,7 @@ {-# LANGUAGE CPP #-} #undef WORD16 +#undef WIN +#undef POSIX +#undef OSWORD #include "Common.hs" + diff --git a/tests/bytestring-tests/Properties/ShortByteString/Word16.hs b/tests/bytestring-tests/Properties/ShortByteString/Word16.hs index aa426397..d604ef97 100644 --- a/tests/bytestring-tests/Properties/ShortByteString/Word16.hs +++ b/tests/bytestring-tests/Properties/ShortByteString/Word16.hs @@ -1,3 +1,6 @@ {-# LANGUAGE CPP #-} #define WORD16 +#undef WIN +#undef POSIX +#undef OSWORD #include "../Common.hs" diff --git a/tests/bytestring-tests/Properties/WindowsString.hs b/tests/bytestring-tests/Properties/WindowsString.hs new file mode 100644 index 00000000..1ce96b04 --- /dev/null +++ b/tests/bytestring-tests/Properties/WindowsString.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE CPP #-} +#define WORD16 +#define WIN +#undef POSIX +#undef OSWORD +#include "Common.hs" +