packages feed

filepath 1.4.2.2 → 1.4.100.0

raw patch · 47 files changed

+10883/−2889 lines, 47 filesdep +bytestringdep +checkersdep +deepseqdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, checkers, deepseq, exceptions, template-haskell

Dependency ranges changed: base

API changes (from Hackage documentation)

+ System.OsPath: (-<.>) :: OsPath -> OsString -> OsPath
+ System.OsPath: (<.>) :: OsPath -> OsString -> OsPath
+ System.OsPath: (</>) :: OsPath -> OsPath -> OsPath
+ System.OsPath: addExtension :: OsPath -> OsString -> OsPath
+ System.OsPath: addTrailingPathSeparator :: OsPath -> OsPath
+ System.OsPath: combine :: OsPath -> OsPath -> OsPath
+ System.OsPath: data OsChar
+ System.OsPath: data OsString
+ System.OsPath: decodeFS :: OsPath -> IO FilePath
+ System.OsPath: decodeUtf :: MonadThrow m => OsPath -> m FilePath
+ System.OsPath: decodeWith :: TextEncoding -> TextEncoding -> OsPath -> Either EncodingException FilePath
+ System.OsPath: dropDrive :: OsPath -> OsPath
+ System.OsPath: dropExtension :: OsPath -> OsPath
+ System.OsPath: dropExtensions :: OsPath -> OsPath
+ System.OsPath: dropFileName :: OsPath -> OsPath
+ System.OsPath: dropTrailingPathSeparator :: OsPath -> OsPath
+ System.OsPath: encodeFS :: FilePath -> IO OsPath
+ System.OsPath: encodeUtf :: MonadThrow m => FilePath -> m OsPath
+ System.OsPath: encodeWith :: TextEncoding -> TextEncoding -> FilePath -> Either EncodingException OsPath
+ System.OsPath: equalFilePath :: OsPath -> OsPath -> Bool
+ System.OsPath: extSeparator :: OsChar
+ System.OsPath: hasDrive :: OsPath -> Bool
+ System.OsPath: hasExtension :: OsPath -> Bool
+ System.OsPath: hasTrailingPathSeparator :: OsPath -> Bool
+ System.OsPath: isAbsolute :: OsPath -> Bool
+ System.OsPath: isDrive :: OsPath -> Bool
+ System.OsPath: isExtSeparator :: OsChar -> Bool
+ System.OsPath: isExtensionOf :: OsString -> OsPath -> Bool
+ System.OsPath: isPathSeparator :: OsChar -> Bool
+ System.OsPath: isRelative :: OsPath -> Bool
+ System.OsPath: isSearchPathSeparator :: OsChar -> Bool
+ System.OsPath: isValid :: OsPath -> Bool
+ System.OsPath: joinDrive :: OsPath -> OsPath -> OsPath
+ System.OsPath: joinPath :: [OsPath] -> OsPath
+ System.OsPath: makeRelative :: OsPath -> OsPath -> OsPath
+ System.OsPath: makeValid :: OsPath -> OsPath
+ System.OsPath: normalise :: OsPath -> OsPath
+ System.OsPath: osp :: QuasiQuoter
+ System.OsPath: pack :: [OsChar] -> OsPath
+ System.OsPath: pathSeparator :: OsChar
+ System.OsPath: pathSeparators :: [OsChar]
+ System.OsPath: replaceBaseName :: OsPath -> OsString -> OsPath
+ System.OsPath: replaceDirectory :: OsPath -> OsPath -> OsPath
+ System.OsPath: replaceExtension :: OsPath -> OsString -> OsPath
+ System.OsPath: replaceExtensions :: OsPath -> OsString -> OsPath
+ System.OsPath: replaceFileName :: OsPath -> OsString -> OsPath
+ System.OsPath: searchPathSeparator :: OsChar
+ System.OsPath: splitDirectories :: OsPath -> [OsPath]
+ System.OsPath: splitDrive :: OsPath -> (OsPath, OsPath)
+ System.OsPath: splitExtension :: OsPath -> (OsPath, OsString)
+ System.OsPath: splitExtensions :: OsPath -> (OsPath, OsString)
+ System.OsPath: splitFileName :: OsPath -> (OsPath, OsPath)
+ System.OsPath: splitPath :: OsPath -> [OsPath]
+ System.OsPath: splitSearchPath :: OsString -> [OsPath]
+ System.OsPath: stripExtension :: OsString -> OsPath -> Maybe OsPath
+ System.OsPath: takeBaseName :: OsPath -> OsPath
+ System.OsPath: takeDirectory :: OsPath -> OsPath
+ System.OsPath: takeDrive :: OsPath -> OsPath
+ System.OsPath: takeExtension :: OsPath -> OsString
+ System.OsPath: takeExtensions :: OsPath -> OsString
+ System.OsPath: takeFileName :: OsPath -> OsPath
+ System.OsPath: toChar :: OsChar -> Char
+ System.OsPath: type OsPath = OsString
+ System.OsPath: unpack :: OsPath -> [OsChar]
+ System.OsPath: unsafeFromChar :: Char -> OsChar
+ System.OsPath.Data.ByteString.Short: (!?) :: ShortByteString -> Int -> Maybe Word8
+ System.OsPath.Data.ByteString.Short: SBS :: ByteArray# -> ShortByteString
+ System.OsPath.Data.ByteString.Short: all :: (Word8 -> Bool) -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: any :: (Word8 -> Bool) -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: append :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: break :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: breakEnd :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: breakSubstring :: ShortByteString -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: concat :: [ShortByteString] -> ShortByteString
+ System.OsPath.Data.ByteString.Short: cons :: Word8 -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: count :: Word8 -> ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short: data ShortByteString
+ System.OsPath.Data.ByteString.Short: drop :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: dropEnd :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: dropWhile :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: dropWhileEnd :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: elem :: Word8 -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: elemIndex :: Word8 -> ShortByteString -> Maybe Int
+ System.OsPath.Data.ByteString.Short: elemIndices :: Word8 -> ShortByteString -> [Int]
+ System.OsPath.Data.ByteString.Short: empty :: ShortByteString
+ System.OsPath.Data.ByteString.Short: filter :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: find :: (Word8 -> Bool) -> ShortByteString -> Maybe Word8
+ System.OsPath.Data.ByteString.Short: findIndex :: (Word8 -> Bool) -> ShortByteString -> Maybe Int
+ System.OsPath.Data.ByteString.Short: findIndices :: (Word8 -> Bool) -> ShortByteString -> [Int]
+ System.OsPath.Data.ByteString.Short: foldl :: (a -> Word8 -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short: foldl' :: (a -> Word8 -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short: foldl1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: foldl1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: foldr :: (Word8 -> a -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short: foldr' :: (Word8 -> a -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short: foldr1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: foldr1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: fromShort :: ShortByteString -> ByteString
+ System.OsPath.Data.ByteString.Short: head :: HasCallStack => ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: index :: HasCallStack => ShortByteString -> Int -> Word8
+ System.OsPath.Data.ByteString.Short: indexMaybe :: ShortByteString -> Int -> Maybe Word8
+ System.OsPath.Data.ByteString.Short: infixl 5 `snoc`
+ System.OsPath.Data.ByteString.Short: infixr 5 `cons`
+ System.OsPath.Data.ByteString.Short: init :: HasCallStack => ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: intercalate :: ShortByteString -> [ShortByteString] -> ShortByteString
+ System.OsPath.Data.ByteString.Short: isInfixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: isPrefixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: isSuffixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: last :: HasCallStack => ShortByteString -> Word8
+ System.OsPath.Data.ByteString.Short: length :: ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short: map :: (Word8 -> Word8) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: null :: ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short: pack :: [Word8] -> ShortByteString
+ System.OsPath.Data.ByteString.Short: packCString :: CString -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short: packCStringLen :: CStringLen -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short: partition :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: replicate :: Int -> Word8 -> ShortByteString
+ System.OsPath.Data.ByteString.Short: reverse :: ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: singleton :: Word8 -> ShortByteString
+ System.OsPath.Data.ByteString.Short: snoc :: ShortByteString -> Word8 -> ShortByteString
+ System.OsPath.Data.ByteString.Short: span :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: spanEnd :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: split :: Word8 -> ShortByteString -> [ShortByteString]
+ System.OsPath.Data.ByteString.Short: splitAt :: Int -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short: splitWith :: (Word8 -> Bool) -> ShortByteString -> [ShortByteString]
+ System.OsPath.Data.ByteString.Short: stripPrefix :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Data.ByteString.Short: stripSuffix :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Data.ByteString.Short: tail :: HasCallStack => ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: take :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: takeEnd :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: takeWhile :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: takeWhileEnd :: (Word8 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: toShort :: ByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short: uncons :: ShortByteString -> Maybe (Word8, ShortByteString)
+ System.OsPath.Data.ByteString.Short: unfoldr :: (a -> Maybe (Word8, a)) -> a -> ShortByteString
+ System.OsPath.Data.ByteString.Short: unfoldrN :: Int -> (a -> Maybe (Word8, a)) -> a -> (ShortByteString, Maybe a)
+ System.OsPath.Data.ByteString.Short: unpack :: ShortByteString -> [Word8]
+ System.OsPath.Data.ByteString.Short: unsnoc :: ShortByteString -> Maybe (ShortByteString, Word8)
+ System.OsPath.Data.ByteString.Short: useAsCString :: ShortByteString -> (CString -> IO a) -> IO a
+ System.OsPath.Data.ByteString.Short: useAsCStringLen :: ShortByteString -> (CStringLen -> IO a) -> IO a
+ System.OsPath.Data.ByteString.Short.Internal: BA# :: ByteArray# -> BA
+ System.OsPath.Data.ByteString.Short.Internal: MBA# :: MutableByteArray# s -> MBA s
+ System.OsPath.Data.ByteString.Short.Internal: _nul :: Word16
+ System.OsPath.Data.ByteString.Short.Internal: asBA :: ShortByteString -> BA
+ System.OsPath.Data.ByteString.Short.Internal: assertEven :: ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: copyAddrToByteArray :: Ptr a -> MBA RealWorld -> Int -> Int -> ST RealWorld ()
+ System.OsPath.Data.ByteString.Short.Internal: copyByteArray :: BA -> Int -> MBA s -> Int -> Int -> ST s ()
+ System.OsPath.Data.ByteString.Short.Internal: copyMutableByteArray :: MBA s -> Int -> MBA s -> Int -> Int -> ST s ()
+ System.OsPath.Data.ByteString.Short.Internal: create :: Int -> (forall s. MBA s -> ST s ()) -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: createAndTrim :: Int -> (forall s. MBA s -> ST s (Int, a)) -> (ShortByteString, a)
+ System.OsPath.Data.ByteString.Short.Internal: createAndTrim' :: Int -> (forall s. MBA s -> ST s Int) -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: createAndTrim'' :: Int -> (forall s. MBA s -> MBA s -> ST s (Int, Int)) -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Internal: data BA
+ System.OsPath.Data.ByteString.Short.Internal: data MBA s
+ System.OsPath.Data.ByteString.Short.Internal: decodeWord16LE# :: (# Word#, Word# #) -> Word#
+ System.OsPath.Data.ByteString.Short.Internal: encodeWord16LE# :: Word# -> (# Word#, Word# #)
+ System.OsPath.Data.ByteString.Short.Internal: errorEmptySBS :: HasCallStack => String -> a
+ System.OsPath.Data.ByteString.Short.Internal: findFromEndUntil :: (Word16 -> Bool) -> ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short.Internal: findIndexOrLength :: (Word16 -> Bool) -> ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short.Internal: indexWord16Array :: BA -> Int -> Word16
+ System.OsPath.Data.ByteString.Short.Internal: isSpace :: Word16 -> Bool
+ System.OsPath.Data.ByteString.Short.Internal: moduleError :: HasCallStack => String -> String -> a
+ System.OsPath.Data.ByteString.Short.Internal: moduleErrorIO :: String -> String -> IO a
+ System.OsPath.Data.ByteString.Short.Internal: moduleErrorMsg :: String -> String -> String
+ System.OsPath.Data.ByteString.Short.Internal: newByteArray :: Int -> ST s (MBA s)
+ System.OsPath.Data.ByteString.Short.Internal: newCWString :: ShortByteString -> IO (Ptr Word16)
+ System.OsPath.Data.ByteString.Short.Internal: newPinnedByteArray :: Int -> ST s (MBA s)
+ System.OsPath.Data.ByteString.Short.Internal: packCWString :: Ptr Word16 -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: packCWStringLen :: (Ptr Word16, Int) -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: packLenWord16 :: Int -> [Word16] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: packLenWord16Rev :: Int -> [Word16] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: packWord16 :: [Word16] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: packWord16Rev :: [Word16] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Internal: setByteArray :: MBA s -> Int -> Int -> Int -> ST s ()
+ System.OsPath.Data.ByteString.Short.Internal: unpackWord16 :: ShortByteString -> [Word16]
+ System.OsPath.Data.ByteString.Short.Internal: unsafeFreezeByteArray :: MBA s -> ST s BA
+ System.OsPath.Data.ByteString.Short.Internal: useAsCWString :: ShortByteString -> (Ptr Word16 -> IO a) -> IO a
+ System.OsPath.Data.ByteString.Short.Internal: useAsCWStringLen :: ShortByteString -> ((Ptr Word16, Int) -> IO a) -> IO a
+ System.OsPath.Data.ByteString.Short.Internal: word16ToChar :: Word16 -> Char
+ System.OsPath.Data.ByteString.Short.Internal: writeWord16Array :: MBA s -> Int -> Word16 -> ST s ()
+ System.OsPath.Data.ByteString.Short.Word16: (!?) :: ShortByteString -> Int -> Maybe Word16
+ System.OsPath.Data.ByteString.Short.Word16: SBS :: ByteArray# -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: all :: (Word16 -> Bool) -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: any :: (Word16 -> Bool) -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: append :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: break :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: breakEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: breakSubstring :: ShortByteString -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: concat :: [ShortByteString] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: cons :: Word16 -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: count :: Word16 -> ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short.Word16: data ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: drop :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: dropEnd :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: dropWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: dropWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: elem :: Word16 -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: elemIndex :: Word16 -> ShortByteString -> Maybe Int
+ System.OsPath.Data.ByteString.Short.Word16: elemIndices :: Word16 -> ShortByteString -> [Int]
+ System.OsPath.Data.ByteString.Short.Word16: empty :: ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: filter :: (Word16 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: find :: (Word16 -> Bool) -> ShortByteString -> Maybe Word16
+ System.OsPath.Data.ByteString.Short.Word16: findIndex :: (Word16 -> Bool) -> ShortByteString -> Maybe Int
+ System.OsPath.Data.ByteString.Short.Word16: findIndices :: (Word16 -> Bool) -> ShortByteString -> [Int]
+ System.OsPath.Data.ByteString.Short.Word16: foldl :: (a -> Word16 -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short.Word16: foldl' :: (a -> Word16 -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short.Word16: foldl1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: foldl1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: foldr :: (Word16 -> a -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short.Word16: foldr' :: (Word16 -> a -> a) -> a -> ShortByteString -> a
+ System.OsPath.Data.ByteString.Short.Word16: foldr1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: foldr1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: fromShort :: ShortByteString -> ByteString
+ System.OsPath.Data.ByteString.Short.Word16: head :: HasCallStack => ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: index :: HasCallStack => ShortByteString -> Int -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: indexMaybe :: ShortByteString -> Int -> Maybe Word16
+ System.OsPath.Data.ByteString.Short.Word16: infixl 5 `snoc`
+ System.OsPath.Data.ByteString.Short.Word16: infixr 5 `cons`
+ System.OsPath.Data.ByteString.Short.Word16: init :: HasCallStack => ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: intercalate :: ShortByteString -> [ShortByteString] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: isInfixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: isPrefixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: isSuffixOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: last :: HasCallStack => ShortByteString -> Word16
+ System.OsPath.Data.ByteString.Short.Word16: length :: ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short.Word16: map :: (Word16 -> Word16) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: newCWString :: ShortByteString -> IO (Ptr Word16)
+ System.OsPath.Data.ByteString.Short.Word16: null :: ShortByteString -> Bool
+ System.OsPath.Data.ByteString.Short.Word16: numWord16 :: ShortByteString -> Int
+ System.OsPath.Data.ByteString.Short.Word16: pack :: [Word16] -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: packCWString :: Ptr Word16 -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: packCWStringLen :: (Ptr Word16, Int) -> IO ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: partition :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: replicate :: Int -> Word16 -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: reverse :: ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: singleton :: Word16 -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: snoc :: ShortByteString -> Word16 -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: span :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: spanEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: split :: Word16 -> ShortByteString -> [ShortByteString]
+ System.OsPath.Data.ByteString.Short.Word16: splitAt :: Int -> ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: splitWith :: (Word16 -> Bool) -> ShortByteString -> [ShortByteString]
+ System.OsPath.Data.ByteString.Short.Word16: stripPrefix :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: stripSuffix :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: tail :: HasCallStack => ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: take :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: takeEnd :: Int -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: takeWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: takeWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: toShort :: ByteString -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: uncons :: ShortByteString -> Maybe (Word16, ShortByteString)
+ System.OsPath.Data.ByteString.Short.Word16: unfoldr :: (a -> Maybe (Word16, a)) -> a -> ShortByteString
+ System.OsPath.Data.ByteString.Short.Word16: unfoldrN :: forall a. Int -> (a -> Maybe (Word16, a)) -> a -> (ShortByteString, Maybe a)
+ System.OsPath.Data.ByteString.Short.Word16: unpack :: ShortByteString -> [Word16]
+ System.OsPath.Data.ByteString.Short.Word16: unsnoc :: ShortByteString -> Maybe (ShortByteString, Word16)
+ System.OsPath.Data.ByteString.Short.Word16: useAsCWString :: ShortByteString -> (Ptr Word16 -> IO a) -> IO a
+ System.OsPath.Data.ByteString.Short.Word16: useAsCWStringLen :: ShortByteString -> ((Ptr Word16, Int) -> IO a) -> IO a
+ System.OsPath.Encoding: EncodingError :: String -> Maybe Word8 -> EncodingException
+ System.OsPath.Encoding: data EncodingException
+ System.OsPath.Encoding: decodeWithBasePosix :: ShortByteString -> IO String
+ System.OsPath.Encoding: decodeWithBaseWindows :: ShortByteString -> IO String
+ System.OsPath.Encoding: encodeWithBasePosix :: String -> IO ShortByteString
+ System.OsPath.Encoding: encodeWithBaseWindows :: String -> IO ShortByteString
+ System.OsPath.Encoding: mkUTF16le_b :: CodingFailureMode -> TextEncoding
+ System.OsPath.Encoding: mkUcs2le :: CodingFailureMode -> TextEncoding
+ System.OsPath.Encoding: showEncodingException :: EncodingException -> String
+ System.OsPath.Encoding: ucs2le :: TextEncoding
+ System.OsPath.Encoding: ucs2le_DF :: CodingFailureMode -> IO (TextDecoder ())
+ System.OsPath.Encoding: ucs2le_EF :: CodingFailureMode -> IO (TextEncoder ())
+ System.OsPath.Encoding: ucs2le_decode :: DecodeBuffer
+ System.OsPath.Encoding: ucs2le_encode :: EncodeBuffer
+ System.OsPath.Encoding: utf16le_b :: TextEncoding
+ System.OsPath.Encoding: utf16le_b_DF :: CodingFailureMode -> IO (TextDecoder ())
+ System.OsPath.Encoding: utf16le_b_EF :: CodingFailureMode -> IO (TextEncoder ())
+ System.OsPath.Encoding: utf16le_b_decode :: DecodeBuffer
+ System.OsPath.Encoding: utf16le_b_encode :: EncodeBuffer
+ System.OsPath.Encoding.Internal: EncodingError :: String -> Maybe Word8 -> EncodingException
+ System.OsPath.Encoding.Internal: cWcharsToChars :: [Word16] -> [Char]
+ System.OsPath.Encoding.Internal: cWcharsToChars_UCS2 :: [Word16] -> [Char]
+ System.OsPath.Encoding.Internal: charsToCWchars :: [Char] -> [Word16]
+ System.OsPath.Encoding.Internal: data EncodingException
+ System.OsPath.Encoding.Internal: decodeWithBasePosix :: ShortByteString -> IO String
+ System.OsPath.Encoding.Internal: decodeWithBaseWindows :: ShortByteString -> IO String
+ System.OsPath.Encoding.Internal: decodeWithTE :: TextEncoding -> ShortByteString -> Either EncodingException String
+ System.OsPath.Encoding.Internal: encodeWithBasePosix :: String -> IO ShortByteString
+ System.OsPath.Encoding.Internal: encodeWithBaseWindows :: String -> IO ShortByteString
+ System.OsPath.Encoding.Internal: encodeWithTE :: TextEncoding -> String -> Either EncodingException ShortByteString
+ System.OsPath.Encoding.Internal: instance Control.DeepSeq.NFData System.OsPath.Encoding.Internal.EncodingException
+ System.OsPath.Encoding.Internal: instance GHC.Classes.Eq System.OsPath.Encoding.Internal.EncodingException
+ System.OsPath.Encoding.Internal: instance GHC.Exception.Type.Exception System.OsPath.Encoding.Internal.EncodingException
+ System.OsPath.Encoding.Internal: instance GHC.Show.Show System.OsPath.Encoding.Internal.EncodingException
+ System.OsPath.Encoding.Internal: mkUTF16le_b :: CodingFailureMode -> TextEncoding
+ System.OsPath.Encoding.Internal: mkUcs2le :: CodingFailureMode -> TextEncoding
+ System.OsPath.Encoding.Internal: peekFilePathPosix :: CStringLen -> IO FilePath
+ System.OsPath.Encoding.Internal: peekFilePathWin :: (Ptr Word16, Int) -> IO FilePath
+ System.OsPath.Encoding.Internal: showEncodingException :: EncodingException -> String
+ System.OsPath.Encoding.Internal: ucs2le :: TextEncoding
+ System.OsPath.Encoding.Internal: ucs2le_DF :: CodingFailureMode -> IO (TextDecoder ())
+ System.OsPath.Encoding.Internal: ucs2le_EF :: CodingFailureMode -> IO (TextEncoder ())
+ System.OsPath.Encoding.Internal: ucs2le_decode :: DecodeBuffer
+ System.OsPath.Encoding.Internal: ucs2le_encode :: EncodeBuffer
+ System.OsPath.Encoding.Internal: utf16le_b :: TextEncoding
+ System.OsPath.Encoding.Internal: utf16le_b_DF :: CodingFailureMode -> IO (TextDecoder ())
+ System.OsPath.Encoding.Internal: utf16le_b_EF :: CodingFailureMode -> IO (TextEncoder ())
+ System.OsPath.Encoding.Internal: utf16le_b_decode :: DecodeBuffer
+ System.OsPath.Encoding.Internal: utf16le_b_encode :: EncodeBuffer
+ System.OsPath.Encoding.Internal: wNUL :: Word16
+ System.OsPath.Encoding.Internal: withFilePathPosix :: FilePath -> (CStringLen -> IO a) -> IO a
+ System.OsPath.Encoding.Internal: withFilePathWin :: FilePath -> (Int -> Ptr Word16 -> IO a) -> IO a
+ System.OsPath.Internal: decodeFS :: OsPath -> IO FilePath
+ System.OsPath.Internal: decodeUtf :: MonadThrow m => OsPath -> m FilePath
+ System.OsPath.Internal: decodeWith :: TextEncoding -> TextEncoding -> OsPath -> Either EncodingException FilePath
+ System.OsPath.Internal: encodeFS :: FilePath -> IO OsPath
+ System.OsPath.Internal: encodeUtf :: MonadThrow m => FilePath -> m OsPath
+ System.OsPath.Internal: encodeWith :: TextEncoding -> TextEncoding -> FilePath -> Either EncodingException OsPath
+ System.OsPath.Internal: fromBytes :: MonadThrow m => ByteString -> m OsPath
+ System.OsPath.Internal: osp :: QuasiQuoter
+ System.OsPath.Internal: pack :: [OsChar] -> OsPath
+ System.OsPath.Internal: unpack :: OsPath -> [OsChar]
+ System.OsPath.Posix: (-<.>) :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: (<.>) :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: (</>) :: PosixPath -> PosixPath -> PosixPath
+ System.OsPath.Posix: addExtension :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: addTrailingPathSeparator :: PosixPath -> PosixPath
+ System.OsPath.Posix: combine :: PosixPath -> PosixPath -> PosixPath
+ System.OsPath.Posix: data PosixChar
+ System.OsPath.Posix: data PosixString
+ System.OsPath.Posix: decodeFS :: PosixString -> IO String
+ System.OsPath.Posix: decodeUtf :: MonadThrow m => PosixString -> m String
+ System.OsPath.Posix: decodeWith :: TextEncoding -> PosixString -> Either EncodingException String
+ System.OsPath.Posix: dropDrive :: PosixPath -> PosixPath
+ System.OsPath.Posix: dropExtension :: PosixPath -> PosixPath
+ System.OsPath.Posix: dropExtensions :: PosixPath -> PosixPath
+ System.OsPath.Posix: dropFileName :: PosixPath -> PosixPath
+ System.OsPath.Posix: dropTrailingPathSeparator :: PosixPath -> PosixPath
+ System.OsPath.Posix: encodeFS :: String -> IO PosixString
+ System.OsPath.Posix: encodeUtf :: MonadThrow m => String -> m PosixString
+ System.OsPath.Posix: encodeWith :: TextEncoding -> String -> Either EncodingException PosixString
+ System.OsPath.Posix: equalFilePath :: PosixPath -> PosixPath -> Bool
+ System.OsPath.Posix: extSeparator :: PosixChar
+ System.OsPath.Posix: hasDrive :: PosixPath -> Bool
+ System.OsPath.Posix: hasExtension :: PosixPath -> Bool
+ System.OsPath.Posix: hasTrailingPathSeparator :: PosixPath -> Bool
+ System.OsPath.Posix: isAbsolute :: PosixPath -> Bool
+ System.OsPath.Posix: isDrive :: PosixPath -> Bool
+ System.OsPath.Posix: isExtSeparator :: PosixChar -> Bool
+ System.OsPath.Posix: isExtensionOf :: PosixString -> PosixPath -> Bool
+ System.OsPath.Posix: isPathSeparator :: PosixChar -> Bool
+ System.OsPath.Posix: isRelative :: PosixPath -> Bool
+ System.OsPath.Posix: isSearchPathSeparator :: PosixChar -> Bool
+ System.OsPath.Posix: isValid :: PosixPath -> Bool
+ System.OsPath.Posix: joinDrive :: PosixPath -> PosixPath -> PosixPath
+ System.OsPath.Posix: joinPath :: [PosixPath] -> PosixPath
+ System.OsPath.Posix: makeRelative :: PosixPath -> PosixPath -> PosixPath
+ System.OsPath.Posix: makeValid :: PosixPath -> PosixPath
+ System.OsPath.Posix: normalise :: PosixPath -> PosixPath
+ System.OsPath.Posix: pack :: [PosixChar] -> PosixString
+ System.OsPath.Posix: pathSeparator :: PosixChar
+ System.OsPath.Posix: pathSeparators :: [PosixChar]
+ System.OsPath.Posix: pstr :: QuasiQuoter
+ System.OsPath.Posix: replaceBaseName :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: replaceDirectory :: PosixPath -> PosixPath -> PosixPath
+ System.OsPath.Posix: replaceExtension :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: replaceExtensions :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: replaceFileName :: PosixPath -> PosixString -> PosixPath
+ System.OsPath.Posix: searchPathSeparator :: PosixChar
+ System.OsPath.Posix: splitDirectories :: PosixPath -> [PosixPath]
+ System.OsPath.Posix: splitDrive :: PosixPath -> (PosixPath, PosixPath)
+ System.OsPath.Posix: splitExtension :: PosixPath -> (PosixPath, PosixString)
+ System.OsPath.Posix: splitExtensions :: PosixPath -> (PosixPath, PosixString)
+ System.OsPath.Posix: splitFileName :: PosixPath -> (PosixPath, PosixPath)
+ System.OsPath.Posix: splitPath :: PosixPath -> [PosixPath]
+ System.OsPath.Posix: splitSearchPath :: PosixString -> [PosixPath]
+ System.OsPath.Posix: stripExtension :: PosixString -> PosixPath -> Maybe PosixPath
+ System.OsPath.Posix: takeBaseName :: PosixPath -> PosixPath
+ System.OsPath.Posix: takeDirectory :: PosixPath -> PosixPath
+ System.OsPath.Posix: takeDrive :: PosixPath -> PosixPath
+ System.OsPath.Posix: takeExtension :: PosixPath -> PosixString
+ System.OsPath.Posix: takeExtensions :: PosixPath -> PosixString
+ System.OsPath.Posix: takeFileName :: PosixPath -> PosixPath
+ System.OsPath.Posix: toChar :: PosixChar -> Char
+ System.OsPath.Posix: type PosixPath = PosixString
+ System.OsPath.Posix: unpack :: PosixString -> [PosixChar]
+ System.OsPath.Posix: unsafeFromChar :: Char -> PosixChar
+ System.OsPath.Posix.Internal: (-<.>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: (<.>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: (</>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: addExtension :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: addTrailingPathSeparator :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: combine :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: dropDrive :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: dropExtension :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: dropExtensions :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: dropFileName :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: dropTrailingPathSeparator :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: equalFilePath :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Posix.Internal: extSeparator :: Word8
+ System.OsPath.Posix.Internal: hasDrive :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: hasExtension :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: hasTrailingPathSeparator :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: infixr 5 </>
+ System.OsPath.Posix.Internal: infixr 7 <.>
+ System.OsPath.Posix.Internal: isAbsolute :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: isDrive :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: isExtSeparator :: Word8 -> Bool
+ System.OsPath.Posix.Internal: isExtensionOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Posix.Internal: isPathSeparator :: Word8 -> Bool
+ System.OsPath.Posix.Internal: isRelative :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: isSearchPathSeparator :: Word8 -> Bool
+ System.OsPath.Posix.Internal: isValid :: ShortByteString -> Bool
+ System.OsPath.Posix.Internal: joinDrive :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: joinPath :: [ShortByteString] -> ShortByteString
+ System.OsPath.Posix.Internal: makeRelative :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: makeValid :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: normalise :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: pathSeparator :: Word8
+ System.OsPath.Posix.Internal: pathSeparators :: [Word8]
+ System.OsPath.Posix.Internal: replaceBaseName :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: replaceDirectory :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: replaceExtension :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: replaceExtensions :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: replaceFileName :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: searchPathSeparator :: Word8
+ System.OsPath.Posix.Internal: splitDirectories :: ShortByteString -> [ShortByteString]
+ System.OsPath.Posix.Internal: splitDrive :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Posix.Internal: splitExtension :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Posix.Internal: splitExtensions :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Posix.Internal: splitFileName :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Posix.Internal: splitPath :: ShortByteString -> [ShortByteString]
+ System.OsPath.Posix.Internal: splitSearchPath :: ShortByteString -> [ShortByteString]
+ System.OsPath.Posix.Internal: stripExtension :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Posix.Internal: takeBaseName :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: takeDirectory :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: takeDrive :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: takeExtension :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: takeExtensions :: ShortByteString -> ShortByteString
+ System.OsPath.Posix.Internal: takeFileName :: ShortByteString -> ShortByteString
+ System.OsPath.Types: data OsChar
+ System.OsPath.Types: data OsString
+ System.OsPath.Types: data PosixChar
+ System.OsPath.Types: data PosixString
+ System.OsPath.Types: data WindowsChar
+ System.OsPath.Types: data WindowsString
+ System.OsPath.Types: type OsPath = OsString
+ System.OsPath.Types: type PlatformPath = PosixPath
+ System.OsPath.Types: type PosixPath = PosixString
+ System.OsPath.Types: type WindowsPath = WindowsString
+ System.OsPath.Windows: (-<.>) :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: (<.>) :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: (</>) :: WindowsPath -> WindowsPath -> WindowsPath
+ System.OsPath.Windows: addExtension :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: addTrailingPathSeparator :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: combine :: WindowsPath -> WindowsPath -> WindowsPath
+ System.OsPath.Windows: data WindowsChar
+ System.OsPath.Windows: data WindowsString
+ System.OsPath.Windows: decodeFS :: WindowsString -> IO String
+ System.OsPath.Windows: decodeUtf :: MonadThrow m => WindowsString -> m String
+ System.OsPath.Windows: decodeWith :: TextEncoding -> WindowsString -> Either EncodingException String
+ System.OsPath.Windows: dropDrive :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: dropExtension :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: dropExtensions :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: dropFileName :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: dropTrailingPathSeparator :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: encodeFS :: String -> IO WindowsString
+ System.OsPath.Windows: encodeUtf :: MonadThrow m => String -> m WindowsString
+ System.OsPath.Windows: encodeWith :: TextEncoding -> String -> Either EncodingException WindowsString
+ System.OsPath.Windows: equalFilePath :: WindowsPath -> WindowsPath -> Bool
+ System.OsPath.Windows: extSeparator :: WindowsChar
+ System.OsPath.Windows: hasDrive :: WindowsPath -> Bool
+ System.OsPath.Windows: hasExtension :: WindowsPath -> Bool
+ System.OsPath.Windows: hasTrailingPathSeparator :: WindowsPath -> Bool
+ System.OsPath.Windows: isAbsolute :: WindowsPath -> Bool
+ System.OsPath.Windows: isDrive :: WindowsPath -> Bool
+ System.OsPath.Windows: isExtSeparator :: WindowsChar -> Bool
+ System.OsPath.Windows: isExtensionOf :: WindowsString -> WindowsPath -> Bool
+ System.OsPath.Windows: isPathSeparator :: WindowsChar -> Bool
+ System.OsPath.Windows: isRelative :: WindowsPath -> Bool
+ System.OsPath.Windows: isSearchPathSeparator :: WindowsChar -> Bool
+ System.OsPath.Windows: isValid :: WindowsPath -> Bool
+ System.OsPath.Windows: joinDrive :: WindowsPath -> WindowsPath -> WindowsPath
+ System.OsPath.Windows: joinPath :: [WindowsPath] -> WindowsPath
+ System.OsPath.Windows: makeRelative :: WindowsPath -> WindowsPath -> WindowsPath
+ System.OsPath.Windows: makeValid :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: normalise :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: pack :: [WindowsChar] -> WindowsString
+ System.OsPath.Windows: pathSeparator :: WindowsChar
+ System.OsPath.Windows: pathSeparators :: [WindowsChar]
+ System.OsPath.Windows: pstr :: QuasiQuoter
+ System.OsPath.Windows: replaceBaseName :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: replaceDirectory :: WindowsPath -> WindowsPath -> WindowsPath
+ System.OsPath.Windows: replaceExtension :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: replaceExtensions :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: replaceFileName :: WindowsPath -> WindowsString -> WindowsPath
+ System.OsPath.Windows: searchPathSeparator :: WindowsChar
+ System.OsPath.Windows: splitDirectories :: WindowsPath -> [WindowsPath]
+ System.OsPath.Windows: splitDrive :: WindowsPath -> (WindowsPath, WindowsPath)
+ System.OsPath.Windows: splitExtension :: WindowsPath -> (WindowsPath, WindowsString)
+ System.OsPath.Windows: splitExtensions :: WindowsPath -> (WindowsPath, WindowsString)
+ System.OsPath.Windows: splitFileName :: WindowsPath -> (WindowsPath, WindowsPath)
+ System.OsPath.Windows: splitPath :: WindowsPath -> [WindowsPath]
+ System.OsPath.Windows: splitSearchPath :: WindowsString -> [WindowsPath]
+ System.OsPath.Windows: stripExtension :: WindowsString -> WindowsPath -> Maybe WindowsPath
+ System.OsPath.Windows: takeBaseName :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: takeDirectory :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: takeDrive :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: takeExtension :: WindowsPath -> WindowsString
+ System.OsPath.Windows: takeExtensions :: WindowsPath -> WindowsString
+ System.OsPath.Windows: takeFileName :: WindowsPath -> WindowsPath
+ System.OsPath.Windows: toChar :: WindowsChar -> Char
+ System.OsPath.Windows: type WindowsPath = WindowsString
+ System.OsPath.Windows: unpack :: WindowsString -> [WindowsChar]
+ System.OsPath.Windows: unsafeFromChar :: Char -> WindowsChar
+ System.OsPath.Windows.Internal: (-<.>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: (<.>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: (</>) :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: addExtension :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: addTrailingPathSeparator :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: combine :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: dropDrive :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: dropExtension :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: dropExtensions :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: dropFileName :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: dropTrailingPathSeparator :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: equalFilePath :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Windows.Internal: extSeparator :: Word16
+ System.OsPath.Windows.Internal: hasDrive :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: hasExtension :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: hasTrailingPathSeparator :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: infixr 5 </>
+ System.OsPath.Windows.Internal: infixr 7 <.>
+ System.OsPath.Windows.Internal: isAbsolute :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: isDrive :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: isExtSeparator :: Word16 -> Bool
+ System.OsPath.Windows.Internal: isExtensionOf :: ShortByteString -> ShortByteString -> Bool
+ System.OsPath.Windows.Internal: isPathSeparator :: Word16 -> Bool
+ System.OsPath.Windows.Internal: isRelative :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: isSearchPathSeparator :: Word16 -> Bool
+ System.OsPath.Windows.Internal: isValid :: ShortByteString -> Bool
+ System.OsPath.Windows.Internal: joinDrive :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: joinPath :: [ShortByteString] -> ShortByteString
+ System.OsPath.Windows.Internal: makeRelative :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: makeValid :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: normalise :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: pathSeparator :: Word16
+ System.OsPath.Windows.Internal: pathSeparators :: [Word16]
+ System.OsPath.Windows.Internal: replaceBaseName :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: replaceDirectory :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: replaceExtension :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: replaceExtensions :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: replaceFileName :: ShortByteString -> ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: searchPathSeparator :: Word16
+ System.OsPath.Windows.Internal: splitDirectories :: ShortByteString -> [ShortByteString]
+ System.OsPath.Windows.Internal: splitDrive :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Windows.Internal: splitExtension :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Windows.Internal: splitExtensions :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Windows.Internal: splitFileName :: ShortByteString -> (ShortByteString, ShortByteString)
+ System.OsPath.Windows.Internal: splitPath :: ShortByteString -> [ShortByteString]
+ System.OsPath.Windows.Internal: splitSearchPath :: ShortByteString -> [ShortByteString]
+ System.OsPath.Windows.Internal: stripExtension :: ShortByteString -> ShortByteString -> Maybe ShortByteString
+ System.OsPath.Windows.Internal: takeBaseName :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: takeDirectory :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: takeDrive :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: takeExtension :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: takeExtensions :: ShortByteString -> ShortByteString
+ System.OsPath.Windows.Internal: takeFileName :: ShortByteString -> ShortByteString
+ System.OsString: data OsChar
+ System.OsString: data OsString
+ System.OsString: decodeFS :: OsString -> IO String
+ System.OsString: decodeUtf :: MonadThrow m => OsString -> m String
+ System.OsString: decodeWith :: TextEncoding -> TextEncoding -> OsString -> Either EncodingException String
+ System.OsString: encodeFS :: String -> IO OsString
+ System.OsString: encodeUtf :: MonadThrow m => String -> m OsString
+ System.OsString: encodeWith :: TextEncoding -> TextEncoding -> String -> Either EncodingException OsString
+ System.OsString: osstr :: QuasiQuoter
+ System.OsString: pack :: [OsChar] -> OsString
+ System.OsString: toChar :: OsChar -> Char
+ System.OsString: unpack :: OsString -> [OsChar]
+ System.OsString: unsafeFromChar :: Char -> OsChar
+ System.OsString.Internal: decodeFS :: OsString -> IO String
+ System.OsString.Internal: decodeUtf :: MonadThrow m => OsString -> m String
+ System.OsString.Internal: decodeWith :: TextEncoding -> TextEncoding -> OsString -> Either EncodingException String
+ System.OsString.Internal: encodeFS :: String -> IO OsString
+ System.OsString.Internal: encodeUtf :: MonadThrow m => String -> m OsString
+ System.OsString.Internal: encodeWith :: TextEncoding -> TextEncoding -> String -> Either EncodingException OsString
+ System.OsString.Internal: fromBytes :: MonadThrow m => ByteString -> m OsString
+ System.OsString.Internal: osstr :: QuasiQuoter
+ System.OsString.Internal: pack :: [OsChar] -> OsString
+ System.OsString.Internal: toChar :: OsChar -> Char
+ System.OsString.Internal: unpack :: OsString -> [OsChar]
+ System.OsString.Internal: unsafeFromChar :: Char -> OsChar
+ System.OsString.Internal.Types: OsChar :: PlatformChar -> OsChar
+ System.OsString.Internal.Types: OsString :: PlatformString -> OsString
+ System.OsString.Internal.Types: PosixChar :: Word8 -> PosixChar
+ System.OsString.Internal.Types: PosixString :: ShortByteString -> PosixString
+ System.OsString.Internal.Types: WindowsChar :: Word16 -> WindowsChar
+ System.OsString.Internal.Types: WindowsString :: ShortByteString -> WindowsString
+ System.OsString.Internal.Types: [getOsChar] :: OsChar -> PlatformChar
+ System.OsString.Internal.Types: [getOsString] :: OsString -> PlatformString
+ System.OsString.Internal.Types: [getPosixChar] :: PosixChar -> Word8
+ System.OsString.Internal.Types: [getPosixString] :: PosixString -> ShortByteString
+ System.OsString.Internal.Types: [getWindowsChar] :: WindowsChar -> Word16
+ System.OsString.Internal.Types: [getWindowsString] :: WindowsString -> ShortByteString
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.OsChar
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.PosixChar
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.WindowsChar
+ System.OsString.Internal.Types: instance Control.DeepSeq.NFData System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Base.Monoid System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Base.Monoid System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Base.Monoid System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Base.Semigroup System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Base.Semigroup System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Base.Semigroup System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.OsChar
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.PosixChar
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.WindowsChar
+ System.OsString.Internal.Types: instance GHC.Classes.Eq System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.OsChar
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.PosixChar
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.WindowsChar
+ System.OsString.Internal.Types: instance GHC.Classes.Ord System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.OsChar
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.PosixChar
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.WindowsChar
+ System.OsString.Internal.Types: instance GHC.Generics.Generic System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.OsChar
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.PosixChar
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.WindowsChar
+ System.OsString.Internal.Types: instance GHC.Show.Show System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.OsString
+ System.OsString.Internal.Types: instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.PosixString
+ System.OsString.Internal.Types: instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.WindowsString
+ System.OsString.Internal.Types: newtype OsChar
+ System.OsString.Internal.Types: newtype OsString
+ System.OsString.Internal.Types: newtype PosixChar
+ System.OsString.Internal.Types: newtype PosixString
+ System.OsString.Internal.Types: newtype WindowsChar
+ System.OsString.Internal.Types: newtype WindowsString
+ System.OsString.Internal.Types: pattern PS :: ShortByteString -> PosixString
+ System.OsString.Internal.Types: pattern PW :: Word8 -> PosixChar
+ System.OsString.Internal.Types: pattern WS :: ShortByteString -> WindowsString
+ System.OsString.Internal.Types: pattern WW :: Word16 -> WindowsChar
+ System.OsString.Internal.Types: type PlatformChar = PosixChar
+ System.OsString.Internal.Types: type PlatformString = PosixString
+ System.OsString.Internal.Types: unPS :: PosixString -> ShortByteString
+ System.OsString.Internal.Types: unPW :: PosixChar -> Word8
+ System.OsString.Internal.Types: unWS :: WindowsString -> ShortByteString
+ System.OsString.Internal.Types: unWW :: WindowsChar -> Word16
+ System.OsString.Posix: data PosixChar
+ System.OsString.Posix: data PosixString
+ System.OsString.Posix: decodeFS :: PosixString -> IO String
+ System.OsString.Posix: decodeUtf :: MonadThrow m => PosixString -> m String
+ System.OsString.Posix: decodeWith :: TextEncoding -> PosixString -> Either EncodingException String
+ System.OsString.Posix: encodeFS :: String -> IO PosixString
+ System.OsString.Posix: encodeUtf :: MonadThrow m => String -> m PosixString
+ System.OsString.Posix: encodeWith :: TextEncoding -> String -> Either EncodingException PosixString
+ System.OsString.Posix: fromBytes :: MonadThrow m => ByteString -> m PosixString
+ System.OsString.Posix: pack :: [PosixChar] -> PosixString
+ System.OsString.Posix: pstr :: QuasiQuoter
+ System.OsString.Posix: toChar :: PosixChar -> Char
+ System.OsString.Posix: unpack :: PosixString -> [PosixChar]
+ System.OsString.Posix: unsafeFromChar :: Char -> PosixChar
+ System.OsString.Windows: data WindowsChar
+ System.OsString.Windows: data WindowsString
+ System.OsString.Windows: decodeFS :: WindowsString -> IO String
+ System.OsString.Windows: decodeUtf :: MonadThrow m => WindowsString -> m String
+ System.OsString.Windows: decodeWith :: TextEncoding -> WindowsString -> Either EncodingException String
+ System.OsString.Windows: encodeFS :: String -> IO WindowsString
+ System.OsString.Windows: encodeUtf :: MonadThrow m => String -> m WindowsString
+ System.OsString.Windows: encodeWith :: TextEncoding -> String -> Either EncodingException WindowsString
+ System.OsString.Windows: fromBytes :: MonadThrow m => ByteString -> m WindowsString
+ System.OsString.Windows: pack :: [WindowsChar] -> WindowsString
+ System.OsString.Windows: pstr :: QuasiQuoter
+ System.OsString.Windows: toChar :: WindowsChar -> Char
+ System.OsString.Windows: unpack :: WindowsString -> [WindowsChar]
+ System.OsString.Windows: unsafeFromChar :: Char -> WindowsChar

Files

+ Generate.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE CPP, RecordWildCards, ViewPatterns #-}++module Generate(main) where++import Control.Exception+import Control.Monad+import Data.Semigroup+import Data.Char+import Data.List+import System.Directory+import System.IO+++main :: IO ()+main = do+    src <- readFile "System/FilePath/Internal.hs"+    let tests = map renderTest $ concatMap parseTest $ lines src+    writeFileBinaryChanged "tests/filepath-tests/TestGen.hs" $ unlines $+        ["-- GENERATED CODE: See ../Generate.hs"+#ifndef GHC_MAKE+        , "{-# LANGUAGE OverloadedStrings #-}"+        , "{-# LANGUAGE ViewPatterns #-}"+#endif+        , "{-# LANGUAGE CPP #-}"+        , "{-# OPTIONS_GHC -Wno-name-shadowing #-}"+        , "{-# OPTIONS_GHC -Wno-orphans #-}"+        ,"module TestGen(tests) where"+        ,"import TestUtil"+        ,"#if !MIN_VERSION_base(4,11,0)"+        ,"import Data.Semigroup"+        ,"#endif"+        ,"import Prelude as P"+        ,"import Data.String"+        ,"import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )"+        ,"import GHC.IO.Encoding.UTF16 ( mkUTF16le )"+        ,"import GHC.IO.Encoding.UTF8 ( mkUTF8 )"+        ,"import System.OsString.Internal.Types"+        ,"import System.OsPath.Encoding.Internal"+        ,"import qualified Data.Char as C"+        ,"import qualified System.OsPath.Data.ByteString.Short as SBS"+        ,"import qualified System.OsPath.Data.ByteString.Short.Word16 as SBS16"+        ,"import qualified System.FilePath.Windows as W"+        ,"import qualified System.FilePath.Posix as P"+#ifdef GHC_MAKE+        ,"import qualified System.OsPath.Windows.Internal as AFP_W"+        ,"import qualified System.OsPath.Posix.Internal as AFP_P"+#else+        ,"import qualified System.OsPath.Windows as AFP_W"+        ,"import qualified System.OsPath.Posix as AFP_P"+#endif+        ,"instance IsString WindowsString where fromString = WS . either (error . show) id . encodeWithTE (mkUTF16le TransliterateCodingFailure)"+        ,"instance IsString PosixString where fromString = PS . either (error . show) id . encodeWithTE (mkUTF8 TransliterateCodingFailure)"+        ,"#if defined(mingw32_HOST_OS) || defined(__MINGW32__)"+        ,"instance IsString OsString where fromString = OsString . WS . either (error . show) id . encodeWithTE (mkUTF16le TransliterateCodingFailure)"+        ,"#else"+        ,"instance IsString OsString where fromString = OsString . PS . either (error . show) id . encodeWithTE (mkUTF8 TransliterateCodingFailure)"+        ,"#endif"+        ,"tests :: [(String, Property)]"+        ,"tests ="] +++        ["    " ++ c ++ "(" ++ show t1 ++ ", " ++ t2 ++ ")" | (c,(t1,t2)) <- zip ("[":repeat ",") tests] +++        ["    ]"]++++data PW = P      -- legacy posix+        | W      -- legacy windows+        | AFP_P  -- abstract-filepath posix+        | AFP_W  -- abstract-filepath windows+  deriving Show++data Test = Test+    {testPlatform :: PW+    ,testVars :: [(String,String)]   -- generator constructor, variable+    ,testBody :: [String]+    }+++parseTest :: String -> [Test]+parseTest (stripPrefix "-- > " -> Just x) = platform $ toLexemes x+    where+        platform ("Windows":":":x) = [valid W x, valid AFP_W x]+        platform ("Posix"  :":":x) = [valid P x, valid AFP_P x]+        platform x                 = [valid P x, valid W x, valid AFP_P x, valid AFP_W x]++        valid p ("Valid":x) = free p a $ drop 1 b+            where (a,b) = break (== "=>") x+        valid p x = free p [] x++        free p val x = Test p [(ctor v, v) | v <- vars] x+            where vars = nub $ sort [v | v@[c] <- x, isAlpha c]+                  ctor v | v < "x"  = ""+                         | v `elem` val = "QFilePathValid" ++ show p+                         | otherwise = case p of+                                         AFP_P -> if v == "z" then "QFilePathsAFP_P" else "QFilePathAFP_P"+                                         AFP_W -> if v == "z" then "QFilePathsAFP_W" else "QFilePathAFP_W"+                                         _ -> if v == "z" then "" else "QFilePath"+parseTest _ = []+++toLexemes :: String -> [String]+toLexemes x = case lex x of+    [("","")] -> []+    [(x,y)] -> x : toLexemes y+    y -> error $ "Generate.toLexemes, " ++ show x ++ " -> " ++ show y+++fromLexemes :: [String] -> String+fromLexemes = unwords . f+    where+        f ("`":x:"`":xs) = ("`" ++ x ++ "`") : f xs+        f (x:y:xs) | x `elem` ["[","("] || y `elem` [",",")","]"] = f $ (x ++ y) : xs+        f (x:xs) = x : f xs+        f [] = []+++renderTest :: Test -> (String, String)+renderTest Test{..} = (body, code)+    where+        code = "property $ " ++ if null testVars then body else "\\" ++ unwords vars ++ " -> " ++ body+        vars = [if null ctor then v else "(" ++ ctor ++ " " ++ v ++ ")" | (ctor,v) <- testVars]++        body = fromLexemes $ map (qualify testPlatform) testBody++++qualify :: PW -> String -> String+qualify pw str+    | str `elem` fpops || (all isAlpha str && length str > 1 && str `notElem` prelude)+      = if str `elem` bs then qualifyBS str  else show pw ++ "." ++ str+    | otherwise = encode str+    where+        bs = ["null", "concat", "isPrefixOf", "isSuffixOf", "any"]+        prelude = ["elem","uncurry","snd","fst","not","if","then","else"+                  ,"True","False","Just","Nothing","fromJust","foldr"]+        fpops = ["</>","<.>","-<.>"]+#ifdef GHC_MAKE+        encode v+          | isString' v = case pw of+                            AFP_P -> "(encodeUtf8 " <> v <> ")"+                            AFP_W -> "(encodeUtf16LE " <> v <> ")"+                            _ -> v+          | isChar' v = case pw of+                            AFP_P -> "(fromIntegral . C.ord $ " <> v <> ")"+                            AFP_W -> "(fromIntegral . C.ord $ " <> v <> ")"+                            _ -> v+          | otherwise = v+        isString' xs@('"':_:_) = last xs == '"'+        isString' _ = False+        isChar' xs@('\'':_:_) = last xs == '\''+        isChar' _ = False+        qualifyBS v = case pw of+                        AFP_P -> "SBS." <> v+                        AFP_W -> "SBS16." <> v+                        _ -> v+#else+        encode v+          | isString' v = case pw of+                            AFP_P -> "(" <> v <> ")"+                            AFP_W -> "(" <> v <> ")"+                            _ -> v+          | isChar' v = case pw of+                            AFP_P -> "(PW . fromIntegral . C.ord $ " <> v <> ")"+                            AFP_W -> "(WW . fromIntegral . C.ord $ " <> v <> ")"+                            _ -> v+          | otherwise = v+        isString' xs@('"':_:_) = last xs == '"'+        isString' _ = False+        isChar' xs@('\'':_:_) = last xs == '\''+        isChar' _ = False+        qualifyBS v = case pw of+                        AFP_P+                          | v == "concat" -> "(PS . SBS." <> v <> " . fmap getPosixString)"+                          | v == "any" -> "(\\f (getPosixString -> x) -> SBS." <> v <> " (f . PW) x)"+                          | v == "isPrefixOf" -> "(\\(getPosixString -> x) (getPosixString -> y) -> SBS." <> v <> " x y)"+                          | v == "isSuffixOf" -> "(\\(getPosixString -> x) (getPosixString -> y) -> SBS." <> v <> " x y)"+                          | otherwise -> "(SBS." <> v <> " . getPosixString)"+                        AFP_W+                          | v == "concat" -> "(WS . SBS16." <> v <> " . fmap getWindowsString)"+                          | v == "any" -> "(\\f (getWindowsString -> x) -> SBS16." <> v <> " (f . WW) x)"+                          | v == "isPrefixOf" -> "(\\(getWindowsString -> x) (getWindowsString -> y) -> SBS16." <> v <> " x y)"+                          | v == "isSuffixOf" -> "(\\(getWindowsString -> x) (getWindowsString -> y) -> SBS16." <> v <> " x y)"+                          | otherwise -> "(SBS16." <> v <> " . getWindowsString)"+                        _ -> v+#endif++++---------------------------------------------------------------------+-- UTILITIES++writeFileBinary :: FilePath -> String -> IO ()+writeFileBinary file x = withBinaryFile file WriteMode $ \h -> hPutStr h x++readFileBinary' :: FilePath -> IO String+readFileBinary' file = withBinaryFile file ReadMode $ \h -> do+    s <- hGetContents h+    evaluate $ length s+    pure s++writeFileBinaryChanged :: FilePath -> String -> IO ()+writeFileBinaryChanged file x = do+    b <- doesFileExist file+    old <- if b then fmap Just $ readFileBinary' file else pure Nothing+    when (Just x /= old) $+        writeFileBinary file x
Makefile view
@@ -1,10 +1,7 @@-all: cpp gen--cpp:-	cpphs --noline -DIS_WINDOWS=False -DMODULE_NAME=Posix   -OSystem/FilePath/Posix.hs   System/FilePath/Internal.hs-	cpphs --noline -DIS_WINDOWS=True  -DMODULE_NAME=Windows -OSystem/FilePath/Windows.hs System/FilePath/Internal.hs+all: gen  gen: 	runhaskell Generate.hs -.PHONY: all cpp gen++.PHONY: all gen
README.md view
@@ -1,32 +1,41 @@ # FilePath [![Hackage version](https://img.shields.io/hackage/v/filepath.svg?label=Hackage)](https://hackage.haskell.org/package/filepath)  The `filepath` package provides functionality for manipulating `FilePath` values, and is shipped with [GHC](https://www.haskell.org/ghc/).-It provides three modules:+It provides two variants for filepaths: -* [`System.FilePath.Posix`](http://hackage.haskell.org/package/filepath/docs/System-FilePath-Posix.html)-  manipulates POSIX/Linux style `FilePath` values (with `/` as the path separator).-* [`System.FilePath.Windows`](http://hackage.haskell.org/package/filepath/docs/System-FilePath-Windows.html)-  manipulates Windows style `FilePath` values (with either `\` or `/` as the path separator, and deals with drives).-* [`System.FilePath`](http://hackage.haskell.org/package/filepath/docs/System-FilePath.html)-  is an alias for the module appropriate to your platform.+1. legacy filepaths: `type FilePath = String`+2. operating system abstracted filepaths (`OsPath`): internally unpinned `ShortByteString` (platform-dependent encoding) +It is recommended to use `OsPath` when possible, because it is more correct.++For each variant there are three main modules:++* `System.FilePath.Posix` / `System.OsPath.Posix` manipulates POSIX\/Linux style `FilePath` values (with `/` as the path separator).+* `System.FilePath.Windows` / `System.OsPath.Windows` manipulates Windows style `FilePath` values (with either `\` or `/` as the path separator, and deals with drives).+* `System.FilePath` / `System.OsPath` for dealing with current platform-specific filepaths+ All three modules provide the same API, and the same documentation (calling out differences in the different variants). +`System.OsString` is like `System.OsPath`, but more general purpose. Refer to the documentation of+those modules for more information.+ ### What is a `FilePath`? -In Haskell, the definition is `type FilePath = String` as of now. A Haskell `String` is a list of Unicode code points.+In Haskell, the legacy definition (used in `base` and Prelude) is `type FilePath = String`,+where a Haskell `String` is a list of Unicode code points. +The new definition is (simplified) `newtype OsPath = AFP ShortByteString`, where+`ShortByteString` is an unpinned byte array and follows syscall conventions, preserving the encoding.+ On unix, filenames don't have a predefined encoding as per the [POSIX specification](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170) and are passed as `char[]` to syscalls. -On windows (at least the API used by `Win32`) filepaths are UTF-16 strings.+On windows (at least the API used by `Win32`) filepaths are UTF-16LE strings. -This means that Haskell filepaths have to be converted to C-strings on unix-(utilizing the current filesystem encoding) and to UTF-16 strings-on windows.+You are encouraged to use `OsPath` whenever possible, because it is more correct. -Further, this is a low-level library and it makes no attempt at providing a more+Also note that this is a low-level library and it makes no attempt at providing a more type safe variant for filepaths (e.g. by distinguishing between absolute and relative paths) and ensures no invariants (such as filepath validity). 
System/FilePath/Internal.hs view
@@ -1,7 +1,5 @@-#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE Safe #-}-#endif {-# LANGUAGE PatternGuards #-}+{-# LANGUAGE TypeApplications #-}  -- This template expects CPP definitions for: --     MODULE_NAME = Posix | Windows@@ -61,16 +59,25 @@ -- -- References: -- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)+#ifndef OS_PATH module System.FilePath.MODULE_NAME+#else+module System.OsPath.MODULE_NAME.Internal+#endif     (     -- * Separator predicates+#ifndef OS_PATH     FilePath,+#endif     pathSeparator, pathSeparators, isPathSeparator,     searchPathSeparator, isSearchPathSeparator,     extSeparator, isExtSeparator,      -- * @$PATH@ methods-    splitSearchPath, getSearchPath,+    splitSearchPath,+#ifndef OS_PATH+    getSearchPath,+#endif      -- * Extension functions     splitExtension,@@ -103,11 +110,50 @@     )     where -import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)+{- HLINT ignore "Use fewer imports" -}+import Prelude (Char, Bool(..), Maybe(..), (.), (&&), (<=), not, fst, maybe, (||), (==), ($), otherwise, fmap, mempty, (>=), (/=), (++), snd)+import Data.Semigroup ((<>))+import qualified Prelude as P import Data.Maybe(isJust)-import Data.List(stripPrefix, isSuffixOf)+import qualified Data.List as L +#ifndef OS_PATH+import Data.String (fromString) import System.Environment(getEnv)+import Prelude (String, map, FilePath, Eq, IO, id, last, init, reverse, dropWhile, null, break, takeWhile, take, all, elem, any, head, tail, span)+import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)+import Data.List(stripPrefix, isSuffixOf, uncons)+#define CHAR Char+#define STRING String+#define FILEPATH FilePath+#else+import Prelude (fromIntegral)+import Control.Exception ( SomeException, evaluate, try, displayException )+import Data.Bifunctor (first)+import Control.DeepSeq (force)+import GHC.IO (unsafePerformIO)+import qualified Data.Char as C+#ifdef WINDOWS+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import qualified GHC.Foreign as GHC+import Data.Word ( Word16 )+import System.OsPath.Data.ByteString.Short.Word16+import System.OsPath.Data.ByteString.Short ( packCStringLen )+#define CHAR Word16+#define STRING ShortByteString+#define FILEPATH ShortByteString+#else+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import qualified GHC.Foreign as GHC+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import Data.Word ( Word8 )+import System.OsPath.Data.ByteString.Short+#define CHAR Word8+#define STRING ShortByteString+#define FILEPATH ShortByteString+#endif+#endif    infixr 7  <.>, -<.>@@ -138,51 +184,52 @@ -- > Windows: pathSeparator == '\\' -- > Posix:   pathSeparator ==  '/' -- > isPathSeparator pathSeparator-pathSeparator :: Char-pathSeparator = if isWindows then '\\' else '/'+pathSeparator :: CHAR+pathSeparator = if isWindows then _backslash else _slash  -- | The list of all possible separators. -- -- > Windows: pathSeparators == ['\\', '/'] -- > Posix:   pathSeparators == ['/'] -- > pathSeparator `elem` pathSeparators-pathSeparators :: [Char]-pathSeparators = if isWindows then "\\/" else "/"+pathSeparators :: [CHAR]+pathSeparators = if isWindows then [_backslash, _slash] else [_slash]  -- | Rather than using @(== 'pathSeparator')@, use this. Test if something --   is a path separator. -- -- > isPathSeparator a == (a `elem` pathSeparators)-isPathSeparator :: Char -> Bool-isPathSeparator '/' = True-isPathSeparator '\\' = isWindows-isPathSeparator _ = False+isPathSeparator :: CHAR -> Bool+isPathSeparator c+  | c == _slash = True+  | c == _backslash = isWindows+  | otherwise = False   -- | The character that is used to separate the entries in the $PATH environment variable. -- -- > Windows: searchPathSeparator == ';' -- > Posix:   searchPathSeparator == ':'-searchPathSeparator :: Char-searchPathSeparator = if isWindows then ';' else ':'+searchPathSeparator :: CHAR+searchPathSeparator = if isWindows then _semicolon else _colon  -- | Is the character a file separator? -- -- > isSearchPathSeparator a == (a == searchPathSeparator)-isSearchPathSeparator :: Char -> Bool+isSearchPathSeparator :: CHAR -> Bool isSearchPathSeparator = (== searchPathSeparator)   -- | File extension character -- -- > extSeparator == '.'-extSeparator :: Char-extSeparator = '.'+extSeparator :: CHAR+extSeparator = _period  -- | Is the character an extension character? -- -- > isExtSeparator a == (a == extSeparator)-isExtSeparator :: Char -> Bool+isExtSeparator :: CHAR -> Bool isExtSeparator = (== extSeparator)  @@ -201,21 +248,31 @@ -- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"] -- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]-splitSearchPath :: String -> [FilePath]+splitSearchPath :: STRING -> [FILEPATH] splitSearchPath = f     where-    f xs = case break isSearchPathSeparator xs of-           (pre, []    ) -> g pre-           (pre, _:post) -> g pre ++ f post+    f xs = let (pre, post) = break isSearchPathSeparator xs+           in case uncons post of+             Nothing     -> g pre+             Just (_, t) -> g pre ++ f t -    g "" = ["." | isPosix]-    g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]-    g x = [x]+    g x = case uncons x of+      Nothing -> [singleton _period | isPosix]+      Just (h, t)+        | h == _quotedbl+        , (Just _) <- uncons t -- >= 2+        , isWindows+        , (Just (i, l)) <- unsnoc t+        , l == _quotedbl -> [i]+        | otherwise -> [x]  --- | Get a list of 'FilePath's in the $PATH variable.-getSearchPath :: IO [FilePath]+-- TODO for AFPP+#ifndef OS_PATH+-- | Get a list of 'FILEPATH's in the $PATH variable.+getSearchPath :: IO [FILEPATH] getSearchPath = fmap splitSearchPath (getEnv "PATH")+#endif   ---------------------------------------------------------------------@@ -224,7 +281,7 @@ -- | Split on the extension. 'addExtension' is the inverse. -- -- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")--- > uncurry (++) (splitExtension x) == x+-- > uncurry (<>) (splitExtension x) == x -- > Valid x => uncurry addExtension (splitExtension x) == x -- > splitExtension "file.txt" == ("file",".txt") -- > splitExtension "file" == ("file","")@@ -233,12 +290,12 @@ -- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext") -- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred") -- > splitExtension "file/path.txt/" == ("file/path.txt/","")-splitExtension :: FilePath -> (String, String)-splitExtension x = case nameDot of-                       "" -> (x,"")-                       _ -> (dir ++ init nameDot, extSeparator : ext)+splitExtension :: FILEPATH -> (STRING, STRING)+splitExtension x = if null nameDot+                   then (x, mempty)+                   else (dir <> init nameDot, singleton extSeparator <> ext)     where-        (dir,file) = splitFileName_ x+        (dir,file)    = splitFileName_ x         (nameDot,ext) = breakEnd isExtSeparator file  -- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.@@ -247,7 +304,7 @@ -- > takeExtension x == snd (splitExtension x) -- > Valid x => takeExtension (addExtension x "ext") == ".ext" -- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"-takeExtension :: FilePath -> String+takeExtension :: FILEPATH -> STRING takeExtension = snd . splitExtension  -- | Remove the current extension and add another, equivalent to 'replaceExtension'.@@ -255,7 +312,7 @@ -- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- > "foo.o" -<.> "c" == "foo.c"-(-<.>) :: FilePath -> String -> FilePath+(-<.>) :: FILEPATH -> STRING -> FILEPATH (-<.>) = replaceExtension  -- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.@@ -268,21 +325,21 @@ -- > replaceExtension "file.txt" "" == "file" -- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- > replaceExtension x y == addExtension (dropExtension x) y-replaceExtension :: FilePath -> String -> FilePath+replaceExtension :: FILEPATH -> STRING -> FILEPATH replaceExtension x y = dropExtension x <.> y  -- | Add an extension, even if there is already one there, equivalent to 'addExtension'. -- -- > "/directory/path" <.> "ext" == "/directory/path.ext" -- > "/directory/path" <.> ".ext" == "/directory/path.ext"-(<.>) :: FilePath -> String -> FilePath+(<.>) :: FILEPATH -> STRING -> FILEPATH (<.>) = addExtension  -- | Remove last extension, and the \".\" preceding it. -- -- > dropExtension "/directory/path.ext" == "/directory/path" -- > dropExtension x == fst (splitExtension x)-dropExtension :: FilePath -> FilePath+dropExtension :: FILEPATH -> FILEPATH dropExtension = fst . splitExtension  -- | Add an extension, even if there is already one there, equivalent to '<.>'.@@ -295,12 +352,13 @@ -- > addExtension x "" == x -- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"-addExtension :: FilePath -> String -> FilePath-addExtension file "" = file-addExtension file xs@(x:_) = joinDrive a res+addExtension :: FILEPATH -> STRING -> FILEPATH+addExtension file xs = case uncons xs of+  Nothing -> file+  Just (x, _) -> joinDrive a res     where-        res = if isExtSeparator x then b ++ xs-              else b ++ [extSeparator] ++ xs+        res = if isExtSeparator x then b <> xs+              else b <> singleton extSeparator <> xs          (a,b) = splitDrive file @@ -309,7 +367,7 @@ -- > hasExtension "/directory/path.ext" == True -- > hasExtension "/directory/path" == False -- > null (takeExtension x) == not (hasExtension x)-hasExtension :: FilePath -> Bool+hasExtension :: FILEPATH -> Bool hasExtension = any isExtSeparator . takeFileName  @@ -321,12 +379,14 @@ -- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- > "png" `isExtensionOf` "/directory/file.png.jpg" == False -- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False-isExtensionOf :: String -> FilePath -> Bool-isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions-isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions+isExtensionOf :: STRING -> FILEPATH -> Bool+isExtensionOf ext = \fp -> case uncons ext of+  Just (x, _)+    | x == _period -> isSuffixOf ext . takeExtensions $ fp+  _ -> isSuffixOf (singleton _period <> ext) . takeExtensions $ fp --- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.---   Returns 'Nothing' if the FilePath does not have the given extension, or+-- | Drop the given extension from a FILEPATH, and the @\".\"@ preceding it.+--   Returns 'Nothing' if the FILEPATH does not have the given extension, or --   'Just' and the part before the extension if it does. -- --   This function can be more predictable than 'dropExtensions', especially if the filename@@ -341,21 +401,22 @@ -- > stripExtension "baz"  "foo.bar"  == Nothing -- > stripExtension "bar"  "foobar"   == Nothing -- > stripExtension ""     x          == Just x-stripExtension :: String -> FilePath -> Maybe FilePath-stripExtension []        path = Just path-stripExtension ext@(x:_) path = stripSuffix dotExt path-    where dotExt = if isExtSeparator x then ext else '.':ext+stripExtension :: STRING -> FILEPATH -> Maybe FILEPATH+stripExtension ext path = case uncons ext of+  Just (x, _) -> let dotExt = if isExtSeparator x then ext else singleton _period <> ext+                 in stripSuffix dotExt path+  Nothing -> Just path   -- | Split on all extensions. -- -- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext") -- > splitExtensions "file.tar.gz" == ("file",".tar.gz")--- > uncurry (++) (splitExtensions x) == x+-- > uncurry (<>) (splitExtensions x) == x -- > Valid x => uncurry addExtension (splitExtensions x) == x -- > splitExtensions "file.tar.gz" == ("file",".tar.gz")-splitExtensions :: FilePath -> (FilePath, String)-splitExtensions x = (a ++ c, d)+splitExtensions :: FILEPATH -> (FILEPATH, STRING)+splitExtensions x = (a <> c, d)     where         (a,b) = splitFileName_ x         (c,d) = break isExtSeparator b@@ -366,14 +427,14 @@ -- > dropExtensions "file.tar.gz" == "file" -- > not $ hasExtension $ dropExtensions x -- > not $ any isExtSeparator $ takeFileName $ dropExtensions x-dropExtensions :: FilePath -> FilePath+dropExtensions :: FILEPATH -> FILEPATH dropExtensions = fst . splitExtensions  -- | Get all extensions. -- -- > takeExtensions "/directory/path.ext" == ".ext" -- > takeExtensions "file.tar.gz" == ".tar.gz"-takeExtensions :: FilePath -> String+takeExtensions :: FILEPATH -> STRING takeExtensions = snd . splitExtensions  @@ -384,7 +445,7 @@ -- -- > replaceExtensions "file.fred.bob" "txt" == "file.txt" -- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"-replaceExtensions :: FilePath -> String -> FilePath+replaceExtensions :: FILEPATH -> STRING -> FILEPATH replaceExtensions x y = dropExtensions x <.> y  @@ -394,14 +455,14 @@  -- | Is the given character a valid drive letter? -- only a-z and A-Z are letters, not isAlpha which is more unicodey-isLetter :: Char -> Bool+isLetter :: CHAR -> Bool isLetter x = isAsciiLower x || isAsciiUpper x   -- | Split a path into a drive and a path. --   On Posix, \/ is a Drive. ----- > uncurry (++) (splitDrive x) == x+-- > uncurry (<>) (splitDrive x) == x -- > Windows: splitDrive "file" == ("","file") -- > Windows: splitDrive "c:/file" == ("c:/","file") -- > Windows: splitDrive "c:\\file" == ("c:\\","file")@@ -415,47 +476,54 @@ -- > Posix:   splitDrive "//test" == ("//","test") -- > Posix:   splitDrive "test/file" == ("","test/file") -- > Posix:   splitDrive "file" == ("","file")-splitDrive :: FilePath -> (FilePath, FilePath)-splitDrive x | isPosix = span (== '/') x+splitDrive :: FILEPATH -> (FILEPATH, FILEPATH)+splitDrive x | isPosix = span (== _slash) x splitDrive x | Just y <- readDriveLetter x = y splitDrive x | Just y <- readDriveUNC x = y splitDrive x | Just y <- readDriveShare x = y-splitDrive x = ("",x)+splitDrive x = (mempty, x) -addSlash :: FilePath -> FilePath -> (FilePath, FilePath)-addSlash a xs = (a++c,d)-    where (c,d) = span isPathSeparator xs+addSlash :: FILEPATH -> FILEPATH -> (FILEPATH, FILEPATH)+addSlash a xs = (a <> c, d)+    where (c, d) = span isPathSeparator xs  -- See [1]. -- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"-readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)-readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =-    case map toUpper xs of-        ('U':'N':'C':s4:_) | isPathSeparator s4 ->-            let (a,b) = readDriveShareName (drop 4 xs)-            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)-        _ -> case readDriveLetter xs of-                 -- Extended-length path.-                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)-                 Nothing -> Nothing-readDriveUNC _ = Nothing+readDriveUNC :: FILEPATH -> Maybe (FILEPATH, FILEPATH)+readDriveUNC bs = case unpack bs of+  (s1:s2:q:s3:xs)+    | q == _question && L.all isPathSeparator [s1,s2,s3] ->+      case L.map toUpper xs of+          (u:n:c:s4:_)+            | u == _U && n == _N && c == _C && isPathSeparator s4 ->+              let (a,b) = readDriveShareName (pack (L.drop 4 xs))+              in Just (pack (s1:s2:_question:s3:L.take 4 xs) <> a, b)+          _ -> case readDriveLetter (pack xs) of+                   -- Extended-length path.+                   Just (a,b) -> Just (pack [s1,s2,_question,s3] <> a, b)+                   Nothing -> Nothing+  _ -> Nothing  {- c:\ -}-readDriveLetter :: String -> Maybe (FilePath, FilePath)-readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)-readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)-readDriveLetter _ = Nothing+readDriveLetter :: STRING -> Maybe (FILEPATH, FILEPATH)+readDriveLetter bs = case unpack bs of+  (x:c:y:xs)+    | c == _colon && isLetter x && isPathSeparator y -> Just $ addSlash (pack [x,_colon]) (pack (y:xs))+  (x:c:xs)+    | c == _colon && isLetter x -> Just (pack [x,_colon], pack xs)+  _ -> Nothing  {- \\sharename\ -}-readDriveShare :: String -> Maybe (FilePath, FilePath)-readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =-        Just (s1:s2:a,b)-    where (a,b) = readDriveShareName xs-readDriveShare _ = Nothing+readDriveShare :: STRING -> Maybe (FILEPATH, FILEPATH)+readDriveShare bs = case unpack bs of+  (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 -> +    let (a, b) = readDriveShareName (pack xs)+    in Just (singleton s1 <> singleton s2 <> a,b)+  _ -> Nothing  {- assume you have already seen \\ -} {- share\bob -> "share\", "bob" -}-readDriveShareName :: String -> (FilePath, FilePath)+readDriveShareName :: STRING -> (FILEPATH, FILEPATH) readDriveShareName name = addSlash a b     where (a,b) = break isPathSeparator name @@ -468,19 +536,19 @@ -- > Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- > Windows: joinDrive "/:" "foo" == "/:\\foo"-joinDrive :: FilePath -> FilePath -> FilePath+joinDrive :: FILEPATH -> FILEPATH -> FILEPATH joinDrive = combineAlways  -- | Get the drive from a filepath. -- -- > takeDrive x == fst (splitDrive x)-takeDrive :: FilePath -> FilePath+takeDrive :: FILEPATH -> FILEPATH takeDrive = fst . splitDrive  -- | Delete the drive, if it exists. -- -- > dropDrive x == snd (splitDrive x)-dropDrive :: FilePath -> FilePath+dropDrive :: FILEPATH -> FILEPATH dropDrive = snd . splitDrive  -- | Does a path have a drive.@@ -491,7 +559,7 @@ -- > Windows: hasDrive "C:foo" == True -- >          hasDrive "foo" == False -- >          hasDrive "" == False-hasDrive :: FilePath -> Bool+hasDrive :: FILEPATH -> Bool hasDrive = not . null . takeDrive  @@ -502,7 +570,7 @@ -- > Windows: isDrive "C:\\" == True -- > Windows: isDrive "C:\\foo" == False -- >          isDrive "" == False-isDrive :: FilePath -> Bool+isDrive :: FILEPATH -> Bool isDrive x = not (null x) && null (dropDrive x)  @@ -520,28 +588,31 @@ -- > splitFileName "bob" == ("./", "bob") -- > Posix:   splitFileName "/" == ("/","") -- > Windows: splitFileName "c:" == ("c:","")-splitFileName :: FilePath -> (String, String)-splitFileName x = (if null dir then "./" else dir, name)-    where-        (dir, name) = splitFileName_ x+splitFileName :: FILEPATH -> (STRING, STRING)+splitFileName x = if null path+    then (dotSlash, file)+    else (path, file)+  where+    (path, file) = splitFileName_ x+    dotSlash = singleton _period <> singleton _slash --- version of splitFileName where, if the FilePath has no directory+-- version of splitFileName where, if the FILEPATH has no directory -- component, the returned directory is "" rather than "./".  This -- is used in cases where we are going to combine the returned--- directory to make a valid FilePath, and having a "./" appear would+-- directory to make a valid FILEPATH, and having a "./" appear would -- look strange and upset simple equality properties.  See -- e.g. replaceFileName.-splitFileName_ :: FilePath -> (String, String)-splitFileName_ x = (drv ++ dir, file)-    where-        (drv,pth) = splitDrive x-        (dir,file) = breakEnd isPathSeparator pth+splitFileName_ :: FILEPATH -> (STRING, STRING)+splitFileName_ fp = (drv <> dir, file)+  where+    (drv, pth) = splitDrive fp+    (dir, file) = breakEnd isPathSeparator pth  -- | Set the filename. -- -- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- > Valid x => replaceFileName x (takeFileName x) == x-replaceFileName :: FilePath -> String -> FilePath+replaceFileName :: FILEPATH -> STRING -> FILEPATH replaceFileName x y = a </> y where (a,_) = splitFileName_ x  -- | Drop the filename. Unlike 'takeDirectory', this function will leave@@ -549,7 +620,7 @@ -- -- > dropFileName "/directory/file.ext" == "/directory/" -- > dropFileName x == fst (splitFileName x)-dropFileName :: FilePath -> FilePath+dropFileName :: FILEPATH -> FILEPATH dropFileName = fst . splitFileName  @@ -557,12 +628,12 @@ -- -- > takeFileName "/directory/file.ext" == "file.ext" -- > takeFileName "test/" == ""--- > takeFileName x `isSuffixOf` x+-- > isSuffixOf (takeFileName x) x -- > takeFileName x == snd (splitFileName x) -- > Valid x => takeFileName (replaceFileName x "fred") == "fred" -- > Valid x => takeFileName (x </> "fred") == "fred" -- > Valid x => isRelative (takeFileName x)-takeFileName :: FilePath -> FilePath+takeFileName :: FILEPATH -> FILEPATH takeFileName = snd . splitFileName  -- | Get the base name, without an extension or path.@@ -574,7 +645,7 @@ -- > takeBaseName "test" == "test" -- > takeBaseName (addTrailingPathSeparator x) == "" -- > takeBaseName "file/file.tar.gz" == "file.tar"-takeBaseName :: FilePath -> String+takeBaseName :: FILEPATH -> STRING takeBaseName = dropExtension . takeFileName  -- | Set the base name.@@ -584,7 +655,7 @@ -- > replaceBaseName "fred" "bill" == "bill" -- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- > Valid x => replaceBaseName x (takeBaseName x) == x-replaceBaseName :: FilePath -> String -> FilePath+replaceBaseName :: FILEPATH -> STRING -> FILEPATH replaceBaseName pth nam = combineAlways a (nam <.> ext)     where         (a,b) = splitFileName_ pth@@ -594,14 +665,16 @@ -- -- > hasTrailingPathSeparator "test" == False -- > hasTrailingPathSeparator "test/" == True-hasTrailingPathSeparator :: FilePath -> Bool-hasTrailingPathSeparator "" = False-hasTrailingPathSeparator x = isPathSeparator (last x)+hasTrailingPathSeparator :: FILEPATH -> Bool+hasTrailingPathSeparator x+  | null x = False+  | otherwise = isPathSeparator $ last x  -hasLeadingPathSeparator :: FilePath -> Bool-hasLeadingPathSeparator "" = False-hasLeadingPathSeparator x = isPathSeparator (head x)+hasLeadingPathSeparator :: FILEPATH -> Bool+hasLeadingPathSeparator x+  | null x = False+  | otherwise = isPathSeparator $ head x   -- | Add a trailing file path separator if one is not already present.@@ -609,8 +682,8 @@ -- > hasTrailingPathSeparator (addTrailingPathSeparator x) -- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"-addTrailingPathSeparator :: FilePath -> FilePath-addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]+addTrailingPathSeparator :: FILEPATH -> FILEPATH+addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x <> singleton pathSeparator   -- | Remove any trailing path separators@@ -619,18 +692,18 @@ -- >           dropTrailingPathSeparator "/" == "/" -- > Windows:  dropTrailingPathSeparator "\\" == "\\" -- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x-dropTrailingPathSeparator :: FilePath -> FilePath+dropTrailingPathSeparator :: FILEPATH -> FILEPATH dropTrailingPathSeparator x =     if hasTrailingPathSeparator x && not (isDrive x)     then let x' = dropWhileEnd isPathSeparator x-         in if null x' then [last x] else x'+         in if null x' then singleton (last x) else x'     else x   -- | Get the directory name, move up one level. -- -- >           takeDirectory "/directory/other.ext" == "/directory"--- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- >           isPrefixOf (takeDirectory x) x || takeDirectory x == "." -- >           takeDirectory "foo" == "." -- >           takeDirectory "/" == "/" -- >           takeDirectory "/foo" == "/"@@ -640,30 +713,32 @@ -- > Windows:  takeDirectory "foo\\bar" == "foo" -- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar" -- > Windows:  takeDirectory "C:\\" == "C:\\"-takeDirectory :: FilePath -> FilePath+takeDirectory :: FILEPATH -> FILEPATH takeDirectory = dropTrailingPathSeparator . dropFileName  -- | Set the directory, keeping the filename the same. -- -- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x-replaceDirectory :: FilePath -> String -> FilePath+replaceDirectory :: FILEPATH -> STRING -> FILEPATH replaceDirectory x dir = combineAlways dir (takeFileName x)   -- | An alias for '</>'.-combine :: FilePath -> FilePath -> FilePath+combine :: FILEPATH -> FILEPATH -> FILEPATH combine a b | hasLeadingPathSeparator b || hasDrive b = b             | otherwise = combineAlways a b  -- | Combine two paths, assuming rhs is NOT absolute.-combineAlways :: FilePath -> FilePath -> FilePath+combineAlways :: FILEPATH -> FILEPATH -> FILEPATH combineAlways a b | null a = b                   | null b = a-                  | hasTrailingPathSeparator a = a ++ b-                  | otherwise = case a of-                      [a1,':'] | isWindows && isLetter a1 -> a ++ b-                      _ -> a ++ [pathSeparator] ++ b+                  | hasTrailingPathSeparator a = a <> b+                  | otherwise = case unpack a of+                      [a1, a2] | isWindows+                               , isLetter a1+                               , a2 == _colon -> a <> b+                      _ -> a <> singleton pathSeparator <> b   -- | Combine two paths with a path separator.@@ -707,7 +782,7 @@ -- -- > Windows: "D:\\foo" </> "C:bar" == "C:bar" -- > Windows: "C:\\foo" </> "C:bar" == "C:bar"-(</>) :: FilePath -> FilePath -> FilePath+(</>) :: FILEPATH -> FILEPATH -> FILEPATH (</>) = combine  @@ -720,13 +795,14 @@ -- > splitPath "" == [] -- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- > Posix:   splitPath "/file/test" == ["/","file/","test"]-splitPath :: FilePath -> [FilePath]-splitPath x = [drive | drive /= ""] ++ f path+splitPath :: FILEPATH -> [FILEPATH]+splitPath x = [drive | not (null drive)] ++ f path     where-        (drive,path) = splitDrive x+        (drive, path) = splitDrive x -        f "" = []-        f y = (a++c) : f d+        f y+          | null y = []+          | otherwise = (a <> c) : f d             where                 (a,b) = break isPathSeparator y                 (c,d) = span  isPathSeparator b@@ -741,20 +817,20 @@ -- >          splitDirectories "" == [] -- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- >          splitDirectories "/test///file" == ["/","test","file"]-splitDirectories :: FilePath -> [FilePath]-splitDirectories = map dropTrailingPathSeparator . splitPath+splitDirectories :: FILEPATH -> [FILEPATH]+splitDirectories = L.map dropTrailingPathSeparator . splitPath   -- | Join path elements back together. ----- > joinPath a == foldr (</>) "" a+-- > joinPath z == foldr (</>) "" z -- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext" -- > Valid x => joinPath (splitPath x) == x -- > joinPath [] == "" -- > Posix: joinPath ["test","file","path"] == "test/file/path"-joinPath :: [FilePath] -> FilePath+joinPath :: [FILEPATH] -> FILEPATH -- Note that this definition on c:\\c:\\, join then split will give c:\\.-joinPath = foldr combine ""+joinPath = P.foldr combine mempty   @@ -764,7 +840,7 @@ --------------------------------------------------------------------- -- File name manipulators --- | Equality of two 'FilePath's.+-- | Equality of two 'FILEPATH's. --   If you call @System.Directory.canonicalizePath@ --   first this has a much better chance of working. --   Note that this doesn't follow symlinks or DOSNAM~1s.@@ -779,7 +855,7 @@ -- > Posix:   not (equalFilePath "foo" "FOO") -- > Windows: equalFilePath "foo" "FOO" -- > Windows: not (equalFilePath "C:" "C:/")-equalFilePath :: FilePath -> FilePath -> Bool+equalFilePath :: FILEPATH -> FILEPATH -> Bool equalFilePath a b = f a == f b     where         f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x@@ -810,26 +886,26 @@ -- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred" -- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/" -- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"-makeRelative :: FilePath -> FilePath -> FilePath+makeRelative :: FILEPATH -> FILEPATH -> FILEPATH makeRelative root path- | equalFilePath root path = "."- | takeAbs root /= takeAbs path = path- | otherwise = f (dropAbs root) (dropAbs path)-    where-        f "" y = dropWhile isPathSeparator y-        f x y = let (x1,x2) = g x-                    (y1,y2) = g y-                in if equalFilePath x1 y1 then f x2 y2 else path--        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)-            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x+  | equalFilePath root path = singleton _period+  | takeAbs root /= takeAbs path = path+  | otherwise = f (dropAbs root) (dropAbs path)+  where+    f x y+      | null x = dropWhile isPathSeparator y+      | otherwise = let (x1,x2) = g x+                        (y1,y2) = g y+                    in if equalFilePath x1 y1 then f x2 y2 else path+    g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)+      where (a, b) = break isPathSeparator $ dropWhile isPathSeparator x -        -- on windows, need to drop '/' which is kind of absolute, but not a drive-        dropAbs x | hasLeadingPathSeparator x && not (hasDrive x) = tail x-        dropAbs x = dropDrive x+    -- on windows, need to drop '/' which is kind of absolute, but not a drive+    dropAbs x | not (null x) && isPathSeparator (head x) && not (hasDrive x) = tail x+    dropAbs x = dropDrive x -        takeAbs x | hasLeadingPathSeparator x && not (hasDrive x) = [pathSeparator]-        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x+    takeAbs x | not (null x) && isPathSeparator (head x) && not (hasDrive x) = singleton pathSeparator+    takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x  -- | Normalise a file --@@ -863,53 +939,71 @@ -- > Posix:   normalise "/" == "/" -- > Posix:   normalise "bob/fred/." == "bob/fred/" -- > Posix:   normalise "//home" == "/home"-normalise :: FilePath -> FilePath-normalise path = result ++ [pathSeparator | addPathSeparator]-    where-        (drv,pth) = splitDrive path-        result = joinDrive' (normaliseDrive drv) (f pth)+normalise :: FILEPATH -> FILEPATH+normalise filepath =+  result <>+  (if addPathSeparator+       then singleton pathSeparator+       else mempty)+  where+    (drv,pth) = splitDrive filepath -        joinDrive' "" "" = "."-        joinDrive' d p = joinDrive d p+    result = joinDrive' (normaliseDrive drv) (f pth) -        addPathSeparator = isDirPath pth-            && not (hasTrailingPathSeparator result)-            && not (isRelativeDrive drv)+    joinDrive' d p+      = if null d && null p+           then singleton _period+           else joinDrive d p -        isDirPath xs = hasTrailingPathSeparator xs-            || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)+    addPathSeparator = isDirPath filepath+      && not (hasTrailingPathSeparator result)+      && not (isRelativeDrive drv) -        f = joinPath . dropDots . propSep . splitDirectories+    isDirPath xs = hasTrailingPathSeparator xs+        || not (null xs) && last xs == _period+           && hasTrailingPathSeparator (init xs) -        propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs-                       | otherwise = x : xs-        propSep [] = []+    f = joinPath . dropDots . propSep . splitDirectories -        dropDots = filter ("." /=)+    propSep (x:xs)+      | all isPathSeparator x = singleton pathSeparator : xs+      | otherwise                   = x : xs+    propSep [] = [] -normaliseDrive :: FilePath -> FilePath-normaliseDrive "" = ""-normaliseDrive _ | isPosix = [pathSeparator]-normaliseDrive drive = if isJust $ readDriveLetter x2-                       then map toUpper x2-                       else x2-    where-        x2 = map repSlash drive+    dropDots = L.filter (singleton _period /=) +normaliseDrive :: FILEPATH -> FILEPATH+normaliseDrive bs+  | null bs = mempty+  | isPosix = pack [pathSeparator]+  | otherwise = if isJust $ readDriveLetter x2+         then map toUpper x2+         else x2+    where+        x2 = map repSlash bs         repSlash x = if isPathSeparator x then pathSeparator else x  -- Information for validity functions on Windows. See [1].-isBadCharacter :: Char -> Bool-isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""+isBadCHARacter :: CHAR -> Bool+isBadCHARacter x = x >= _nul && x <= _US+  || x `L.elem`+      [ _less+      , _greater+      , _colon+      , _quotedbl+      , _bar+      , _question+      , _asterisk+      ] -badElements :: [FilePath]-badElements =+badElements :: [FILEPATH]+badElements = fmap fromString     ["CON","PRN","AUX","NUL","CLOCK$"     ,"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"     ,"LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"]  --- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,+-- | Is a FILEPATH valid, i.e. could you create a file like it? This function checks for invalid names, --   and invalid characters, but does not check if length limits are exceeded, as these are typically --   filesystem dependent. --@@ -929,21 +1023,22 @@ -- > Windows: isValid "foo\tbar" == False -- > Windows: isValid "nul .txt" == False -- > Windows: isValid " nul.txt" == True-isValid :: FilePath -> Bool-isValid "" = False-isValid x | '\0' `elem` x = False-isValid _ | isPosix = True-isValid path =-        not (any isBadCharacter x2) &&-        not (any f $ splitDirectories x2) &&-        not (isJust (readDriveShare x1) && all isPathSeparator x1) &&-        not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))+isValid :: FILEPATH -> Bool+isValid path+  | null path = False+  | _nul `elem` path = False+  | isPosix = True+  | otherwise =+      not (any isBadCHARacter x2) &&+      not (L.any f $ splitDirectories x2) &&+      not (isJust (readDriveShare x1) && all isPathSeparator x1) &&+      not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))     where-        (x1,x2) = splitDrive path-        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements+      (x1,x2) = splitDrive path+      f x = map toUpper (dropWhileEnd (== _space) $ dropExtensions x) `L.elem` badElements  --- | Take a FilePath and make it valid; does not change already valid FilePaths.+-- | Take a FILEPATH and make it valid; does not change already valid FILEPATHs. -- -- > isValid (makeValid x) -- > isValid x ==> makeValid x == x@@ -959,27 +1054,28 @@ -- > Windows: makeValid "\\\\\\foo" == "\\\\drive" -- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- > Windows: makeValid "nul .txt" == "nul _.txt"-makeValid :: FilePath -> FilePath-makeValid "" = "_"+makeValid :: FILEPATH -> FILEPATH makeValid path-        | isPosix = map (\x -> if x == '\0' then '_' else x) path-        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"-        | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =-            makeValid (drv ++ [pathSeparator] ++ pth)-        | otherwise = joinDrive drv $ validElements $ validChars pth-    where-        (drv,pth) = splitDrive path+  | null path = singleton _underscore+  | isPosix = map (\x -> if x == _nul then _underscore else x) path+  | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv <> fromString "drive"+  | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =+      makeValid (drv <> singleton pathSeparator <> pth)+  | otherwise = joinDrive drv $ validElements $ validCHARs pth -        validChars = map f-        f x = if isBadCharacter x then '_' else x+  where+    (drv,pth) = splitDrive path -        validElements x = joinPath $ map g $ splitPath x-        g x = h a ++ b-            where (a,b) = break isPathSeparator x-        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x-            where (a,b) = splitExtensions x+    validCHARs = map f+    f x = if isBadCHARacter x then _underscore else x +    validElements = joinPath . fmap g . splitPath+    g x = h a <> b+        where (a,b) = break isPathSeparator x+    h x = if map toUpper (dropWhileEnd (== _space) a) `L.elem` badElements then snoc a _underscore  <.> b else x+        where (a,b) = splitExtensions x + -- | Is a path relative, or is it fixed to the root? -- -- > Windows: isRelative "path\\test" == True@@ -1002,7 +1098,7 @@ -- * "A UNC name of any format [is never relative]." -- -- * "You cannot use the "\\?\" prefix with a relative path."-isRelative :: FilePath -> Bool+isRelative :: FILEPATH -> Bool isRelative x = null drive || isRelativeDrive drive     where drive = takeDrive x @@ -1011,7 +1107,7 @@ -- From [1]: "If a file name begins with only a disk designator but not the -- backslash after the colon, it is interpreted as a relative path to the -- current directory on the drive with the specified letter."-isRelativeDrive :: String -> Bool+isRelativeDrive :: STRING -> Bool isRelativeDrive x =     maybe False (not . hasTrailingPathSeparator . fst) (readDriveLetter x) @@ -1019,9 +1115,10 @@ -- | @not . 'isRelative'@ -- -- > isAbsolute x == not (isRelative x)-isAbsolute :: FilePath -> Bool+isAbsolute :: FILEPATH -> Bool isAbsolute = not . isRelative +#ifndef OS_PATH  ----------------------------------------------------------------------------- -- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])@@ -1045,4 +1142,109 @@ -- Nothing if the list did not end with the suffix given, or Just the list -- before the suffix, if it does. stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]-stripSuffix xs ys = fmap reverse $ stripPrefix (reverse xs) (reverse ys)+stripSuffix xs ys = reverse P.<$> stripPrefix (reverse xs) (reverse ys)+++unsnoc :: [a] -> Maybe ([a], a)+unsnoc [] = Nothing+unsnoc xs = Just (init xs, last xs)+++_period, _quotedbl, _backslash, _slash, _question, _U, _N, _C, _colon, _semicolon, _US, _less, _greater, _bar, _asterisk, _nul, _space, _underscore :: Char+_period = '.'+_quotedbl = '"'+_slash = '/'+_backslash = '\\'+_question = '?'+_colon = ':'+_semicolon = ';'+_U = 'U'+_N = 'N'+_C = 'C'+_US = '\US'+_less = '<'+_greater = '>'+_bar = '|'+_asterisk = '*'+_nul = '\NUL'+_space = ' '+_underscore = '_'++singleton :: Char -> String+singleton c = [c]++pack :: String -> String+pack = id+++unpack :: String -> String+unpack = id+++snoc :: String -> Char -> String+{- HLINT ignore "Redundant lambda" -}+snoc str = \c -> str <> [c]++#else+#ifdef WINDOWS+fromString :: P.String -> STRING+fromString str = P.either (P.error . P.show) P.id $ unsafePerformIO $ do+  r <- try @SomeException $ GHC.withCStringLen (mkUTF16le ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr+  evaluate $ force $ first displayException r+#else+fromString :: P.String -> STRING+fromString str = P.either (P.error . P.show) P.id $ unsafePerformIO $ do+  r <- try @SomeException $ GHC.withCStringLen (mkUTF8 ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr+  evaluate $ force $ first displayException r+#endif++_a, _z, _A, _Z, _period, _quotedbl, _backslash, _slash, _question, _U, _N, _C, _colon, _semicolon, _US, _less, _greater, _bar, _asterisk, _nul, _space, _underscore :: CHAR+_a = 0x61+_z = 0x7a+_A = 0x41+_Z = 0x5a+_period = 0x2e+_quotedbl = 0x22+_slash = 0x2f+_backslash = 0x5c+_question = 0x3f+_colon = 0x3a+_semicolon = 0x3b+_U = 0x55+_N = 0x4e+_C = 0x43+_US = 0x1f+_less = 0x3c+_greater = 0x3e+_bar = 0x7c+_asterisk = 0x2a+_nul = 0x00+_space = 0x20+_underscore = 0x5f++isAsciiUpper :: CHAR -> Bool+isAsciiUpper w = _A <= w && w <= _Z++isAsciiLower :: CHAR -> Bool+isAsciiLower w = _a <= w && w <= _z++----------------------------------------------------------------++toUpper :: CHAR -> CHAR+-- charToWord16 should be safe here, since C.toUpper doesn't go beyond Word16 maxbound+toUpper = charToWord . C.toUpper . wordToChar++toLower :: CHAR -> CHAR+-- charToWord16 should be safe here, since C.toLower doesn't go beyond Word16 maxbound+toLower = charToWord . C.toLower . wordToChar+++-- | Total conversion to char.+wordToChar :: CHAR -> Char+wordToChar = C.chr . fromIntegral++-- | This is unsafe and clamps at Word16 maxbound.+charToWord :: Char -> CHAR+charToWord = fromIntegral . C.ord++#endif
System/FilePath/Posix.hs view
@@ -1,1048 +1,7 @@----{-# LANGUAGE PatternGuards #-}---- This template expects CPP definitions for:---     MODULE_NAME = Posix | Windows---     IS_WINDOWS  = False | True---- |--- Module      :  System.FilePath.MODULE_NAME--- Copyright   :  (c) Neil Mitchell 2005-2014--- License     :  BSD3------ Maintainer  :  ndmitchell@gmail.com--- Stability   :  stable--- Portability :  portable------ A library for 'FilePath' manipulations, using MODULE_NAME style paths on--- all platforms. Importing "System.FilePath" is usually better.------ Given the example 'FilePath': @\/directory\/file.ext@------ We can use the following functions to extract pieces.------ * 'takeFileName' gives @\"file.ext\"@------ * 'takeDirectory' gives @\"\/directory\"@------ * 'takeExtension' gives @\".ext\"@------ * 'dropExtension' gives @\"\/directory\/file\"@------ * 'takeBaseName' gives @\"file\"@------ And we could have built an equivalent path with the following expressions:------ * @\"\/directory\" '</>' \"file.ext\"@.------ * @\"\/directory\/file" '<.>' \"ext\"@.------ * @\"\/directory\/file.txt" '-<.>' \"ext\"@.------ Each function in this module is documented with several examples,--- which are also used as tests.------ Here are a few examples of using the @filepath@ functions together:------ /Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:------ @['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@------ /Example 2:/ Download a file from @url@ and save it to disk:------ @do let file = 'makeValid' url---   System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@------ /Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:------ @'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@------ References:--- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)-module System.FilePath.Posix-    (-    -- * Separator predicates-    FilePath,-    pathSeparator, pathSeparators, isPathSeparator,-    searchPathSeparator, isSearchPathSeparator,-    extSeparator, isExtSeparator,--    -- * @$PATH@ methods-    splitSearchPath, getSearchPath,--    -- * Extension functions-    splitExtension,-    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),-    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,-    stripExtension,--    -- * Filename\/directory functions-    splitFileName,-    takeFileName, replaceFileName, dropFileName,-    takeBaseName, replaceBaseName,-    takeDirectory, replaceDirectory,-    combine, (</>),-    splitPath, joinPath, splitDirectories,--    -- * Drive functions-    splitDrive, joinDrive,-    takeDrive, hasDrive, dropDrive, isDrive,--    -- * Trailing slash functions-    hasTrailingPathSeparator,-    addTrailingPathSeparator,-    dropTrailingPathSeparator,--    -- * File name manipulations-    normalise, equalFilePath,-    makeRelative,-    isRelative, isAbsolute,-    isValid, makeValid-    )-    where--import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)-import Data.Maybe(isJust)-import Data.List(stripPrefix, isSuffixOf)--import System.Environment(getEnv)---infixr 7  <.>, -<.>-infixr 5  </>------------------------------------------------------------------------------ Platform Abstraction Methods (private)---- | Is the operating system Unix or Linux like-isPosix :: Bool-isPosix = not isWindows---- | Is the operating system Windows like-isWindows :: Bool-isWindows = False--------------------------------------------------------------------------- The basic functions---- | The character that separates directories. In the case where more than---   one character is possible, 'pathSeparator' is the \'ideal\' one.------ > Windows: pathSeparator == '\\'--- > Posix:   pathSeparator ==  '/'--- > isPathSeparator pathSeparator-pathSeparator :: Char-pathSeparator = if isWindows then '\\' else '/'---- | The list of all possible separators.------ > Windows: pathSeparators == ['\\', '/']--- > Posix:   pathSeparators == ['/']--- > pathSeparator `elem` pathSeparators-pathSeparators :: [Char]-pathSeparators = if isWindows then "\\/" else "/"---- | Rather than using @(== 'pathSeparator')@, use this. Test if something---   is a path separator.------ > isPathSeparator a == (a `elem` pathSeparators)-isPathSeparator :: Char -> Bool-isPathSeparator '/' = True-isPathSeparator '\\' = isWindows-isPathSeparator _ = False----- | The character that is used to separate the entries in the $PATH environment variable.------ > Windows: searchPathSeparator == ';'--- > Posix:   searchPathSeparator == ':'-searchPathSeparator :: Char-searchPathSeparator = if isWindows then ';' else ':'---- | Is the character a file separator?------ > isSearchPathSeparator a == (a == searchPathSeparator)-isSearchPathSeparator :: Char -> Bool-isSearchPathSeparator = (== searchPathSeparator)----- | File extension character------ > extSeparator == '.'-extSeparator :: Char-extSeparator = '.'---- | Is the character an extension character?------ > isExtSeparator a == (a == extSeparator)-isExtSeparator :: Char -> Bool-isExtSeparator = (== extSeparator)--------------------------------------------------------------------------- Path methods (environment $PATH)---- | Take a string, split it on the 'searchPathSeparator' character.---   Blank items are ignored on Windows, and converted to @.@ on Posix.---   On Windows path elements are stripped of quotes.------   Follows the recommendations in---   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>------ > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]--- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]--- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]--- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]--- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]-splitSearchPath :: String -> [FilePath]-splitSearchPath = f-    where-    f xs = case break isSearchPathSeparator xs of-           (pre, []    ) -> g pre-           (pre, _:post) -> g pre ++ f post--    g "" = ["." | isPosix]-    g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]-    g x = [x]----- | Get a list of 'FilePath's in the $PATH variable.-getSearchPath :: IO [FilePath]-getSearchPath = fmap splitSearchPath (getEnv "PATH")--------------------------------------------------------------------------- Extension methods---- | Split on the extension. 'addExtension' is the inverse.------ > splitExtension "/directory/path.ext" == ("/directory/path",".ext")--- > uncurry (++) (splitExtension x) == x--- > Valid x => uncurry addExtension (splitExtension x) == x--- > splitExtension "file.txt" == ("file",".txt")--- > splitExtension "file" == ("file","")--- > splitExtension "file/file.txt" == ("file/file",".txt")--- > splitExtension "file.txt/boris" == ("file.txt/boris","")--- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")--- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")--- > splitExtension "file/path.txt/" == ("file/path.txt/","")-splitExtension :: FilePath -> (String, String)-splitExtension x = case nameDot of-                       "" -> (x,"")-                       _ -> (dir ++ init nameDot, extSeparator : ext)-    where-        (dir,file) = splitFileName_ x-        (nameDot,ext) = breakEnd isExtSeparator file---- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.------ > takeExtension "/directory/path.ext" == ".ext"--- > takeExtension x == snd (splitExtension x)--- > Valid x => takeExtension (addExtension x "ext") == ".ext"--- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"-takeExtension :: FilePath -> String-takeExtension = snd . splitExtension---- | Remove the current extension and add another, equivalent to 'replaceExtension'.------ > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"--- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"--- > "foo.o" -<.> "c" == "foo.c"-(-<.>) :: FilePath -> String -> FilePath-(-<.>) = replaceExtension---- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.------ > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"--- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"--- > replaceExtension "file.txt" ".bob" == "file.bob"--- > replaceExtension "file.txt" "bob" == "file.bob"--- > replaceExtension "file" ".bob" == "file.bob"--- > replaceExtension "file.txt" "" == "file"--- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"--- > replaceExtension x y == addExtension (dropExtension x) y-replaceExtension :: FilePath -> String -> FilePath-replaceExtension x y = dropExtension x <.> y---- | Add an extension, even if there is already one there, equivalent to 'addExtension'.------ > "/directory/path" <.> "ext" == "/directory/path.ext"--- > "/directory/path" <.> ".ext" == "/directory/path.ext"-(<.>) :: FilePath -> String -> FilePath-(<.>) = addExtension---- | Remove last extension, and the \".\" preceding it.------ > dropExtension "/directory/path.ext" == "/directory/path"--- > dropExtension x == fst (splitExtension x)-dropExtension :: FilePath -> FilePath-dropExtension = fst . splitExtension---- | Add an extension, even if there is already one there, equivalent to '<.>'.------ > addExtension "/directory/path" "ext" == "/directory/path.ext"--- > addExtension "file.txt" "bib" == "file.txt.bib"--- > addExtension "file." ".bib" == "file..bib"--- > addExtension "file" ".bib" == "file.bib"--- > addExtension "/" "x" == "/.x"--- > addExtension x "" == x--- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"--- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"-addExtension :: FilePath -> String -> FilePath-addExtension file "" = file-addExtension file xs@(x:_) = joinDrive a res-    where-        res = if isExtSeparator x then b ++ xs-              else b ++ [extSeparator] ++ xs--        (a,b) = splitDrive file---- | Does the given filename have an extension?------ > hasExtension "/directory/path.ext" == True--- > hasExtension "/directory/path" == False--- > null (takeExtension x) == not (hasExtension x)-hasExtension :: FilePath -> Bool-hasExtension = any isExtSeparator . takeFileName----- | Does the given filename have the specified extension?------ > "png" `isExtensionOf` "/directory/file.png" == True--- > ".png" `isExtensionOf` "/directory/file.png" == True--- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True--- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False--- > "png" `isExtensionOf` "/directory/file.png.jpg" == False--- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False-isExtensionOf :: String -> FilePath -> Bool-isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions-isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions---- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.---   Returns 'Nothing' if the FilePath does not have the given extension, or---   'Just' and the part before the extension if it does.------   This function can be more predictable than 'dropExtensions', especially if the filename---   might itself contain @.@ characters.------ > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"--- > stripExtension "hi.o" "foo.x.hs.o" == Nothing--- > dropExtension x == fromJust (stripExtension (takeExtension x) x)--- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)--- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"--- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."--- > stripExtension "baz"  "foo.bar"  == Nothing--- > stripExtension "bar"  "foobar"   == Nothing--- > stripExtension ""     x          == Just x-stripExtension :: String -> FilePath -> Maybe FilePath-stripExtension []        path = Just path-stripExtension ext@(x:_) path = stripSuffix dotExt path-    where dotExt = if isExtSeparator x then ext else '.':ext----- | Split on all extensions.------ > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")--- > splitExtensions "file.tar.gz" == ("file",".tar.gz")--- > uncurry (++) (splitExtensions x) == x--- > Valid x => uncurry addExtension (splitExtensions x) == x--- > splitExtensions "file.tar.gz" == ("file",".tar.gz")-splitExtensions :: FilePath -> (FilePath, String)-splitExtensions x = (a ++ c, d)-    where-        (a,b) = splitFileName_ x-        (c,d) = break isExtSeparator b---- | Drop all extensions.------ > dropExtensions "/directory/path.ext" == "/directory/path"--- > dropExtensions "file.tar.gz" == "file"--- > not $ hasExtension $ dropExtensions x--- > not $ any isExtSeparator $ takeFileName $ dropExtensions x-dropExtensions :: FilePath -> FilePath-dropExtensions = fst . splitExtensions---- | Get all extensions.------ > takeExtensions "/directory/path.ext" == ".ext"--- > takeExtensions "file.tar.gz" == ".tar.gz"-takeExtensions :: FilePath -> String-takeExtensions = snd . splitExtensions----- | Replace all extensions of a file with a new extension. Note---   that 'replaceExtension' and 'addExtension' both work for adding---   multiple extensions, so only required when you need to drop---   all extensions first.------ > replaceExtensions "file.fred.bob" "txt" == "file.txt"--- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"-replaceExtensions :: FilePath -> String -> FilePath-replaceExtensions x y = dropExtensions x <.> y---------------------------------------------------------------------------- Drive methods---- | Is the given character a valid drive letter?--- only a-z and A-Z are letters, not isAlpha which is more unicodey-isLetter :: Char -> Bool-isLetter x = isAsciiLower x || isAsciiUpper x----- | Split a path into a drive and a path.---   On Posix, \/ is a Drive.------ > uncurry (++) (splitDrive x) == x--- > Windows: splitDrive "file" == ("","file")--- > Windows: splitDrive "c:/file" == ("c:/","file")--- > Windows: splitDrive "c:\\file" == ("c:\\","file")--- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")--- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")--- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")--- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")--- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")--- > Windows: splitDrive "/d" == ("","/d")--- > Posix:   splitDrive "/test" == ("/","test")--- > Posix:   splitDrive "//test" == ("//","test")--- > Posix:   splitDrive "test/file" == ("","test/file")--- > Posix:   splitDrive "file" == ("","file")-splitDrive :: FilePath -> (FilePath, FilePath)-splitDrive x | isPosix = span (== '/') x-splitDrive x | Just y <- readDriveLetter x = y-splitDrive x | Just y <- readDriveUNC x = y-splitDrive x | Just y <- readDriveShare x = y-splitDrive x = ("",x)--addSlash :: FilePath -> FilePath -> (FilePath, FilePath)-addSlash a xs = (a++c,d)-    where (c,d) = span isPathSeparator xs---- See [1].--- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"-readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)-readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =-    case map toUpper xs of-        ('U':'N':'C':s4:_) | isPathSeparator s4 ->-            let (a,b) = readDriveShareName (drop 4 xs)-            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)-        _ -> case readDriveLetter xs of-                 -- Extended-length path.-                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)-                 Nothing -> Nothing-readDriveUNC _ = Nothing--{- c:\ -}-readDriveLetter :: String -> Maybe (FilePath, FilePath)-readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)-readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)-readDriveLetter _ = Nothing--{- \\sharename\ -}-readDriveShare :: String -> Maybe (FilePath, FilePath)-readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =-        Just (s1:s2:a,b)-    where (a,b) = readDriveShareName xs-readDriveShare _ = Nothing--{- assume you have already seen \\ -}-{- share\bob -> "share\", "bob" -}-readDriveShareName :: String -> (FilePath, FilePath)-readDriveShareName name = addSlash a b-    where (a,b) = break isPathSeparator name------ | Join a drive and the rest of the path.------ > Valid x => uncurry joinDrive (splitDrive x) == x--- > Windows: joinDrive "C:" "foo" == "C:foo"--- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"--- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"--- > Windows: joinDrive "/:" "foo" == "/:\\foo"-joinDrive :: FilePath -> FilePath -> FilePath-joinDrive = combineAlways---- | Get the drive from a filepath.------ > takeDrive x == fst (splitDrive x)-takeDrive :: FilePath -> FilePath-takeDrive = fst . splitDrive---- | Delete the drive, if it exists.------ > dropDrive x == snd (splitDrive x)-dropDrive :: FilePath -> FilePath-dropDrive = snd . splitDrive---- | Does a path have a drive.------ > not (hasDrive x) == null (takeDrive x)--- > Posix:   hasDrive "/foo" == True--- > Windows: hasDrive "C:\\foo" == True--- > Windows: hasDrive "C:foo" == True--- >          hasDrive "foo" == False--- >          hasDrive "" == False-hasDrive :: FilePath -> Bool-hasDrive = not . null . takeDrive----- | Is an element a drive------ > Posix:   isDrive "/" == True--- > Posix:   isDrive "/foo" == False--- > Windows: isDrive "C:\\" == True--- > Windows: isDrive "C:\\foo" == False--- >          isDrive "" == False-isDrive :: FilePath -> Bool-isDrive x = not (null x) && null (dropDrive x)--------------------------------------------------------------------------- Operations on a filepath, as a list of directories---- | Split a filename into directory and file. '</>' is the inverse.---   The first component will often end with a trailing slash.------ > splitFileName "/directory/file.ext" == ("/directory/","file.ext")--- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"--- > Valid x => isValid (fst (splitFileName x))--- > splitFileName "file/bob.txt" == ("file/", "bob.txt")--- > splitFileName "file/" == ("file/", "")--- > splitFileName "bob" == ("./", "bob")--- > Posix:   splitFileName "/" == ("/","")--- > Windows: splitFileName "c:" == ("c:","")-splitFileName :: FilePath -> (String, String)-splitFileName x = (if null dir then "./" else dir, name)-    where-        (dir, name) = splitFileName_ x---- version of splitFileName where, if the FilePath has no directory--- component, the returned directory is "" rather than "./".  This--- is used in cases where we are going to combine the returned--- directory to make a valid FilePath, and having a "./" appear would--- look strange and upset simple equality properties.  See--- e.g. replaceFileName.-splitFileName_ :: FilePath -> (String, String)-splitFileName_ x = (drv ++ dir, file)-    where-        (drv,pth) = splitDrive x-        (dir,file) = breakEnd isPathSeparator pth---- | Set the filename.------ > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"--- > Valid x => replaceFileName x (takeFileName x) == x-replaceFileName :: FilePath -> String -> FilePath-replaceFileName x y = a </> y where (a,_) = splitFileName_ x---- | Drop the filename. Unlike 'takeDirectory', this function will leave---   a trailing path separator on the directory.------ > dropFileName "/directory/file.ext" == "/directory/"--- > dropFileName x == fst (splitFileName x)-dropFileName :: FilePath -> FilePath-dropFileName = fst . splitFileName----- | Get the file name.------ > takeFileName "/directory/file.ext" == "file.ext"--- > takeFileName "test/" == ""--- > takeFileName x `isSuffixOf` x--- > takeFileName x == snd (splitFileName x)--- > Valid x => takeFileName (replaceFileName x "fred") == "fred"--- > Valid x => takeFileName (x </> "fred") == "fred"--- > Valid x => isRelative (takeFileName x)-takeFileName :: FilePath -> FilePath-takeFileName = snd . splitFileName---- | Get the base name, without an extension or path.------ > takeBaseName "/directory/file.ext" == "file"--- > takeBaseName "file/test.txt" == "test"--- > takeBaseName "dave.ext" == "dave"--- > takeBaseName "" == ""--- > takeBaseName "test" == "test"--- > takeBaseName (addTrailingPathSeparator x) == ""--- > takeBaseName "file/file.tar.gz" == "file.tar"-takeBaseName :: FilePath -> String-takeBaseName = dropExtension . takeFileName---- | Set the base name.------ > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"--- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"--- > replaceBaseName "fred" "bill" == "bill"--- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"--- > Valid x => replaceBaseName x (takeBaseName x) == x-replaceBaseName :: FilePath -> String -> FilePath-replaceBaseName pth nam = combineAlways a (nam <.> ext)-    where-        (a,b) = splitFileName_ pth-        ext = takeExtension b---- | Is an item either a directory or the last character a path separator?------ > hasTrailingPathSeparator "test" == False--- > hasTrailingPathSeparator "test/" == True-hasTrailingPathSeparator :: FilePath -> Bool-hasTrailingPathSeparator "" = False-hasTrailingPathSeparator x = isPathSeparator (last x)---hasLeadingPathSeparator :: FilePath -> Bool-hasLeadingPathSeparator "" = False-hasLeadingPathSeparator x = isPathSeparator (head x)----- | Add a trailing file path separator if one is not already present.------ > hasTrailingPathSeparator (addTrailingPathSeparator x)--- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x--- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"-addTrailingPathSeparator :: FilePath -> FilePath-addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]----- | Remove any trailing path separators------ > dropTrailingPathSeparator "file/test/" == "file/test"--- >           dropTrailingPathSeparator "/" == "/"--- > Windows:  dropTrailingPathSeparator "\\" == "\\"--- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x-dropTrailingPathSeparator :: FilePath -> FilePath-dropTrailingPathSeparator x =-    if hasTrailingPathSeparator x && not (isDrive x)-    then let x' = dropWhileEnd isPathSeparator x-         in if null x' then [last x] else x'-    else x----- | Get the directory name, move up one level.------ >           takeDirectory "/directory/other.ext" == "/directory"--- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."--- >           takeDirectory "foo" == "."--- >           takeDirectory "/" == "/"--- >           takeDirectory "/foo" == "/"--- >           takeDirectory "/foo/bar/baz" == "/foo/bar"--- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"--- >           takeDirectory "foo/bar/baz" == "foo/bar"--- > Windows:  takeDirectory "foo\\bar" == "foo"--- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"--- > Windows:  takeDirectory "C:\\" == "C:\\"-takeDirectory :: FilePath -> FilePath-takeDirectory = dropTrailingPathSeparator . dropFileName---- | Set the directory, keeping the filename the same.------ > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"--- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x-replaceDirectory :: FilePath -> String -> FilePath-replaceDirectory x dir = combineAlways dir (takeFileName x)----- | An alias for '</>'.-combine :: FilePath -> FilePath -> FilePath-combine a b | hasLeadingPathSeparator b || hasDrive b = b-            | otherwise = combineAlways a b---- | Combine two paths, assuming rhs is NOT absolute.-combineAlways :: FilePath -> FilePath -> FilePath-combineAlways a b | null a = b-                  | null b = a-                  | hasTrailingPathSeparator a = a ++ b-                  | otherwise = case a of-                      [a1,':'] | isWindows && isLetter a1 -> a ++ b-                      _ -> a ++ [pathSeparator] ++ b----- | Combine two paths with a path separator.---   If the second path starts with a path separator or a drive letter, then it returns the second.---   The intention is that @readFile (dir '</>' file)@ will access the same file as---   @setCurrentDirectory dir; readFile file@.------ > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"--- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"--- >          "directory" </> "/file.ext" == "/file.ext"--- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x------   Combined:------ > Posix:   "/" </> "test" == "/test"--- > Posix:   "home" </> "bob" == "home/bob"--- > Posix:   "x:" </> "foo" == "x:/foo"--- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"--- > Windows: "home" </> "bob" == "home\\bob"------   Not combined:------ > Posix:   "home" </> "/bob" == "/bob"--- > Windows: "home" </> "C:\\bob" == "C:\\bob"------   Not combined (tricky):------   On Windows, if a filepath starts with a single slash, it is relative to the---   root of the current drive. In [1], this is (confusingly) referred to as an---   absolute path.---   The current behavior of '</>' is to never combine these forms.------ > Windows: "home" </> "/bob" == "/bob"--- > Windows: "home" </> "\\bob" == "\\bob"--- > Windows: "C:\\home" </> "\\bob" == "\\bob"------   On Windows, from [1]: "If a file name begins with only a disk designator---   but not the backslash after the colon, it is interpreted as a relative path---   to the current directory on the drive with the specified letter."---   The current behavior of '</>' is to never combine these forms.------ > Windows: "D:\\foo" </> "C:bar" == "C:bar"--- > Windows: "C:\\foo" </> "C:bar" == "C:bar"-(</>) :: FilePath -> FilePath -> FilePath-(</>) = combine----- | Split a path by the directory separator.------ > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]--- > concat (splitPath x) == x--- > splitPath "test//item/" == ["test//","item/"]--- > splitPath "test/item/file" == ["test/","item/","file"]--- > splitPath "" == []--- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]--- > Posix:   splitPath "/file/test" == ["/","file/","test"]-splitPath :: FilePath -> [FilePath]-splitPath x = [drive | drive /= ""] ++ f path-    where-        (drive,path) = splitDrive x--        f "" = []-        f y = (a++c) : f d-            where-                (a,b) = break isPathSeparator y-                (c,d) = span  isPathSeparator b---- | Just as 'splitPath', but don't add the trailing slashes to each element.------ >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]--- >          splitDirectories "test/file" == ["test","file"]--- >          splitDirectories "/test/file" == ["/","test","file"]--- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]--- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x--- >          splitDirectories "" == []--- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]--- >          splitDirectories "/test///file" == ["/","test","file"]-splitDirectories :: FilePath -> [FilePath]-splitDirectories = map dropTrailingPathSeparator . splitPath----- | Join path elements back together.------ > joinPath a == foldr (</>) "" a--- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"--- > Valid x => joinPath (splitPath x) == x--- > joinPath [] == ""--- > Posix: joinPath ["test","file","path"] == "test/file/path"-joinPath :: [FilePath] -> FilePath--- Note that this definition on c:\\c:\\, join then split will give c:\\.-joinPath = foldr combine ""------------------------------------------------------------------------------- File name manipulators---- | Equality of two 'FilePath's.---   If you call @System.Directory.canonicalizePath@---   first this has a much better chance of working.---   Note that this doesn't follow symlinks or DOSNAM~1s.------ Similar to 'normalise', this does not expand @".."@, because of symlinks.------ >          x == y ==> equalFilePath x y--- >          normalise x == normalise y ==> equalFilePath x y--- >          equalFilePath "foo" "foo/"--- >          not (equalFilePath "/a/../c" "/c")--- >          not (equalFilePath "foo" "/foo")--- > Posix:   not (equalFilePath "foo" "FOO")--- > Windows: equalFilePath "foo" "FOO"--- > Windows: not (equalFilePath "C:" "C:/")-equalFilePath :: FilePath -> FilePath -> Bool-equalFilePath a b = f a == f b-    where-        f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x-            | otherwise = dropTrailingPathSeparator $ normalise x----- | Contract a filename, based on a relative path. Note that the resulting path---   will never introduce @..@ paths, as the presence of symlinks means @..\/b@---   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see---   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.------   The corresponding @makeAbsolute@ function can be found in---   @System.Directory@.------ >          makeRelative "/directory" "/directory/file.ext" == "file.ext"--- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x--- >          makeRelative x x == "."--- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x--- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"--- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"--- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"--- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"--- > Windows: makeRelative "/Home" "/home/bob" == "bob"--- > Windows: makeRelative "/" "//" == "//"--- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"--- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"--- > Posix:   makeRelative "/fred" "bob" == "bob"--- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"--- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"--- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"-makeRelative :: FilePath -> FilePath -> FilePath-makeRelative root path- | equalFilePath root path = "."- | takeAbs root /= takeAbs path = path- | otherwise = f (dropAbs root) (dropAbs path)-    where-        f "" y = dropWhile isPathSeparator y-        f x y = let (x1,x2) = g x-                    (y1,y2) = g y-                in if equalFilePath x1 y1 then f x2 y2 else path--        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)-            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x--        -- on windows, need to drop '/' which is kind of absolute, but not a drive-        dropAbs x | hasLeadingPathSeparator x && not (hasDrive x) = tail x-        dropAbs x = dropDrive x--        takeAbs x | hasLeadingPathSeparator x && not (hasDrive x) = [pathSeparator]-        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x---- | Normalise a file------ * \/\/ outside of the drive can be made blank------ * \/ -> 'pathSeparator'------ * .\/ -> \"\"------ Does not remove @".."@, because of symlinks.------ > Posix:   normalise "/file/\\test////" == "/file/\\test/"--- > Posix:   normalise "/file/./test" == "/file/test"--- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"--- > Posix:   normalise "../bob/fred/" == "../bob/fred/"--- > Posix:   normalise "/a/../c" == "/a/../c"--- > Posix:   normalise "./bob/fred/" == "bob/fred/"--- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"--- > Windows: normalise "c:\\" == "C:\\"--- > Windows: normalise "C:.\\" == "C:"--- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"--- > Windows: normalise "//server/test" == "\\\\server\\test"--- > Windows: normalise "c:/file" == "C:\\file"--- > Windows: normalise "/file" == "\\file"--- > Windows: normalise "\\" == "\\"--- > Windows: normalise "/./" == "\\"--- >          normalise "." == "."--- > Posix:   normalise "./" == "./"--- > Posix:   normalise "./." == "./"--- > Posix:   normalise "/./" == "/"--- > Posix:   normalise "/" == "/"--- > Posix:   normalise "bob/fred/." == "bob/fred/"--- > Posix:   normalise "//home" == "/home"-normalise :: FilePath -> FilePath-normalise path = result ++ [pathSeparator | addPathSeparator]-    where-        (drv,pth) = splitDrive path-        result = joinDrive' (normaliseDrive drv) (f pth)--        joinDrive' "" "" = "."-        joinDrive' d p = joinDrive d p--        addPathSeparator = isDirPath pth-            && not (hasTrailingPathSeparator result)-            && not (isRelativeDrive drv)--        isDirPath xs = hasTrailingPathSeparator xs-            || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)--        f = joinPath . dropDots . propSep . splitDirectories--        propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs-                       | otherwise = x : xs-        propSep [] = []--        dropDots = filter ("." /=)--normaliseDrive :: FilePath -> FilePath-normaliseDrive "" = ""-normaliseDrive _ | isPosix = [pathSeparator]-normaliseDrive drive = if isJust $ readDriveLetter x2-                       then map toUpper x2-                       else x2-    where-        x2 = map repSlash drive--        repSlash x = if isPathSeparator x then pathSeparator else x---- Information for validity functions on Windows. See [1].-isBadCharacter :: Char -> Bool-isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""--badElements :: [FilePath]-badElements =-    ["CON","PRN","AUX","NUL","CLOCK$"-    ,"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"-    ,"LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"]----- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,---   and invalid characters, but does not check if length limits are exceeded, as these are typically---   filesystem dependent.------ >          isValid "" == False--- >          isValid "\0" == False--- > Posix:   isValid "/random_ path:*" == True--- > Posix:   isValid x == not (null x)--- > Windows: isValid "c:\\test" == True--- > Windows: isValid "c:\\test:of_test" == False--- > Windows: isValid "test*" == False--- > Windows: isValid "c:\\test\\nul" == False--- > Windows: isValid "c:\\test\\prn.txt" == False--- > Windows: isValid "c:\\nul\\file" == False--- > Windows: isValid "\\\\" == False--- > Windows: isValid "\\\\\\foo" == False--- > Windows: isValid "\\\\?\\D:file" == False--- > Windows: isValid "foo\tbar" == False--- > Windows: isValid "nul .txt" == False--- > Windows: isValid " nul.txt" == True-isValid :: FilePath -> Bool-isValid "" = False-isValid x | '\0' `elem` x = False-isValid _ | isPosix = True-isValid path =-        not (any isBadCharacter x2) &&-        not (any f $ splitDirectories x2) &&-        not (isJust (readDriveShare x1) && all isPathSeparator x1) &&-        not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))-    where-        (x1,x2) = splitDrive path-        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements----- | Take a FilePath and make it valid; does not change already valid FilePaths.------ > isValid (makeValid x)--- > isValid x ==> makeValid x == x--- > makeValid "" == "_"--- > makeValid "file\0name" == "file_name"--- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"--- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"--- > Windows: makeValid "test*" == "test_"--- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"--- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"--- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"--- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"--- > Windows: makeValid "\\\\\\foo" == "\\\\drive"--- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"--- > Windows: makeValid "nul .txt" == "nul _.txt"-makeValid :: FilePath -> FilePath-makeValid "" = "_"-makeValid path-        | isPosix = map (\x -> if x == '\0' then '_' else x) path-        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"-        | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =-            makeValid (drv ++ [pathSeparator] ++ pth)-        | otherwise = joinDrive drv $ validElements $ validChars pth-    where-        (drv,pth) = splitDrive path--        validChars = map f-        f x = if isBadCharacter x then '_' else x--        validElements x = joinPath $ map g $ splitPath x-        g x = h a ++ b-            where (a,b) = break isPathSeparator x-        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x-            where (a,b) = splitExtensions x----- | Is a path relative, or is it fixed to the root?------ > Windows: isRelative "path\\test" == True--- > Windows: isRelative "c:\\test" == False--- > Windows: isRelative "c:test" == True--- > Windows: isRelative "c:\\" == False--- > Windows: isRelative "c:/" == False--- > Windows: isRelative "c:" == True--- > Windows: isRelative "\\\\foo" == False--- > Windows: isRelative "\\\\?\\foo" == False--- > Windows: isRelative "\\\\?\\UNC\\foo" == False--- > Windows: isRelative "/foo" == True--- > Windows: isRelative "\\foo" == True--- > Posix:   isRelative "test/path" == True--- > Posix:   isRelative "/test" == False--- > Posix:   isRelative "/" == False------ According to [1]:------ * "A UNC name of any format [is never relative]."------ * "You cannot use the "\\?\" prefix with a relative path."-isRelative :: FilePath -> Bool-isRelative x = null drive || isRelativeDrive drive-    where drive = takeDrive x---{- c:foo -}--- From [1]: "If a file name begins with only a disk designator but not the--- backslash after the colon, it is interpreted as a relative path to the--- current directory on the drive with the specified letter."-isRelativeDrive :: String -> Bool-isRelativeDrive x =-    maybe False (not . hasTrailingPathSeparator . fst) (readDriveLetter x)----- | @not . 'isRelative'@------ > isAbsolute x == not (isRelative x)-isAbsolute :: FilePath -> Bool-isAbsolute = not . isRelative----------------------------------------------------------------------------------- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])--- Note that Data.List.dropWhileEnd is only available in base >= 4.5.-dropWhileEnd :: (a -> Bool) -> [a] -> [a]-dropWhileEnd p = reverse . dropWhile p . reverse---- takeWhileEnd (>2) [1,2,3,4,1,2,3,4] == [3,4])-takeWhileEnd :: (a -> Bool) -> [a] -> [a]-takeWhileEnd p = reverse . takeWhile p . reverse---- spanEnd (>2) [1,2,3,4,1,2,3,4] = ([1,2,3,4,1,2], [3,4])-spanEnd :: (a -> Bool) -> [a] -> ([a], [a])-spanEnd p xs = (dropWhileEnd p xs, takeWhileEnd p xs)---- breakEnd (< 2) [1,2,3,4,1,2,3,4] == ([1,2,3,4,1],[2,3,4])-breakEnd :: (a -> Bool) -> [a] -> ([a], [a])-breakEnd p = spanEnd (not . p)---- | The stripSuffix function drops the given suffix from a list. It returns--- Nothing if the list did not end with the suffix given, or Just the list--- before the suffix, if it does.-stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]-stripSuffix xs ys = fmap reverse $ stripPrefix (reverse xs) (reverse ys)+{-# LANGUAGE CPP #-}++#undef WINDOWS+#define IS_WINDOWS False+#define MODULE_NAME Posix++#include "Internal.hs"
System/FilePath/Windows.hs view
@@ -1,1048 +1,8 @@----{-# LANGUAGE PatternGuards #-}---- This template expects CPP definitions for:---     MODULE_NAME = Posix | Windows---     IS_WINDOWS  = False | True---- |--- Module      :  System.FilePath.MODULE_NAME--- Copyright   :  (c) Neil Mitchell 2005-2014--- License     :  BSD3------ Maintainer  :  ndmitchell@gmail.com--- Stability   :  stable--- Portability :  portable------ A library for 'FilePath' manipulations, using MODULE_NAME style paths on--- all platforms. Importing "System.FilePath" is usually better.------ Given the example 'FilePath': @\/directory\/file.ext@------ We can use the following functions to extract pieces.------ * 'takeFileName' gives @\"file.ext\"@------ * 'takeDirectory' gives @\"\/directory\"@------ * 'takeExtension' gives @\".ext\"@------ * 'dropExtension' gives @\"\/directory\/file\"@------ * 'takeBaseName' gives @\"file\"@------ And we could have built an equivalent path with the following expressions:------ * @\"\/directory\" '</>' \"file.ext\"@.------ * @\"\/directory\/file" '<.>' \"ext\"@.------ * @\"\/directory\/file.txt" '-<.>' \"ext\"@.------ Each function in this module is documented with several examples,--- which are also used as tests.------ Here are a few examples of using the @filepath@ functions together:------ /Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:------ @['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@------ /Example 2:/ Download a file from @url@ and save it to disk:------ @do let file = 'makeValid' url---   System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@------ /Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:------ @'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@------ References:--- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)-module System.FilePath.Windows-    (-    -- * Separator predicates-    FilePath,-    pathSeparator, pathSeparators, isPathSeparator,-    searchPathSeparator, isSearchPathSeparator,-    extSeparator, isExtSeparator,--    -- * @$PATH@ methods-    splitSearchPath, getSearchPath,--    -- * Extension functions-    splitExtension,-    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),-    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,-    stripExtension,--    -- * Filename\/directory functions-    splitFileName,-    takeFileName, replaceFileName, dropFileName,-    takeBaseName, replaceBaseName,-    takeDirectory, replaceDirectory,-    combine, (</>),-    splitPath, joinPath, splitDirectories,--    -- * Drive functions-    splitDrive, joinDrive,-    takeDrive, hasDrive, dropDrive, isDrive,--    -- * Trailing slash functions-    hasTrailingPathSeparator,-    addTrailingPathSeparator,-    dropTrailingPathSeparator,--    -- * File name manipulations-    normalise, equalFilePath,-    makeRelative,-    isRelative, isAbsolute,-    isValid, makeValid-    )-    where--import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)-import Data.Maybe(isJust)-import Data.List(stripPrefix, isSuffixOf)--import System.Environment(getEnv)---infixr 7  <.>, -<.>-infixr 5  </>------------------------------------------------------------------------------ Platform Abstraction Methods (private)---- | Is the operating system Unix or Linux like-isPosix :: Bool-isPosix = not isWindows---- | Is the operating system Windows like-isWindows :: Bool-isWindows = True--------------------------------------------------------------------------- The basic functions---- | The character that separates directories. In the case where more than---   one character is possible, 'pathSeparator' is the \'ideal\' one.------ > Windows: pathSeparator == '\\'--- > Posix:   pathSeparator ==  '/'--- > isPathSeparator pathSeparator-pathSeparator :: Char-pathSeparator = if isWindows then '\\' else '/'---- | The list of all possible separators.------ > Windows: pathSeparators == ['\\', '/']--- > Posix:   pathSeparators == ['/']--- > pathSeparator `elem` pathSeparators-pathSeparators :: [Char]-pathSeparators = if isWindows then "\\/" else "/"---- | Rather than using @(== 'pathSeparator')@, use this. Test if something---   is a path separator.------ > isPathSeparator a == (a `elem` pathSeparators)-isPathSeparator :: Char -> Bool-isPathSeparator '/' = True-isPathSeparator '\\' = isWindows-isPathSeparator _ = False----- | The character that is used to separate the entries in the $PATH environment variable.------ > Windows: searchPathSeparator == ';'--- > Posix:   searchPathSeparator == ':'-searchPathSeparator :: Char-searchPathSeparator = if isWindows then ';' else ':'---- | Is the character a file separator?------ > isSearchPathSeparator a == (a == searchPathSeparator)-isSearchPathSeparator :: Char -> Bool-isSearchPathSeparator = (== searchPathSeparator)----- | File extension character------ > extSeparator == '.'-extSeparator :: Char-extSeparator = '.'---- | Is the character an extension character?------ > isExtSeparator a == (a == extSeparator)-isExtSeparator :: Char -> Bool-isExtSeparator = (== extSeparator)--------------------------------------------------------------------------- Path methods (environment $PATH)---- | Take a string, split it on the 'searchPathSeparator' character.---   Blank items are ignored on Windows, and converted to @.@ on Posix.---   On Windows path elements are stripped of quotes.------   Follows the recommendations in---   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>------ > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]--- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]--- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]--- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]--- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]-splitSearchPath :: String -> [FilePath]-splitSearchPath = f-    where-    f xs = case break isSearchPathSeparator xs of-           (pre, []    ) -> g pre-           (pre, _:post) -> g pre ++ f post--    g "" = ["." | isPosix]-    g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]-    g x = [x]----- | Get a list of 'FilePath's in the $PATH variable.-getSearchPath :: IO [FilePath]-getSearchPath = fmap splitSearchPath (getEnv "PATH")--------------------------------------------------------------------------- Extension methods---- | Split on the extension. 'addExtension' is the inverse.------ > splitExtension "/directory/path.ext" == ("/directory/path",".ext")--- > uncurry (++) (splitExtension x) == x--- > Valid x => uncurry addExtension (splitExtension x) == x--- > splitExtension "file.txt" == ("file",".txt")--- > splitExtension "file" == ("file","")--- > splitExtension "file/file.txt" == ("file/file",".txt")--- > splitExtension "file.txt/boris" == ("file.txt/boris","")--- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")--- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")--- > splitExtension "file/path.txt/" == ("file/path.txt/","")-splitExtension :: FilePath -> (String, String)-splitExtension x = case nameDot of-                       "" -> (x,"")-                       _ -> (dir ++ init nameDot, extSeparator : ext)-    where-        (dir,file) = splitFileName_ x-        (nameDot,ext) = breakEnd isExtSeparator file---- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.------ > takeExtension "/directory/path.ext" == ".ext"--- > takeExtension x == snd (splitExtension x)--- > Valid x => takeExtension (addExtension x "ext") == ".ext"--- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"-takeExtension :: FilePath -> String-takeExtension = snd . splitExtension---- | Remove the current extension and add another, equivalent to 'replaceExtension'.------ > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"--- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"--- > "foo.o" -<.> "c" == "foo.c"-(-<.>) :: FilePath -> String -> FilePath-(-<.>) = replaceExtension---- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.------ > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"--- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"--- > replaceExtension "file.txt" ".bob" == "file.bob"--- > replaceExtension "file.txt" "bob" == "file.bob"--- > replaceExtension "file" ".bob" == "file.bob"--- > replaceExtension "file.txt" "" == "file"--- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"--- > replaceExtension x y == addExtension (dropExtension x) y-replaceExtension :: FilePath -> String -> FilePath-replaceExtension x y = dropExtension x <.> y---- | Add an extension, even if there is already one there, equivalent to 'addExtension'.------ > "/directory/path" <.> "ext" == "/directory/path.ext"--- > "/directory/path" <.> ".ext" == "/directory/path.ext"-(<.>) :: FilePath -> String -> FilePath-(<.>) = addExtension---- | Remove last extension, and the \".\" preceding it.------ > dropExtension "/directory/path.ext" == "/directory/path"--- > dropExtension x == fst (splitExtension x)-dropExtension :: FilePath -> FilePath-dropExtension = fst . splitExtension---- | Add an extension, even if there is already one there, equivalent to '<.>'.------ > addExtension "/directory/path" "ext" == "/directory/path.ext"--- > addExtension "file.txt" "bib" == "file.txt.bib"--- > addExtension "file." ".bib" == "file..bib"--- > addExtension "file" ".bib" == "file.bib"--- > addExtension "/" "x" == "/.x"--- > addExtension x "" == x--- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"--- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"-addExtension :: FilePath -> String -> FilePath-addExtension file "" = file-addExtension file xs@(x:_) = joinDrive a res-    where-        res = if isExtSeparator x then b ++ xs-              else b ++ [extSeparator] ++ xs--        (a,b) = splitDrive file---- | Does the given filename have an extension?------ > hasExtension "/directory/path.ext" == True--- > hasExtension "/directory/path" == False--- > null (takeExtension x) == not (hasExtension x)-hasExtension :: FilePath -> Bool-hasExtension = any isExtSeparator . takeFileName----- | Does the given filename have the specified extension?------ > "png" `isExtensionOf` "/directory/file.png" == True--- > ".png" `isExtensionOf` "/directory/file.png" == True--- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True--- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False--- > "png" `isExtensionOf` "/directory/file.png.jpg" == False--- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False-isExtensionOf :: String -> FilePath -> Bool-isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions-isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions---- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.---   Returns 'Nothing' if the FilePath does not have the given extension, or---   'Just' and the part before the extension if it does.------   This function can be more predictable than 'dropExtensions', especially if the filename---   might itself contain @.@ characters.------ > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"--- > stripExtension "hi.o" "foo.x.hs.o" == Nothing--- > dropExtension x == fromJust (stripExtension (takeExtension x) x)--- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)--- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"--- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."--- > stripExtension "baz"  "foo.bar"  == Nothing--- > stripExtension "bar"  "foobar"   == Nothing--- > stripExtension ""     x          == Just x-stripExtension :: String -> FilePath -> Maybe FilePath-stripExtension []        path = Just path-stripExtension ext@(x:_) path = stripSuffix dotExt path-    where dotExt = if isExtSeparator x then ext else '.':ext----- | Split on all extensions.------ > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")--- > splitExtensions "file.tar.gz" == ("file",".tar.gz")--- > uncurry (++) (splitExtensions x) == x--- > Valid x => uncurry addExtension (splitExtensions x) == x--- > splitExtensions "file.tar.gz" == ("file",".tar.gz")-splitExtensions :: FilePath -> (FilePath, String)-splitExtensions x = (a ++ c, d)-    where-        (a,b) = splitFileName_ x-        (c,d) = break isExtSeparator b---- | Drop all extensions.------ > dropExtensions "/directory/path.ext" == "/directory/path"--- > dropExtensions "file.tar.gz" == "file"--- > not $ hasExtension $ dropExtensions x--- > not $ any isExtSeparator $ takeFileName $ dropExtensions x-dropExtensions :: FilePath -> FilePath-dropExtensions = fst . splitExtensions---- | Get all extensions.------ > takeExtensions "/directory/path.ext" == ".ext"--- > takeExtensions "file.tar.gz" == ".tar.gz"-takeExtensions :: FilePath -> String-takeExtensions = snd . splitExtensions----- | Replace all extensions of a file with a new extension. Note---   that 'replaceExtension' and 'addExtension' both work for adding---   multiple extensions, so only required when you need to drop---   all extensions first.------ > replaceExtensions "file.fred.bob" "txt" == "file.txt"--- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"-replaceExtensions :: FilePath -> String -> FilePath-replaceExtensions x y = dropExtensions x <.> y---------------------------------------------------------------------------- Drive methods---- | Is the given character a valid drive letter?--- only a-z and A-Z are letters, not isAlpha which is more unicodey-isLetter :: Char -> Bool-isLetter x = isAsciiLower x || isAsciiUpper x----- | Split a path into a drive and a path.---   On Posix, \/ is a Drive.------ > uncurry (++) (splitDrive x) == x--- > Windows: splitDrive "file" == ("","file")--- > Windows: splitDrive "c:/file" == ("c:/","file")--- > Windows: splitDrive "c:\\file" == ("c:\\","file")--- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")--- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")--- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")--- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")--- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")--- > Windows: splitDrive "/d" == ("","/d")--- > Posix:   splitDrive "/test" == ("/","test")--- > Posix:   splitDrive "//test" == ("//","test")--- > Posix:   splitDrive "test/file" == ("","test/file")--- > Posix:   splitDrive "file" == ("","file")-splitDrive :: FilePath -> (FilePath, FilePath)-splitDrive x | isPosix = span (== '/') x-splitDrive x | Just y <- readDriveLetter x = y-splitDrive x | Just y <- readDriveUNC x = y-splitDrive x | Just y <- readDriveShare x = y-splitDrive x = ("",x)--addSlash :: FilePath -> FilePath -> (FilePath, FilePath)-addSlash a xs = (a++c,d)-    where (c,d) = span isPathSeparator xs---- See [1].--- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"-readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)-readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =-    case map toUpper xs of-        ('U':'N':'C':s4:_) | isPathSeparator s4 ->-            let (a,b) = readDriveShareName (drop 4 xs)-            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)-        _ -> case readDriveLetter xs of-                 -- Extended-length path.-                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)-                 Nothing -> Nothing-readDriveUNC _ = Nothing--{- c:\ -}-readDriveLetter :: String -> Maybe (FilePath, FilePath)-readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)-readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)-readDriveLetter _ = Nothing--{- \\sharename\ -}-readDriveShare :: String -> Maybe (FilePath, FilePath)-readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =-        Just (s1:s2:a,b)-    where (a,b) = readDriveShareName xs-readDriveShare _ = Nothing--{- assume you have already seen \\ -}-{- share\bob -> "share\", "bob" -}-readDriveShareName :: String -> (FilePath, FilePath)-readDriveShareName name = addSlash a b-    where (a,b) = break isPathSeparator name------ | Join a drive and the rest of the path.------ > Valid x => uncurry joinDrive (splitDrive x) == x--- > Windows: joinDrive "C:" "foo" == "C:foo"--- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"--- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"--- > Windows: joinDrive "/:" "foo" == "/:\\foo"-joinDrive :: FilePath -> FilePath -> FilePath-joinDrive = combineAlways---- | Get the drive from a filepath.------ > takeDrive x == fst (splitDrive x)-takeDrive :: FilePath -> FilePath-takeDrive = fst . splitDrive---- | Delete the drive, if it exists.------ > dropDrive x == snd (splitDrive x)-dropDrive :: FilePath -> FilePath-dropDrive = snd . splitDrive---- | Does a path have a drive.------ > not (hasDrive x) == null (takeDrive x)--- > Posix:   hasDrive "/foo" == True--- > Windows: hasDrive "C:\\foo" == True--- > Windows: hasDrive "C:foo" == True--- >          hasDrive "foo" == False--- >          hasDrive "" == False-hasDrive :: FilePath -> Bool-hasDrive = not . null . takeDrive----- | Is an element a drive------ > Posix:   isDrive "/" == True--- > Posix:   isDrive "/foo" == False--- > Windows: isDrive "C:\\" == True--- > Windows: isDrive "C:\\foo" == False--- >          isDrive "" == False-isDrive :: FilePath -> Bool-isDrive x = not (null x) && null (dropDrive x)--------------------------------------------------------------------------- Operations on a filepath, as a list of directories---- | Split a filename into directory and file. '</>' is the inverse.---   The first component will often end with a trailing slash.------ > splitFileName "/directory/file.ext" == ("/directory/","file.ext")--- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"--- > Valid x => isValid (fst (splitFileName x))--- > splitFileName "file/bob.txt" == ("file/", "bob.txt")--- > splitFileName "file/" == ("file/", "")--- > splitFileName "bob" == ("./", "bob")--- > Posix:   splitFileName "/" == ("/","")--- > Windows: splitFileName "c:" == ("c:","")-splitFileName :: FilePath -> (String, String)-splitFileName x = (if null dir then "./" else dir, name)-    where-        (dir, name) = splitFileName_ x---- version of splitFileName where, if the FilePath has no directory--- component, the returned directory is "" rather than "./".  This--- is used in cases where we are going to combine the returned--- directory to make a valid FilePath, and having a "./" appear would--- look strange and upset simple equality properties.  See--- e.g. replaceFileName.-splitFileName_ :: FilePath -> (String, String)-splitFileName_ x = (drv ++ dir, file)-    where-        (drv,pth) = splitDrive x-        (dir,file) = breakEnd isPathSeparator pth---- | Set the filename.------ > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"--- > Valid x => replaceFileName x (takeFileName x) == x-replaceFileName :: FilePath -> String -> FilePath-replaceFileName x y = a </> y where (a,_) = splitFileName_ x---- | Drop the filename. Unlike 'takeDirectory', this function will leave---   a trailing path separator on the directory.------ > dropFileName "/directory/file.ext" == "/directory/"--- > dropFileName x == fst (splitFileName x)-dropFileName :: FilePath -> FilePath-dropFileName = fst . splitFileName----- | Get the file name.------ > takeFileName "/directory/file.ext" == "file.ext"--- > takeFileName "test/" == ""--- > takeFileName x `isSuffixOf` x--- > takeFileName x == snd (splitFileName x)--- > Valid x => takeFileName (replaceFileName x "fred") == "fred"--- > Valid x => takeFileName (x </> "fred") == "fred"--- > Valid x => isRelative (takeFileName x)-takeFileName :: FilePath -> FilePath-takeFileName = snd . splitFileName---- | Get the base name, without an extension or path.------ > takeBaseName "/directory/file.ext" == "file"--- > takeBaseName "file/test.txt" == "test"--- > takeBaseName "dave.ext" == "dave"--- > takeBaseName "" == ""--- > takeBaseName "test" == "test"--- > takeBaseName (addTrailingPathSeparator x) == ""--- > takeBaseName "file/file.tar.gz" == "file.tar"-takeBaseName :: FilePath -> String-takeBaseName = dropExtension . takeFileName---- | Set the base name.------ > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"--- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"--- > replaceBaseName "fred" "bill" == "bill"--- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"--- > Valid x => replaceBaseName x (takeBaseName x) == x-replaceBaseName :: FilePath -> String -> FilePath-replaceBaseName pth nam = combineAlways a (nam <.> ext)-    where-        (a,b) = splitFileName_ pth-        ext = takeExtension b---- | Is an item either a directory or the last character a path separator?------ > hasTrailingPathSeparator "test" == False--- > hasTrailingPathSeparator "test/" == True-hasTrailingPathSeparator :: FilePath -> Bool-hasTrailingPathSeparator "" = False-hasTrailingPathSeparator x = isPathSeparator (last x)---hasLeadingPathSeparator :: FilePath -> Bool-hasLeadingPathSeparator "" = False-hasLeadingPathSeparator x = isPathSeparator (head x)----- | Add a trailing file path separator if one is not already present.------ > hasTrailingPathSeparator (addTrailingPathSeparator x)--- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x--- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"-addTrailingPathSeparator :: FilePath -> FilePath-addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]----- | Remove any trailing path separators------ > dropTrailingPathSeparator "file/test/" == "file/test"--- >           dropTrailingPathSeparator "/" == "/"--- > Windows:  dropTrailingPathSeparator "\\" == "\\"--- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x-dropTrailingPathSeparator :: FilePath -> FilePath-dropTrailingPathSeparator x =-    if hasTrailingPathSeparator x && not (isDrive x)-    then let x' = dropWhileEnd isPathSeparator x-         in if null x' then [last x] else x'-    else x----- | Get the directory name, move up one level.------ >           takeDirectory "/directory/other.ext" == "/directory"--- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."--- >           takeDirectory "foo" == "."--- >           takeDirectory "/" == "/"--- >           takeDirectory "/foo" == "/"--- >           takeDirectory "/foo/bar/baz" == "/foo/bar"--- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"--- >           takeDirectory "foo/bar/baz" == "foo/bar"--- > Windows:  takeDirectory "foo\\bar" == "foo"--- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"--- > Windows:  takeDirectory "C:\\" == "C:\\"-takeDirectory :: FilePath -> FilePath-takeDirectory = dropTrailingPathSeparator . dropFileName---- | Set the directory, keeping the filename the same.------ > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"--- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x-replaceDirectory :: FilePath -> String -> FilePath-replaceDirectory x dir = combineAlways dir (takeFileName x)----- | An alias for '</>'.-combine :: FilePath -> FilePath -> FilePath-combine a b | hasLeadingPathSeparator b || hasDrive b = b-            | otherwise = combineAlways a b---- | Combine two paths, assuming rhs is NOT absolute.-combineAlways :: FilePath -> FilePath -> FilePath-combineAlways a b | null a = b-                  | null b = a-                  | hasTrailingPathSeparator a = a ++ b-                  | otherwise = case a of-                      [a1,':'] | isWindows && isLetter a1 -> a ++ b-                      _ -> a ++ [pathSeparator] ++ b----- | Combine two paths with a path separator.---   If the second path starts with a path separator or a drive letter, then it returns the second.---   The intention is that @readFile (dir '</>' file)@ will access the same file as---   @setCurrentDirectory dir; readFile file@.------ > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"--- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"--- >          "directory" </> "/file.ext" == "/file.ext"--- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x------   Combined:------ > Posix:   "/" </> "test" == "/test"--- > Posix:   "home" </> "bob" == "home/bob"--- > Posix:   "x:" </> "foo" == "x:/foo"--- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"--- > Windows: "home" </> "bob" == "home\\bob"------   Not combined:------ > Posix:   "home" </> "/bob" == "/bob"--- > Windows: "home" </> "C:\\bob" == "C:\\bob"------   Not combined (tricky):------   On Windows, if a filepath starts with a single slash, it is relative to the---   root of the current drive. In [1], this is (confusingly) referred to as an---   absolute path.---   The current behavior of '</>' is to never combine these forms.------ > Windows: "home" </> "/bob" == "/bob"--- > Windows: "home" </> "\\bob" == "\\bob"--- > Windows: "C:\\home" </> "\\bob" == "\\bob"------   On Windows, from [1]: "If a file name begins with only a disk designator---   but not the backslash after the colon, it is interpreted as a relative path---   to the current directory on the drive with the specified letter."---   The current behavior of '</>' is to never combine these forms.------ > Windows: "D:\\foo" </> "C:bar" == "C:bar"--- > Windows: "C:\\foo" </> "C:bar" == "C:bar"-(</>) :: FilePath -> FilePath -> FilePath-(</>) = combine----- | Split a path by the directory separator.------ > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]--- > concat (splitPath x) == x--- > splitPath "test//item/" == ["test//","item/"]--- > splitPath "test/item/file" == ["test/","item/","file"]--- > splitPath "" == []--- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]--- > Posix:   splitPath "/file/test" == ["/","file/","test"]-splitPath :: FilePath -> [FilePath]-splitPath x = [drive | drive /= ""] ++ f path-    where-        (drive,path) = splitDrive x--        f "" = []-        f y = (a++c) : f d-            where-                (a,b) = break isPathSeparator y-                (c,d) = span  isPathSeparator b---- | Just as 'splitPath', but don't add the trailing slashes to each element.------ >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]--- >          splitDirectories "test/file" == ["test","file"]--- >          splitDirectories "/test/file" == ["/","test","file"]--- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]--- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x--- >          splitDirectories "" == []--- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]--- >          splitDirectories "/test///file" == ["/","test","file"]-splitDirectories :: FilePath -> [FilePath]-splitDirectories = map dropTrailingPathSeparator . splitPath----- | Join path elements back together.------ > joinPath a == foldr (</>) "" a--- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"--- > Valid x => joinPath (splitPath x) == x--- > joinPath [] == ""--- > Posix: joinPath ["test","file","path"] == "test/file/path"-joinPath :: [FilePath] -> FilePath--- Note that this definition on c:\\c:\\, join then split will give c:\\.-joinPath = foldr combine ""------------------------------------------------------------------------------- File name manipulators---- | Equality of two 'FilePath's.---   If you call @System.Directory.canonicalizePath@---   first this has a much better chance of working.---   Note that this doesn't follow symlinks or DOSNAM~1s.------ Similar to 'normalise', this does not expand @".."@, because of symlinks.------ >          x == y ==> equalFilePath x y--- >          normalise x == normalise y ==> equalFilePath x y--- >          equalFilePath "foo" "foo/"--- >          not (equalFilePath "/a/../c" "/c")--- >          not (equalFilePath "foo" "/foo")--- > Posix:   not (equalFilePath "foo" "FOO")--- > Windows: equalFilePath "foo" "FOO"--- > Windows: not (equalFilePath "C:" "C:/")-equalFilePath :: FilePath -> FilePath -> Bool-equalFilePath a b = f a == f b-    where-        f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x-            | otherwise = dropTrailingPathSeparator $ normalise x----- | Contract a filename, based on a relative path. Note that the resulting path---   will never introduce @..@ paths, as the presence of symlinks means @..\/b@---   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see---   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.------   The corresponding @makeAbsolute@ function can be found in---   @System.Directory@.------ >          makeRelative "/directory" "/directory/file.ext" == "file.ext"--- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x--- >          makeRelative x x == "."--- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x--- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"--- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"--- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"--- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"--- > Windows: makeRelative "/Home" "/home/bob" == "bob"--- > Windows: makeRelative "/" "//" == "//"--- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"--- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"--- > Posix:   makeRelative "/fred" "bob" == "bob"--- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"--- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"--- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"-makeRelative :: FilePath -> FilePath -> FilePath-makeRelative root path- | equalFilePath root path = "."- | takeAbs root /= takeAbs path = path- | otherwise = f (dropAbs root) (dropAbs path)-    where-        f "" y = dropWhile isPathSeparator y-        f x y = let (x1,x2) = g x-                    (y1,y2) = g y-                in if equalFilePath x1 y1 then f x2 y2 else path--        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)-            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x--        -- on windows, need to drop '/' which is kind of absolute, but not a drive-        dropAbs x | hasLeadingPathSeparator x && not (hasDrive x) = tail x-        dropAbs x = dropDrive x--        takeAbs x | hasLeadingPathSeparator x && not (hasDrive x) = [pathSeparator]-        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x---- | Normalise a file------ * \/\/ outside of the drive can be made blank------ * \/ -> 'pathSeparator'------ * .\/ -> \"\"------ Does not remove @".."@, because of symlinks.------ > Posix:   normalise "/file/\\test////" == "/file/\\test/"--- > Posix:   normalise "/file/./test" == "/file/test"--- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"--- > Posix:   normalise "../bob/fred/" == "../bob/fred/"--- > Posix:   normalise "/a/../c" == "/a/../c"--- > Posix:   normalise "./bob/fred/" == "bob/fred/"--- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"--- > Windows: normalise "c:\\" == "C:\\"--- > Windows: normalise "C:.\\" == "C:"--- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"--- > Windows: normalise "//server/test" == "\\\\server\\test"--- > Windows: normalise "c:/file" == "C:\\file"--- > Windows: normalise "/file" == "\\file"--- > Windows: normalise "\\" == "\\"--- > Windows: normalise "/./" == "\\"--- >          normalise "." == "."--- > Posix:   normalise "./" == "./"--- > Posix:   normalise "./." == "./"--- > Posix:   normalise "/./" == "/"--- > Posix:   normalise "/" == "/"--- > Posix:   normalise "bob/fred/." == "bob/fred/"--- > Posix:   normalise "//home" == "/home"-normalise :: FilePath -> FilePath-normalise path = result ++ [pathSeparator | addPathSeparator]-    where-        (drv,pth) = splitDrive path-        result = joinDrive' (normaliseDrive drv) (f pth)--        joinDrive' "" "" = "."-        joinDrive' d p = joinDrive d p--        addPathSeparator = isDirPath pth-            && not (hasTrailingPathSeparator result)-            && not (isRelativeDrive drv)--        isDirPath xs = hasTrailingPathSeparator xs-            || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)--        f = joinPath . dropDots . propSep . splitDirectories--        propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs-                       | otherwise = x : xs-        propSep [] = []--        dropDots = filter ("." /=)--normaliseDrive :: FilePath -> FilePath-normaliseDrive "" = ""-normaliseDrive _ | isPosix = [pathSeparator]-normaliseDrive drive = if isJust $ readDriveLetter x2-                       then map toUpper x2-                       else x2-    where-        x2 = map repSlash drive--        repSlash x = if isPathSeparator x then pathSeparator else x---- Information for validity functions on Windows. See [1].-isBadCharacter :: Char -> Bool-isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""--badElements :: [FilePath]-badElements =-    ["CON","PRN","AUX","NUL","CLOCK$"-    ,"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"-    ,"LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"]----- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,---   and invalid characters, but does not check if length limits are exceeded, as these are typically---   filesystem dependent.------ >          isValid "" == False--- >          isValid "\0" == False--- > Posix:   isValid "/random_ path:*" == True--- > Posix:   isValid x == not (null x)--- > Windows: isValid "c:\\test" == True--- > Windows: isValid "c:\\test:of_test" == False--- > Windows: isValid "test*" == False--- > Windows: isValid "c:\\test\\nul" == False--- > Windows: isValid "c:\\test\\prn.txt" == False--- > Windows: isValid "c:\\nul\\file" == False--- > Windows: isValid "\\\\" == False--- > Windows: isValid "\\\\\\foo" == False--- > Windows: isValid "\\\\?\\D:file" == False--- > Windows: isValid "foo\tbar" == False--- > Windows: isValid "nul .txt" == False--- > Windows: isValid " nul.txt" == True-isValid :: FilePath -> Bool-isValid "" = False-isValid x | '\0' `elem` x = False-isValid _ | isPosix = True-isValid path =-        not (any isBadCharacter x2) &&-        not (any f $ splitDirectories x2) &&-        not (isJust (readDriveShare x1) && all isPathSeparator x1) &&-        not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))-    where-        (x1,x2) = splitDrive path-        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements----- | Take a FilePath and make it valid; does not change already valid FilePaths.------ > isValid (makeValid x)--- > isValid x ==> makeValid x == x--- > makeValid "" == "_"--- > makeValid "file\0name" == "file_name"--- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"--- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"--- > Windows: makeValid "test*" == "test_"--- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"--- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"--- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"--- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"--- > Windows: makeValid "\\\\\\foo" == "\\\\drive"--- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"--- > Windows: makeValid "nul .txt" == "nul _.txt"-makeValid :: FilePath -> FilePath-makeValid "" = "_"-makeValid path-        | isPosix = map (\x -> if x == '\0' then '_' else x) path-        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"-        | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =-            makeValid (drv ++ [pathSeparator] ++ pth)-        | otherwise = joinDrive drv $ validElements $ validChars pth-    where-        (drv,pth) = splitDrive path--        validChars = map f-        f x = if isBadCharacter x then '_' else x--        validElements x = joinPath $ map g $ splitPath x-        g x = h a ++ b-            where (a,b) = break isPathSeparator x-        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x-            where (a,b) = splitExtensions x----- | Is a path relative, or is it fixed to the root?------ > Windows: isRelative "path\\test" == True--- > Windows: isRelative "c:\\test" == False--- > Windows: isRelative "c:test" == True--- > Windows: isRelative "c:\\" == False--- > Windows: isRelative "c:/" == False--- > Windows: isRelative "c:" == True--- > Windows: isRelative "\\\\foo" == False--- > Windows: isRelative "\\\\?\\foo" == False--- > Windows: isRelative "\\\\?\\UNC\\foo" == False--- > Windows: isRelative "/foo" == True--- > Windows: isRelative "\\foo" == True--- > Posix:   isRelative "test/path" == True--- > Posix:   isRelative "/test" == False--- > Posix:   isRelative "/" == False------ According to [1]:------ * "A UNC name of any format [is never relative]."------ * "You cannot use the "\\?\" prefix with a relative path."-isRelative :: FilePath -> Bool-isRelative x = null drive || isRelativeDrive drive-    where drive = takeDrive x---{- c:foo -}--- From [1]: "If a file name begins with only a disk designator but not the--- backslash after the colon, it is interpreted as a relative path to the--- current directory on the drive with the specified letter."-isRelativeDrive :: String -> Bool-isRelativeDrive x =-    maybe False (not . hasTrailingPathSeparator . fst) (readDriveLetter x)----- | @not . 'isRelative'@------ > isAbsolute x == not (isRelative x)-isAbsolute :: FilePath -> Bool-isAbsolute = not . isRelative----------------------------------------------------------------------------------- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])--- Note that Data.List.dropWhileEnd is only available in base >= 4.5.-dropWhileEnd :: (a -> Bool) -> [a] -> [a]-dropWhileEnd p = reverse . dropWhile p . reverse---- takeWhileEnd (>2) [1,2,3,4,1,2,3,4] == [3,4])-takeWhileEnd :: (a -> Bool) -> [a] -> [a]-takeWhileEnd p = reverse . takeWhile p . reverse---- spanEnd (>2) [1,2,3,4,1,2,3,4] = ([1,2,3,4,1,2], [3,4])-spanEnd :: (a -> Bool) -> [a] -> ([a], [a])-spanEnd p xs = (dropWhileEnd p xs, takeWhileEnd p xs)---- breakEnd (< 2) [1,2,3,4,1,2,3,4] == ([1,2,3,4,1],[2,3,4])-breakEnd :: (a -> Bool) -> [a] -> ([a], [a])-breakEnd p = spanEnd (not . p)---- | The stripSuffix function drops the given suffix from a list. It returns--- Nothing if the list did not end with the suffix given, or Just the list--- before the suffix, if it does.-stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]-stripSuffix xs ys = fmap reverse $ stripPrefix (reverse xs) (reverse ys)+{-# LANGUAGE CPP #-}++#undef POSIX+#define WINDOWS+#define IS_WINDOWS True+#define MODULE_NAME Windows++#include "Internal.hs"
+ System/OsPath.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE CPP #-}++#define FILEPATH_NAME OsPath+#define OSSTRING_NAME OsString+#define WORD_NAME OsChar++-- |+-- Module      :  System.OsPath+-- Copyright   :  © 2021 Julian Ospald+-- License     :  MIT+--+-- Maintainer  :  Julian Ospald <hasufell@posteo.de>+-- Stability   :  experimental+-- Portability :  portable+--+-- An implementation of the <https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path Abstract FilePath Proposal>,+-- which aims to supersede @type FilePath = String@ for various reasons:+--+-- 1. it is more efficient and avoids memory fragmentation (uses unpinned 'ShortByteString' under the hood)+-- 2. it is more type-safe (newtype over 'ShortByteString')+-- 3. avoids round-tripping issues by not converting to String (which is not total and loses the encoding)+-- 4. abstracts over unix and windows while keeping the original bytes+--+-- It is important to know that filenames\/filepaths have different representations across platforms:+--+-- - On /Windows/, filepaths are expected to be encoded as UTF16-LE <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/76f10dd8-699d-45e6-a53c-5aefc586da20 as per the documentation>, but+--   may also include invalid surrogate pairs, in which case UCS-2 can be used. They are passed as @wchar_t*@ to syscalls.+--   'OsPath' only maintains the wide character invariant.+-- - On /Unix/, filepaths don't have a predefined encoding (although they+--   are often interpreted as UTF8) as per the+--   <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170 POSIX specification>+--   and are passed as @char[]@ to syscalls. 'OsPath' maintains no invariant+--   here.+--+-- Apart from encoding, filepaths have additional restrictions per platform:+--+-- - On /Windows/ the <https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions naming convention> may apply+-- - On /Unix/, only @NUL@ bytes are disallowed as per the <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170 POSIX specification>+--+-- Use 'isValid' to check for these restrictions ('OsPath' doesn't+-- maintain this invariant).+--+-- Also note that these restrictions are+-- not exhaustive and further filesystem specific restrictions may apply on+-- all platforms. This library makes no attempt at satisfying these.+-- Library users may need to account for that, depending+-- on what filesystems they want to support.+--+-- It is advised to follow these principles when dealing with filepaths\/filenames:+--+-- 1. Avoid interpreting filenames that the OS returns, unless absolutely necessary.+--    For example, the filepath separator is usually a predefined 'Word8'/'Word16', regardless of encoding.+--    So even if we need to split filepaths, it might still not be necessary to understand the encoding+--    of the filename.+-- 2. When interpreting OS returned filenames consider that these might not be UTF8 on /unix/+--    or at worst don't have an ASCII compatible encoding. The are 3 available strategies fer decoding/encoding:+--    a) pick the best UTF (UTF-8 on unix, UTF-16LE on windows), b) decode with an explicitly defined 'TextEncoding',+--    c) mimic the behavior of the @base@ library (permissive UTF16 on windows, current filesystem encoding on unix).+-- 3. Avoid comparing @String@ based filepaths, because filenames of different encodings+--    may have the same @String@ representation, although they're not the same byte-wise.+++#include "OsPath/Common.hs"
+ System/OsPath.hs-boot view
@@ -0,0 +1,6 @@+module System.OsPath where++import System.OsPath.Types+    ( OsPath )++isValid :: OsPath -> Bool
+ System/OsPath/Common.hs view
@@ -0,0 +1,1437 @@+{-# LANGUAGE TypeApplications #-}+-- This template expects CPP definitions for:+--+--     WINDOWS defined? = no            | yes              | no+--     POSIX   defined? = yes           | no               | no+--+--     FILEPATH_NAME    = PosixPath     | WindowsPath  | OsPath+--     OSSTRING_NAME    = PosixString   | WindowsString    | OsString+--     WORD_NAME        = PosixChar     | WindowsChar      | OsChar++-- For (native) abstract file paths we document both platforms, so people can+-- understand how their code is compiled no matter what. But for the+-- platform-specific types we only want to document the behavior on that+-- platform.+#if defined(WINDOWS)+#define WINDOWS_DOC+#elif defined(POSIX)+#define POSIX_DOC+#endif++#ifdef WINDOWS+module System.OsPath.Windows+#elif defined(POSIX)+module System.OsPath.Posix+#else+module System.OsPath+#endif+  (+  -- * Types+#ifdef WINDOWS+    WindowsString+  , WindowsChar+  , WindowsPath+#elif defined(POSIX)+    PosixString+  , PosixChar+  , PosixPath+#else+    OsPath+  , OsString+  , OsChar+#endif+  -- * Filepath construction+  , PS.encodeUtf+  , PS.encodeWith+  , PS.encodeFS+#if defined(WINDOWS) || defined(POSIX)+  , pstr+#else+  , osp+#endif+  , PS.pack++  -- * Filepath deconstruction+  , PS.decodeUtf+  , PS.decodeWith+  , PS.decodeFS+  , PS.unpack++  -- * Word construction+  , unsafeFromChar++  -- * Word deconstruction+  , toChar++  -- * Separator predicates+  , pathSeparator+  , pathSeparators+  , isPathSeparator+  , searchPathSeparator+  , isSearchPathSeparator+  , extSeparator+  , isExtSeparator++  -- * $PATH methods+  , splitSearchPath,++  -- * Extension functions+    splitExtension,+    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,+    stripExtension,++    -- * Filename\/directory functions+    splitFileName,+    takeFileName, replaceFileName, dropFileName,+    takeBaseName, replaceBaseName,+    takeDirectory, replaceDirectory,+    combine, (</>),+    splitPath, joinPath, splitDirectories,++    -- * Drive functions+    splitDrive, joinDrive,+    takeDrive, hasDrive, dropDrive, isDrive,++    -- * Trailing slash functions+    hasTrailingPathSeparator,+    addTrailingPathSeparator,+    dropTrailingPathSeparator,++    -- * File name manipulations+    normalise, equalFilePath,+    makeRelative,+    isRelative, isAbsolute,+    isValid, makeValid+  )+where+++#ifdef WINDOWS+import System.OsPath.Types+import System.OsString.Windows as PS+    ( unsafeFromChar+    , toChar+    , decodeUtf+    , decodeWith+    , decodeFS+    , pack+    , encodeUtf+    , encodeWith+    , encodeFS+    , unpack+    )+import Data.Bifunctor ( bimap )+import qualified System.OsPath.Windows.Internal as C+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import Language.Haskell.TH.Quote+    ( QuasiQuoter (..) )+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import Control.Monad ( when )++#elif defined(POSIX)+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import Control.Monad ( when )+import Language.Haskell.TH.Quote+    ( QuasiQuoter (..) )+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )++import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import System.OsPath.Types+import System.OsString.Posix as PS+    ( unsafeFromChar+    , toChar+    , decodeUtf+    , decodeWith+    , decodeFS+    , pack+    , encodeUtf+    , encodeWith+    , encodeFS+    , unpack+    )+import Data.Bifunctor ( bimap )+import qualified System.OsPath.Posix.Internal as C++#else++import System.OsPath.Internal as PS+    ( osp+    , decodeUtf+    , decodeWith+    , decodeFS+    , pack+    , encodeUtf+    , encodeWith+    , encodeFS+    , unpack+    )+import System.OsPath.Types+    ( OsPath )+import System.OsString ( unsafeFromChar, toChar )++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+import qualified System.OsPath.Windows as C+#else+import qualified System.OsPath.Posix as C+#endif++import Data.Bifunctor+    ( bimap )+#endif+import System.OsString.Internal.Types+++------------------------+-- Separator predicates+++#ifdef WINDOWS_DOC+-- | The character that separates directories. In the case where more than+--   one character is possible, 'pathSeparator' is the \'ideal\' one.+--+-- > pathSeparator == '\\'S+#elif defined(POSIX_DOC)+-- | The character that separates directories. In the case where more than+--   one character is possible, 'pathSeparator' is the \'ideal\' one.+--+-- > pathSeparator ==  '/'+#else+-- | The character that separates directories. In the case where more than+--   one character is possible, 'pathSeparator' is the \'ideal\' one.+--+-- > Windows: pathSeparator == '\\'S+-- > Posix:   pathSeparator ==  '/'+#endif+pathSeparator :: WORD_NAME+pathSeparator = WORD_NAME C.pathSeparator++#ifdef WINDOWS_DOC+-- | The list of all possible separators.+--+-- > pathSeparators == ['\\', '/']+-- > pathSeparator `elem` pathSeparators+#elif defined(POSIX_DOC)+-- | The list of all possible separators.+--+-- > pathSeparators == ['/']+-- > pathSeparator `elem` pathSeparators+#else+-- | The list of all possible separators.+--+-- > Windows: pathSeparators == ['\\', '/']+-- > Posix:   pathSeparators == ['/']+-- > pathSeparator `elem` pathSeparators+#endif+pathSeparators :: [WORD_NAME]+pathSeparators = WORD_NAME <$> C.pathSeparators++-- | Rather than using @(== 'pathSeparator')@, use this. Test if something+--   is a path separator.+--+-- > isPathSeparator a == (a `elem` pathSeparators)+isPathSeparator :: WORD_NAME -> Bool+isPathSeparator (WORD_NAME w) = C.isPathSeparator w++#ifdef WINDOWS_DOC+-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- > searchPathSeparator == ';'+#elif defined(POSIX_DOC)+-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- > searchPathSeparator == ':'+#else+-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- > Posix:   searchPathSeparator == ':'+-- > Windows: searchPathSeparator == ';'+#endif+searchPathSeparator :: WORD_NAME+searchPathSeparator = WORD_NAME C.searchPathSeparator++-- | Is the character a file separator?+--+-- > isSearchPathSeparator a == (a == searchPathSeparator)+isSearchPathSeparator :: WORD_NAME -> Bool+isSearchPathSeparator (WORD_NAME w) = C.isSearchPathSeparator w+++-- | File extension character+--+-- > extSeparator == '.'+extSeparator :: WORD_NAME+extSeparator = WORD_NAME C.extSeparator+++-- | Is the character an extension character?+--+-- > isExtSeparator a == (a == extSeparator)+isExtSeparator :: WORD_NAME -> Bool+isExtSeparator (WORD_NAME w) = C.isExtSeparator w+++---------------------------------------------------------------------+-- Path methods (environment $PATH)++#ifdef WINDOWS_DOC+-- | Take a string, split it on the 'searchPathSeparator' character.+--+--   Blank items are ignored and path elements are stripped of quotes.+--+-- > splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]+-- > splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]+-- > splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]+#elif defined(POSIX_DOC)+-- | Take a string, split it on the 'searchPathSeparator' character.+--+--   Blank items are converted to @.@ on , and quotes are not+--   treated specially.+--+--   Follows the recommendations in+--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>+--+-- > splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]+-- > splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]+#else+-- | Take a string, split it on the 'searchPathSeparator' character.+--+--   On Windows, blank items are ignored on Windows, and path elements are+--   stripped of quotes.+--+--   On Posix, blank items are converted to @.@ on Posix, and quotes are not+--   treated specially.+--+--   Follows the recommendations in+--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>+--+-- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]+-- > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]+-- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]+#endif+splitSearchPath :: OSSTRING_NAME -> [FILEPATH_NAME]+splitSearchPath (OSSTRING_NAME x) = fmap OSSTRING_NAME . C.splitSearchPath $ x++++------------------------+-- Extension functions++-- | Split on the extension. 'addExtension' is the inverse.+--+-- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")+-- > uncurry (<>) (splitExtension x) == x+-- > Valid x => uncurry addExtension (splitExtension x) == x+-- > splitExtension "file.txt" == ("file",".txt")+-- > splitExtension "file" == ("file","")+-- > splitExtension "file/file.txt" == ("file/file",".txt")+-- > splitExtension "file.txt/boris" == ("file.txt/boris","")+-- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")+-- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")+-- > splitExtension "file/path.txt/" == ("file/path.txt/","")+splitExtension :: FILEPATH_NAME -> (FILEPATH_NAME, OSSTRING_NAME)+splitExtension (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitExtension x+++-- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.+--+-- > takeExtension "/directory/path.ext" == ".ext"+-- > takeExtension x == snd (splitExtension x)+-- > Valid x => takeExtension (addExtension x "ext") == ".ext"+-- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"+takeExtension :: FILEPATH_NAME -> OSSTRING_NAME+takeExtension (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeExtension x+++-- | Remove the current extension and add another, equivalent to 'replaceExtension'.+--+-- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"+-- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"+-- > "foo.o" -<.> "c" == "foo.c"+(-<.>) :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+(-<.>) = replaceExtension++-- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.+--+-- > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"+-- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"+-- > replaceExtension "file.txt" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "bob" == "file.bob"+-- > replaceExtension "file" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "" == "file"+-- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"+-- > replaceExtension x y == addExtension (dropExtension x) y+replaceExtension :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+replaceExtension (OSSTRING_NAME path) (OSSTRING_NAME ext) = OSSTRING_NAME (C.replaceExtension path ext)+++-- | Add an extension, even if there is already one there, equivalent to 'addExtension'.+--+-- > "/directory/path" <.> "ext" == "/directory/path.ext"+-- > "/directory/path" <.> ".ext" == "/directory/path.ext"+(<.>) :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+(<.>) = addExtension++-- | Remove last extension, and the \".\" preceding it.+--+-- > dropExtension "/directory/path.ext" == "/directory/path"+-- > dropExtension x == fst (splitExtension x)+dropExtension :: FILEPATH_NAME -> FILEPATH_NAME+dropExtension (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropExtension x+++-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"++#ifdef WINDOWS_DOC+-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+-- > addExtension "\\\\share" ".txt" == "\\\\share\\.txt"+#elif defined(POSIX_DOC)+-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+#else+-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"+#endif+addExtension :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+addExtension (OSSTRING_NAME bs) (OSSTRING_NAME ext) = OSSTRING_NAME $ C.addExtension bs ext+++-- | Does the given filename have an extension?+--+-- > hasExtension "/directory/path.ext" == True+-- > hasExtension "/directory/path" == False+-- > null (takeExtension x) == not (hasExtension x)+hasExtension :: FILEPATH_NAME -> Bool+hasExtension (OSSTRING_NAME x) = C.hasExtension x++-- | Does the given filename have the specified extension?+--+-- > "png" `isExtensionOf` "/directory/file.png" == True+-- > ".png" `isExtensionOf` "/directory/file.png" == True+-- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True+-- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False+-- > "png" `isExtensionOf` "/directory/file.png.jpg" == False+-- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False+isExtensionOf :: OSSTRING_NAME -> FILEPATH_NAME -> Bool+isExtensionOf (OSSTRING_NAME x) (OSSTRING_NAME y) = C.isExtensionOf x y++-- | Drop the given extension from a filepath, and the @\".\"@ preceding it.+--   Returns 'Nothing' if the filepath does not have the given extension, or+--   'Just' and the part before the extension if it does.+--+--   This function can be more predictable than 'dropExtensions', especially if the filename+--   might itself contain @.@ characters.+--+-- > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"+-- > stripExtension "hi.o" "foo.x.hs.o" == Nothing+-- > dropExtension x == fromJust (stripExtension (takeExtension x) x)+-- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)+-- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"+-- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."+-- > stripExtension "baz"  "foo.bar"  == Nothing+-- > stripExtension "bar"  "foobar"   == Nothing+-- > stripExtension ""     x          == Just x+stripExtension :: OSSTRING_NAME -> FILEPATH_NAME -> Maybe FILEPATH_NAME+stripExtension (OSSTRING_NAME bs) (OSSTRING_NAME x) = OSSTRING_NAME <$> C.stripExtension bs x++-- | Split on all extensions.+--+-- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+-- > uncurry (<>) (splitExtensions x) == x+-- > Valid x => uncurry addExtension (splitExtensions x) == x+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+splitExtensions :: FILEPATH_NAME -> (FILEPATH_NAME, OSSTRING_NAME)+splitExtensions (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitExtensions x+++-- | Drop all extensions.+--+-- > dropExtensions "/directory/path.ext" == "/directory/path"+-- > dropExtensions "file.tar.gz" == "file"+-- > not $ hasExtension $ dropExtensions x+-- > not $ any isExtSeparator $ takeFileName $ dropExtensions x+dropExtensions :: FILEPATH_NAME -> FILEPATH_NAME+dropExtensions (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropExtensions x+++-- | Get all extensions.+--+-- > takeExtensions "/directory/path.ext" == ".ext"+-- > takeExtensions "file.tar.gz" == ".tar.gz"+takeExtensions :: FILEPATH_NAME -> OSSTRING_NAME+takeExtensions (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeExtensions x++-- | Replace all extensions of a file with a new extension. Note+--   that 'replaceExtension' and 'addExtension' both work for adding+--   multiple extensions, so only required when you need to drop+--   all extensions first.+--+-- > replaceExtensions "file.fred.bob" "txt" == "file.txt"+-- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"+replaceExtensions :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+replaceExtensions (OSSTRING_NAME x) (OSSTRING_NAME y) = OSSTRING_NAME $ C.replaceExtensions x y+++------------------------+-- Drive functions+++#ifdef WINDOWS_DOC+-- | Split a path into a drive and a path.+--+-- > uncurry (<>) (splitDrive x) == x+-- > splitDrive "file" == ("","file")+-- > splitDrive "c:/file" == ("c:/","file")+-- > splitDrive "c:\\file" == ("c:\\","file")+-- > splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")+-- > splitDrive "\\\\shared" == ("\\\\shared","")+-- > splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")+-- > splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")+-- > splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")+-- > splitDrive "/d" == ("","/d")+#elif defined(POSIX_DOC)+-- | Split a path into a drive and a path.+--   \/ is a Drive.+--+-- > uncurry (<>) (splitDrive x) == x+-- > splitDrive "/test" == ("/","test")+-- > splitDrive "//test" == ("//","test")+-- > splitDrive "test/file" == ("","test/file")+-- > splitDrive "file" == ("","file")+#else+-- | Split a path into a drive and a path.+--   On Posix, \/ is a Drive.+--+-- > uncurry (<>) (splitDrive x) == x+-- > Windows: splitDrive "file" == ("","file")+-- > Windows: splitDrive "c:/file" == ("c:/","file")+-- > Windows: splitDrive "c:\\file" == ("c:\\","file")+-- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")+-- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")+-- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")+-- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")+-- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")+-- > Windows: splitDrive "/d" == ("","/d")+-- > Posix:   splitDrive "/test" == ("/","test")+-- > Posix:   splitDrive "//test" == ("//","test")+-- > Posix:   splitDrive "test/file" == ("","test/file")+-- > Posix:   splitDrive "file" == ("","file")+#endif+splitDrive :: FILEPATH_NAME -> (FILEPATH_NAME, FILEPATH_NAME)+splitDrive (OSSTRING_NAME p) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitDrive p+++-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+-- > Windows: joinDrive "C:" "foo" == "C:foo"+-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"+-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"+-- > Windows: joinDrive "/:" "foo" == "/:\\foo"++#ifdef WINDOWS_DOC+-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+-- > joinDrive "C:" "foo" == "C:foo"+-- > joinDrive "C:\\" "bar" == "C:\\bar"+-- > joinDrive "\\\\share" "foo" == "\\\\share\\foo"+-- > joinDrive "/:" "foo" == "/:\\foo"+#elif defined(POSIX_DOC)+-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+#else+-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+-- > Windows: joinDrive "C:" "foo" == "C:foo"+-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"+-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"+-- > Windows: joinDrive "/:" "foo" == "/:\\foo"+#endif+joinDrive :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME+joinDrive (OSSTRING_NAME a) (OSSTRING_NAME b) = OSSTRING_NAME $ C.joinDrive a b+++-- | Get the drive from a filepath.+--+-- > takeDrive x == fst (splitDrive x)+takeDrive :: FILEPATH_NAME -> FILEPATH_NAME+takeDrive (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeDrive x+++-- | Delete the drive, if it exists.+--+-- > dropDrive x == snd (splitDrive x)+dropDrive :: FILEPATH_NAME -> FILEPATH_NAME+dropDrive (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropDrive x+++#ifdef WINDOWS_DOC+-- | Does a path have a drive.+--+-- > not (hasDrive x) == null (takeDrive x)+-- > hasDrive "C:\\foo" == True+-- > hasDrive "C:foo" == True+-- > hasDrive "foo" == False+-- > hasDrive "" == False+--+#elif defined(POSIX_DOC)+-- | Does a path have a drive.+--+-- > not (hasDrive x) == null (takeDrive x)+-- > hasDrive "/foo" == True+-- > hasDrive "foo" == False+-- > hasDrive "" == False+--+#else+-- | Does a path have a drive.+--+-- > not (hasDrive x) == null (takeDrive x)+-- > Posix:   hasDrive "/foo" == True+-- > Windows: hasDrive "C:\\foo" == True+-- > Windows: hasDrive "C:foo" == True+-- >          hasDrive "foo" == False+-- >          hasDrive "" == False+--+#endif+hasDrive :: FILEPATH_NAME -> Bool+hasDrive (OSSTRING_NAME x) = C.hasDrive x+++#ifdef WINDOWS_DOC+-- | Is an element a drive+--+-- > isDrive "C:\\" == True+-- > isDrive "C:\\foo" == False+-- > isDrive "" == False+#elif defined(POSIX_DOC)+-- | Is an element a drive+--+-- > isDrive "/" == True+-- > isDrive "/foo" == False+-- > isDrive "" == False+#else+-- | Is an element a drive+--+-- > Posix:   isDrive "/" == True+-- > Posix:   isDrive "/foo" == False+-- > Windows: isDrive "C:\\" == True+-- > Windows: isDrive "C:\\foo" == False+-- >          isDrive "" == False+#endif+isDrive :: FILEPATH_NAME -> Bool+isDrive (OSSTRING_NAME x) = C.isDrive x+++---------------------------------------------------------------------+-- Operations on a filepath, as a list of directories++#ifdef WINDOWS_DOC+-- | Split a filename into directory and file. '</>' is the inverse.+--   The first component will often end with a trailing slash.+--+-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")+-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"+-- > Valid x => isValid (fst (splitFileName x))+-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")+-- > splitFileName "file/" == ("file/", "")+-- > splitFileName "bob" == ("./", "bob")+-- > splitFileName "c:" == ("c:","")+#elif defined(POSIX_DOC)+-- | Split a filename into directory and file. '</>' is the inverse.+--   The first component will often end with a trailing slash.+--+-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")+-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"+-- > Valid x => isValid (fst (splitFileName x))+-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")+-- > splitFileName "file/" == ("file/", "")+-- > splitFileName "bob" == ("./", "bob")+-- > splitFileName "/" == ("/","")+#else+-- | Split a filename into directory and file. '</>' is the inverse.+--   The first component will often end with a trailing slash.+--+-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")+-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"+-- > Valid x => isValid (fst (splitFileName x))+-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")+-- > splitFileName "file/" == ("file/", "")+-- > splitFileName "bob" == ("./", "bob")+-- > Posix:   splitFileName "/" == ("/","")+-- > Windows: splitFileName "c:" == ("c:","")+#endif+splitFileName :: FILEPATH_NAME -> (FILEPATH_NAME, FILEPATH_NAME)+splitFileName (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitFileName x+++-- | Set the filename.+--+-- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"+-- > Valid x => replaceFileName x (takeFileName x) == x+replaceFileName :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+replaceFileName (OSSTRING_NAME x) (OSSTRING_NAME y) = OSSTRING_NAME $ C.replaceFileName x y+++-- | Drop the filename. Unlike 'takeDirectory', this function will leave+--   a trailing path separator on the directory.+--+-- > dropFileName "/directory/file.ext" == "/directory/"+-- > dropFileName x == fst (splitFileName x)+dropFileName :: FILEPATH_NAME -> FILEPATH_NAME+dropFileName (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropFileName x+++-- | Get the file name.+--+-- > takeFileName "/directory/file.ext" == "file.ext"+-- > takeFileName "test/" == ""+-- > takeFileName x `isSuffixOf` x+-- > takeFileName x == snd (splitFileName x)+-- > Valid x => takeFileName (replaceFileName x "fred") == "fred"+-- > Valid x => takeFileName (x </> "fred") == "fred"+-- > Valid x => isRelative (takeFileName x)+takeFileName :: FILEPATH_NAME -> FILEPATH_NAME+takeFileName (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeFileName x+++-- | Get the base name, without an extension or path.+--+-- > takeBaseName "/directory/file.ext" == "file"+-- > takeBaseName "file/test.txt" == "test"+-- > takeBaseName "dave.ext" == "dave"+-- > takeBaseName "" == ""+-- > takeBaseName "test" == "test"+-- > takeBaseName (addTrailingPathSeparator x) == ""+-- > takeBaseName "file/file.tar.gz" == "file.tar"+takeBaseName :: FILEPATH_NAME -> FILEPATH_NAME+takeBaseName (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeBaseName x+++-- | Set the base name.+--+-- > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"+-- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"+-- > replaceBaseName "fred" "bill" == "bill"+-- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"+-- > Valid x => replaceBaseName x (takeBaseName x) == x+replaceBaseName :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME+replaceBaseName (OSSTRING_NAME path) (OSSTRING_NAME name) = OSSTRING_NAME $ C.replaceBaseName path name+++-- | Is an item either a directory or the last character a path separator?+--+-- > hasTrailingPathSeparator "test" == False+-- > hasTrailingPathSeparator "test/" == True+hasTrailingPathSeparator :: FILEPATH_NAME -> Bool+hasTrailingPathSeparator (OSSTRING_NAME x) = C.hasTrailingPathSeparator x+++#ifdef WINDOWS_DOC+-- | Add a trailing file path separator if one is not already present.+--+-- > hasTrailingPathSeparator (addTrailingPathSeparator x)+-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x+#elif defined(POSIX_DOC)+-- | Add a trailing file path separator if one is not already present.+--+-- > hasTrailingPathSeparator (addTrailingPathSeparator x)+-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x+-- > addTrailingPathSeparator "test/rest" == "test/rest/"+#else+-- | Add a trailing file path separator if one is not already present.+--+-- > hasTrailingPathSeparator (addTrailingPathSeparator x)+-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x+-- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"+#endif+addTrailingPathSeparator :: FILEPATH_NAME -> FILEPATH_NAME+addTrailingPathSeparator (OSSTRING_NAME bs) = OSSTRING_NAME $ C.addTrailingPathSeparator bs+++#ifdef WINDOWS_DOC+-- | Remove any trailing path separators+--+-- > dropTrailingPathSeparator "file/test/" == "file/test"+-- > dropTrailingPathSeparator "/" == "/"+-- > dropTrailingPathSeparator "\\" == "\\"+#elif defined(POSIX_DOC)+-- | Remove any trailing path separators+--+-- > dropTrailingPathSeparator "file/test/" == "file/test"+-- > dropTrailingPathSeparator "/" == "/"+-- > not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x+#else+-- | Remove any trailing path separators+--+-- > dropTrailingPathSeparator "file/test/" == "file/test"+-- >           dropTrailingPathSeparator "/" == "/"+-- > Windows:  dropTrailingPathSeparator "\\" == "\\"+-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x+#endif+dropTrailingPathSeparator :: FILEPATH_NAME -> FILEPATH_NAME+dropTrailingPathSeparator (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropTrailingPathSeparator x+++#ifdef WINDOWS_DOC+-- | Get the directory name, move up one level.+--+-- > takeDirectory "/directory/other.ext" == "/directory"+-- > takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- > takeDirectory "foo" == "."+-- > takeDirectory "/" == "/"+-- > takeDirectory "/foo" == "/"+-- > takeDirectory "/foo/bar/baz" == "/foo/bar"+-- > takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"+-- > takeDirectory "foo/bar/baz" == "foo/bar"+-- > takeDirectory "foo\\bar" == "foo"+-- > takeDirectory "foo\\bar\\\\" == "foo\\bar"+-- > takeDirectory "C:\\" == "C:\\"+#elif defined(POSIX_DOC)+-- | Get the directory name, move up one level.+--+-- >           takeDirectory "/directory/other.ext" == "/directory"+-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- >           takeDirectory "foo" == "."+-- >           takeDirectory "/" == "/"+-- >           takeDirectory "/foo" == "/"+-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"+-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"+-- >           takeDirectory "foo/bar/baz" == "foo/bar"+#else+-- | Get the directory name, move up one level.+--+-- >           takeDirectory "/directory/other.ext" == "/directory"+-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- >           takeDirectory "foo" == "."+-- >           takeDirectory "/" == "/"+-- >           takeDirectory "/foo" == "/"+-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"+-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"+-- >           takeDirectory "foo/bar/baz" == "foo/bar"+-- > Windows:  takeDirectory "foo\\bar" == "foo"+-- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"+-- > Windows:  takeDirectory "C:\\" == "C:\\"+#endif+takeDirectory :: FILEPATH_NAME -> FILEPATH_NAME+takeDirectory (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeDirectory x+++-- | Set the directory, keeping the filename the same.+--+-- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"+-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x+replaceDirectory :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME+replaceDirectory (OSSTRING_NAME file) (OSSTRING_NAME dir) = OSSTRING_NAME $ C.replaceDirectory file dir+++-- | An alias for '</>'.+combine :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME+combine (OSSTRING_NAME a) (OSSTRING_NAME b) = OSSTRING_NAME $ C.combine a b++#ifdef WINDOWS_DOC+-- | Combine two paths with a path separator.+--   If the second path starts with a path separator or a drive letter, then it returns the second.+--   The intention is that @readFile (dir '</>' file)@ will access the same file as+--   @setCurrentDirectory dir; readFile file@.+--+-- > "/directory" </> "file.ext" == "/directory\\file.ext"+-- > "directory" </> "/file.ext" == "/file.ext"+-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x+--+--   Combined:+--+-- > "C:\\foo" </> "bar" == "C:\\foo\\bar"+-- > "home" </> "bob" == "home\\bob"+--+--   Not combined:+--+-- > "home" </> "C:\\bob" == "C:\\bob"+--+--   Not combined (tricky):+--+--   If a filepath starts with a single slash, it is relative to the+--   root of the current drive. In [1], this is (confusingly) referred to as an+--   absolute path.+--   The current behavior of '</>' is to never combine these forms.+--+-- > "home" </> "/bob" == "/bob"+-- > "home" </> "\\bob" == "\\bob"+-- > "C:\\home" </> "\\bob" == "\\bob"+--+--   From [1]: "If a file name begins with only a disk designator+--   but not the backslash after the colon, it is interpreted as a relative path+--   to the current directory on the drive with the specified letter."+--   The current behavior of '</>' is to never combine these forms.+--+-- > "D:\\foo" </> "C:bar" == "C:bar"+-- > "C:\\foo" </> "C:bar" == "C:bar"+#elif defined(POSIX_DOC)+-- | Combine two paths with a path separator.+--   If the second path starts with a path separator or a drive letter, then it returns the second.+--   The intention is that @readFile (dir '</>' file)@ will access the same file as+--   @setCurrentDirectory dir; readFile file@.+--+-- > "/directory" </> "file.ext" == "/directory/file.ext"+-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x+--+--   Combined:+--+-- > "/" </> "test" == "/test"+-- > "home" </> "bob" == "home/bob"+-- > "x:" </> "foo" == "x:/foo"+--+--   Not combined:+--+-- > "home" </> "/bob" == "/bob"+#else+-- | Combine two paths with a path separator.+--   If the second path starts with a path separator or a drive letter, then it returns the second.+--   The intention is that @readFile (dir '</>' file)@ will access the same file as+--   @setCurrentDirectory dir; readFile file@.+--+-- > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"+-- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"+-- >          "directory" </> "/file.ext" == "/file.ext"+-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x+--+--   Combined:+--+-- > Posix:   "/" </> "test" == "/test"+-- > Posix:   "home" </> "bob" == "home/bob"+-- > Posix:   "x:" </> "foo" == "x:/foo"+-- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"+-- > Windows: "home" </> "bob" == "home\\bob"+--+--   Not combined:+--+-- > Posix:   "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "C:\\bob" == "C:\\bob"+--+--   Not combined (tricky):+--+--   On Windows, if a filepath starts with a single slash, it is relative to the+--   root of the current drive. In [1], this is (confusingly) referred to as an+--   absolute path.+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "\\bob" == "\\bob"+-- > Windows: "C:\\home" </> "\\bob" == "\\bob"+--+--   On Windows, from [1]: "If a file name begins with only a disk designator+--   but not the backslash after the colon, it is interpreted as a relative path+--   to the current directory on the drive with the specified letter."+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "D:\\foo" </> "C:bar" == "C:bar"+-- > Windows: "C:\\foo" </> "C:bar" == "C:bar"+#endif+(</>) :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME+(</>) = combine+++#ifdef WINDOWS_DOC+-- | Split a path by the directory separator.+--+-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]+-- > concat (splitPath x) == x+-- > splitPath "test//item/" == ["test//","item/"]+-- > splitPath "test/item/file" == ["test/","item/","file"]+-- > splitPath "" == []+-- > splitPath "c:\\test\\path" == ["c:\\","test\\","path"]+#elif defined(POSIX_DOC)+-- | Split a path by the directory separator.+--+-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]+-- > concat (splitPath x) == x+-- > splitPath "test//item/" == ["test//","item/"]+-- > splitPath "test/item/file" == ["test/","item/","file"]+-- > splitPath "" == []+-- > splitPath "/file/test" == ["/","file/","test"]+#else+-- | Split a path by the directory separator.+--+-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]+-- > concat (splitPath x) == x+-- > splitPath "test//item/" == ["test//","item/"]+-- > splitPath "test/item/file" == ["test/","item/","file"]+-- > splitPath "" == []+-- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]+-- > Posix:   splitPath "/file/test" == ["/","file/","test"]+#endif+splitPath :: FILEPATH_NAME -> [FILEPATH_NAME]+splitPath (OSSTRING_NAME bs) = OSSTRING_NAME <$> C.splitPath bs++#ifdef WINDOWS_DOC+-- | Just as 'splitPath', but don't add the trailing slashes to each element.+--+-- > splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]+-- > splitDirectories "test/file" == ["test","file"]+-- > splitDirectories "/test/file" == ["/","test","file"]+-- > splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]+-- > Valid x => joinPath (splitDirectories x) `equalFilePath` x+-- > splitDirectories "" == []+-- > splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]+-- > splitDirectories "/test///file" == ["/","test","file"]+#elif defined(POSIX_DOC)+-- | Just as 'splitPath', but don't add the trailing slashes to each element.+--+-- > splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]+-- > splitDirectories "test/file" == ["test","file"]+-- > splitDirectories "/test/file" == ["/","test","file"]+-- > Valid x => joinPath (splitDirectories x) `equalFilePath` x+-- > splitDirectories "" == []+-- > splitDirectories "/test///file" == ["/","test","file"]+#else+-- | Just as 'splitPath', but don't add the trailing slashes to each element.+--+-- >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]+-- >          splitDirectories "test/file" == ["test","file"]+-- >          splitDirectories "/test/file" == ["/","test","file"]+-- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]+-- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x+-- >          splitDirectories "" == []+-- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]+-- >          splitDirectories "/test///file" == ["/","test","file"]+#endif+splitDirectories :: FILEPATH_NAME -> [FILEPATH_NAME]+splitDirectories (OSSTRING_NAME x) = OSSTRING_NAME <$> C.splitDirectories x++#ifdef WINDOWS_DOC+-- | Join path elements back together.+--+-- > joinPath z == foldr (</>) "" z+-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"+-- > Valid x => joinPath (splitPath x) == x+-- > joinPath [] == ""+#elif defined(POSIX_DOC)+-- | Join path elements back together.+--+-- > joinPath z == foldr (</>) "" z+-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"+-- > Valid x => joinPath (splitPath x) == x+-- > joinPath [] == ""+-- > joinPath ["test","file","path"] == "test/file/path"+#else+-- | Join path elements back together.+--+-- > joinPath z == foldr (</>) "" z+-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"+-- > Valid x => joinPath (splitPath x) == x+-- > joinPath [] == ""+-- > Posix: joinPath ["test","file","path"] == "test/file/path"+#endif+joinPath :: [FILEPATH_NAME] -> FILEPATH_NAME+joinPath = foldr (</>) (OSSTRING_NAME mempty)++++++++++------------------------+-- File name manipulations+++#ifdef WINDOWS_DOC+-- | Equality of two filepaths.+--   If you call @System.Directory.canonicalizePath@+--   first this has a much better chance of working.+--   Note that this doesn't follow symlinks or DOSNAM~1s.+--+-- Similar to 'normalise', this does not expand @".."@, because of symlinks.+--+-- > x == y ==> equalFilePath x y+-- > normalise x == normalise y ==> equalFilePath x y+-- > equalFilePath "foo" "foo/"+-- > not (equalFilePath "/a/../c" "/c")+-- > not (equalFilePath "foo" "/foo")+-- > equalFilePath "foo" "FOO"+-- > not (equalFilePath "C:" "C:/")+#elif defined(POSIX_DOC)+-- | Equality of two filepaths.+--   If you call @System.Directory.canonicalizePath@+--   first this has a much better chance of working.+--   Note that this doesn't follow symlinks or DOSNAM~1s.+--+-- Similar to 'normalise', this does not expand @".."@, because of symlinks.+--+-- > x == y ==> equalFilePath x y+-- > normalise x == normalise y ==> equalFilePath x y+-- > equalFilePath "foo" "foo/"+-- > not (equalFilePath "/a/../c" "/c")+-- > not (equalFilePath "foo" "/foo")+-- > not (equalFilePath "foo" "FOO")+#else+-- | Equality of two filepaths.+--   If you call @System.Directory.canonicalizePath@+--   first this has a much better chance of working.+--   Note that this doesn't follow symlinks or DOSNAM~1s.+--+-- Similar to 'normalise', this does not expand @".."@, because of symlinks.+--+-- >          x == y ==> equalFilePath x y+-- >          normalise x == normalise y ==> equalFilePath x y+-- >          equalFilePath "foo" "foo/"+-- >          not (equalFilePath "/a/../c" "/c")+-- >          not (equalFilePath "foo" "/foo")+-- > Posix:   not (equalFilePath "foo" "FOO")+-- > Windows: equalFilePath "foo" "FOO"+-- > Windows: not (equalFilePath "C:" "C:/")+#endif+equalFilePath :: FILEPATH_NAME -> FILEPATH_NAME -> Bool+equalFilePath (OSSTRING_NAME p1) (OSSTRING_NAME p2) = C.equalFilePath p1 p2++#ifdef WINDOWS_DOC+-- | Contract a filename, based on a relative path. Note that the resulting path+--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@+--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see+--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.+--+--   The corresponding @makeAbsolute@ function can be found in+--   @System.Directory@.+--+-- > makeRelative "/directory" "/directory/file.ext" == "file.ext"+-- > Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x+-- > makeRelative x x == "."+-- > Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x+-- > makeRelative "C:\\Home" "c:\\home\\bob" == "bob"+-- > makeRelative "C:\\Home" "c:/home/bob" == "bob"+-- > makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"+-- > makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"+-- > makeRelative "/Home" "/home/bob" == "bob"+-- > makeRelative "/" "//" == "//"+#elif defined(POSIX_DOC)+-- | Contract a filename, based on a relative path. Note that the resulting path+--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@+--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see+--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.+--+--   The corresponding @makeAbsolute@ function can be found in+--   @System.Directory@.+--+-- > makeRelative "/directory" "/directory/file.ext" == "file.ext"+-- > Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x+-- > makeRelative x x == "."+-- > Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x+-- > makeRelative "/Home" "/home/bob" == "/home/bob"+-- > makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"+-- > makeRelative "/fred" "bob" == "bob"+-- > makeRelative "/file/test" "/file/test/fred" == "fred"+-- > makeRelative "/file/test" "/file/test/fred/" == "fred/"+-- > makeRelative "some/path" "some/path/a/b/c" == "a/b/c"+#else+-- | Contract a filename, based on a relative path. Note that the resulting path+--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@+--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see+--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.+--+--   The corresponding @makeAbsolute@ function can be found in+--   @System.Directory@.+--+-- >          makeRelative "/directory" "/directory/file.ext" == "file.ext"+-- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x+-- >          makeRelative x x == "."+-- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x+-- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"+-- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"+-- > Windows: makeRelative "/Home" "/home/bob" == "bob"+-- > Windows: makeRelative "/" "//" == "//"+-- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"+-- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"+-- > Posix:   makeRelative "/fred" "bob" == "bob"+-- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"+-- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"+-- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"+#endif+makeRelative :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME+makeRelative (OSSTRING_NAME root) (OSSTRING_NAME path) = OSSTRING_NAME $ C.makeRelative root path++#ifdef WINDOWS_DOC+-- | Normalise a file+--+-- * \/\/ outside of the drive can be made blank+--+-- * \/ -> 'pathSeparator'+--+-- * .\/ -> \"\"+--+-- Does not remove @".."@, because of symlinks.+--+-- > normalise "c:\\file/bob\\" == "C:\\file\\bob\\"+-- > normalise "c:\\" == "C:\\"+-- > normalise "C:.\\" == "C:"+-- > normalise "\\\\server\\test" == "\\\\server\\test"+-- > normalise "//server/test" == "\\\\server\\test"+-- > normalise "c:/file" == "C:\\file"+-- > normalise "/file" == "\\file"+-- > normalise "\\" == "\\"+-- > normalise "/./" == "\\"+-- > normalise "." == "."+#elif defined(POSIX_DOC)+-- | Normalise a file+--+-- * \/\/ outside of the drive can be made blank+--+-- * \/ -> 'pathSeparator'+--+-- * .\/ -> \"\"+--+-- Does not remove @".."@, because of symlinks.+--+-- > normalise "/file/\\test////" == "/file/\\test/"+-- > normalise "/file/./test" == "/file/test"+-- > normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"+-- > normalise "../bob/fred/" == "../bob/fred/"+-- > normalise "/a/../c" == "/a/../c"+-- > normalise "./bob/fred/" == "bob/fred/"+-- > normalise "." == "."+-- > normalise "./" == "./"+-- > normalise "./." == "./"+-- > normalise "/./" == "/"+-- > normalise "/" == "/"+-- > normalise "bob/fred/." == "bob/fred/"+-- > normalise "//home" == "/home"+#else+-- | Normalise a file+--+-- * \/\/ outside of the drive can be made blank+--+-- * \/ -> 'pathSeparator'+--+-- * .\/ -> \"\"+--+-- Does not remove @".."@, because of symlinks.+--+-- > Posix:   normalise "/file/\\test////" == "/file/\\test/"+-- > Posix:   normalise "/file/./test" == "/file/test"+-- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"+-- > Posix:   normalise "../bob/fred/" == "../bob/fred/"+-- > Posix:   normalise "/a/../c" == "/a/../c"+-- > Posix:   normalise "./bob/fred/" == "bob/fred/"+-- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"+-- > Windows: normalise "c:\\" == "C:\\"+-- > Windows: normalise "C:.\\" == "C:"+-- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"+-- > Windows: normalise "//server/test" == "\\\\server\\test"+-- > Windows: normalise "c:/file" == "C:\\file"+-- > Windows: normalise "/file" == "\\file"+-- > Windows: normalise "\\" == "\\"+-- > Windows: normalise "/./" == "\\"+-- >          normalise "." == "."+-- > Posix:   normalise "./" == "./"+-- > Posix:   normalise "./." == "./"+-- > Posix:   normalise "/./" == "/"+-- > Posix:   normalise "/" == "/"+-- > Posix:   normalise "bob/fred/." == "bob/fred/"+-- > Posix:   normalise "//home" == "/home"+#endif+normalise :: FILEPATH_NAME -> FILEPATH_NAME+normalise (OSSTRING_NAME filepath) = OSSTRING_NAME $ C.normalise filepath+++#ifdef WINDOWS_DOC+-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,+--   and invalid characters, but does not check if length limits are exceeded, as these are typically+--   filesystem dependent.+--+-- > isValid "" == False+-- > isValid "\0" == False+-- > isValid "c:\\test" == True+-- > isValid "c:\\test:of_test" == False+-- > isValid "test*" == False+-- > isValid "c:\\test\\nul" == False+-- > isValid "c:\\test\\prn.txt" == False+-- > isValid "c:\\nul\\file" == False+-- > isValid "\\\\" == False+-- > isValid "\\\\\\foo" == False+-- > isValid "\\\\?\\D:file" == False+-- > isValid "foo\tbar" == False+-- > isValid "nul .txt" == False+-- > isValid " nul.txt" == True+#elif defined(POSIX_DOC)+-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,+--   and invalid characters, but does not check if length limits are exceeded, as these are typically+--   filesystem dependent.+--+-- > isValid "" == False+-- > isValid "\0" == False+-- > isValid "/random_ path:*" == True+-- > isValid x == not (null x)+#else+-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,+--   and invalid characters, but does not check if length limits are exceeded, as these are typically+--   filesystem dependent.+--+-- >          isValid "" == False+-- >          isValid "\0" == False+-- > Posix:   isValid "/random_ path:*" == True+-- > Posix:   isValid x == not (null x)+-- > Windows: isValid "c:\\test" == True+-- > Windows: isValid "c:\\test:of_test" == False+-- > Windows: isValid "test*" == False+-- > Windows: isValid "c:\\test\\nul" == False+-- > Windows: isValid "c:\\test\\prn.txt" == False+-- > Windows: isValid "c:\\nul\\file" == False+-- > Windows: isValid "\\\\" == False+-- > Windows: isValid "\\\\\\foo" == False+-- > Windows: isValid "\\\\?\\D:file" == False+-- > Windows: isValid "foo\tbar" == False+-- > Windows: isValid "nul .txt" == False+-- > Windows: isValid " nul.txt" == True+#endif+isValid :: FILEPATH_NAME -> Bool+isValid (OSSTRING_NAME filepath) = C.isValid filepath+++#ifdef WINDOWS_DOC+-- | Take a filepath and make it valid; does not change already valid filepaths.+--+-- > isValid (makeValid x)+-- > isValid x ==> makeValid x == x+-- > makeValid "" == "_"+-- > makeValid "file\0name" == "file_name"+-- > makeValid "c:\\already\\/valid" == "c:\\already\\/valid"+-- > makeValid "c:\\test:of_test" == "c:\\test_of_test"+-- > makeValid "test*" == "test_"+-- > makeValid "c:\\test\\nul" == "c:\\test\\nul_"+-- > makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"+-- > makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"+-- > makeValid "c:\\nul\\file" == "c:\\nul_\\file"+-- > makeValid "\\\\\\foo" == "\\\\drive"+-- > makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"+-- > makeValid "nul .txt" == "nul _.txt"+#elif defined(POSIX_DOC)+-- | Take a filepath and make it valid; does not change already valid filepaths.+--+-- > isValid (makeValid x)+-- > isValid x ==> makeValid x == x+-- > makeValid "" == "_"+-- > makeValid "file\0name" == "file_name"+#else+-- | Take a filepath and make it valid; does not change already valid filepaths.+--+-- > isValid (makeValid x)+-- > isValid x ==> makeValid x == x+-- > makeValid "" == "_"+-- > makeValid "file\0name" == "file_name"+-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"+-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"+-- > Windows: makeValid "test*" == "test_"+-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"+-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"+-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"+-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"+-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"+-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"+-- > Windows: makeValid "nul .txt" == "nul _.txt"+#endif+makeValid :: FILEPATH_NAME -> FILEPATH_NAME+makeValid (OSSTRING_NAME path) = OSSTRING_NAME $ C.makeValid path+++#ifdef WINDOWS_DOC+-- | Is a path relative, or is it fixed to the root?+--+-- > isRelative "path\\test" == True+-- > isRelative "c:\\test" == False+-- > isRelative "c:test" == True+-- > isRelative "c:\\" == False+-- > isRelative "c:/" == False+-- > isRelative "c:" == True+-- > isRelative "\\\\foo" == False+-- > isRelative "\\\\?\\foo" == False+-- > isRelative "\\\\?\\UNC\\foo" == False+-- > isRelative "/foo" == True+-- > isRelative "\\foo" == True+--+-- According to [1]:+--+-- * "A UNC name of any format [is never relative]."+--+-- * "You cannot use the "\\?\" prefix with a relative path."+#elif defined(POSIX_DOC)+-- | Is a path relative, or is it fixed to the root?+--+-- > isRelative "test/path" == True+-- > isRelative "/test" == False+-- > isRelative "/" == False+#else+-- | Is a path relative, or is it fixed to the root?+--+-- > Windows: isRelative "path\\test" == True+-- > Windows: isRelative "c:\\test" == False+-- > Windows: isRelative "c:test" == True+-- > Windows: isRelative "c:\\" == False+-- > Windows: isRelative "c:/" == False+-- > Windows: isRelative "c:" == True+-- > Windows: isRelative "\\\\foo" == False+-- > Windows: isRelative "\\\\?\\foo" == False+-- > Windows: isRelative "\\\\?\\UNC\\foo" == False+-- > Windows: isRelative "/foo" == True+-- > Windows: isRelative "\\foo" == True+-- > Posix:   isRelative "test/path" == True+-- > Posix:   isRelative "/test" == False+-- > Posix:   isRelative "/" == False+--+-- According to [1]:+--+-- * "A UNC name of any format [is never relative]."+--+-- * "You cannot use the "\\?\" prefix with a relative path."+#endif+isRelative :: FILEPATH_NAME -> Bool+isRelative (OSSTRING_NAME x) = C.isRelative x+++-- | @not . 'isRelative'@+--+-- > isAbsolute x == not (isRelative x)+isAbsolute :: FILEPATH_NAME -> Bool+isAbsolute (OSSTRING_NAME x) = C.isAbsolute x
+ System/OsPath/Data/ByteString/Short.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE NoImplicitPrelude #-}+-- |+-- Module      : System.OsPath.Data.ByteString.Short+-- Copyright   : (c) Duncan Coutts 2012-2013, Julian Ospald 2022+-- License     : BSD-style+--+-- Maintainer  : hasufell@posteo.de+-- Stability   : stable+-- Portability : ghc only+--+-- A compact representation suitable for storing short byte strings in memory.+--+-- In typical use cases it can be imported alongside "Data.ByteString", e.g.+--+-- > import qualified Data.ByteString       as B+-- > import qualified Data.ByteString.Short as B+-- >          (ShortByteString, toShort, fromShort)+--+-- Other 'ShortByteString' operations clash with "Data.ByteString" or "Prelude"+-- functions however, so they should be imported @qualified@ with a different+-- alias e.g.+--+-- > import qualified Data.ByteString.Short as B.Short+--+module System.OsPath.Data.ByteString.Short (++    -- * The @ShortByteString@ type++    ShortByteString(..),++    -- ** Memory overhead+    -- | With GHC, the memory overheads are as follows, expressed in words and+    -- in bytes (words are 4 and 8 bytes on 32 or 64bit machines respectively).+    --+    -- * 'B.ByteString' unshared: 8 words; 32 or 64 bytes.+    --+    -- * 'B.ByteString' shared substring: 4 words; 16 or 32 bytes.+    --+    -- * 'ShortByteString': 4 words; 16 or 32 bytes.+    --+    -- For the string data itself, both 'ShortByteString' and 'B.ByteString' use+    -- one byte per element, rounded up to the nearest word. For example,+    -- including the overheads, a length 10 'ShortByteString' would take+    -- @16 + 12 = 28@ bytes on a 32bit platform and @32 + 16 = 48@ bytes on a+    -- 64bit platform.+    --+    -- These overheads can all be reduced by 1 word (4 or 8 bytes) when the+    -- 'ShortByteString' or 'B.ByteString' is unpacked into another constructor.+    --+    -- For example:+    --+    -- > data ThingId = ThingId {-# UNPACK #-} !Int+    -- >                        {-# UNPACK #-} !ShortByteString+    --+    -- This will take @1 + 1 + 3@ words (the @ThingId@ constructor ++    -- unpacked @Int@ + unpacked @ShortByteString@), plus the words for the+    -- string data.++    -- ** Heap fragmentation+    -- | With GHC, the 'B.ByteString' representation uses /pinned/ memory,+    -- meaning it cannot be moved by the GC. This is usually the right thing to+    -- do for larger strings, but for small strings using pinned memory can+    -- lead to heap fragmentation which wastes space. The 'ShortByteString'+    -- type (and the @Text@ type from the @text@ package) use /unpinned/ memory+    -- so they do not contribute to heap fragmentation. In addition, with GHC,+    -- small unpinned strings are allocated in the same way as normal heap+    -- allocations, rather than in a separate pinned area.++    -- * Introducing and eliminating 'ShortByteString's+    empty,+    singleton,+    pack,+    unpack,+    fromShort,+    toShort,++    -- * Basic interface+    snoc,+    cons,+    append,+    last,+    tail,+    uncons,+    head,+    init,+    unsnoc,+    null,+    length,++    -- * Transforming ShortByteStrings+    map,+    reverse,+    intercalate,++    -- * Reducing 'ShortByteString's (folds)+    foldl,+    foldl',+    foldl1,+    foldl1',++    foldr,+    foldr',+    foldr1,+    foldr1',++    -- ** Special folds+    all,+    any,+    concat,++    -- ** Generating and unfolding ByteStrings+    replicate,+    unfoldr,+    unfoldrN,++    -- * Substrings++    -- ** Breaking strings+    take,+    takeEnd,+    takeWhileEnd,+    takeWhile,+    drop,+    dropEnd,+    dropWhile,+    dropWhileEnd,+    breakEnd,+    break,+    span,+    spanEnd,+    splitAt,+    split,+    splitWith,+    stripSuffix,+    stripPrefix,++    -- * Predicates+    isInfixOf,+    isPrefixOf,+    isSuffixOf,++    -- ** Search for arbitrary substrings+    breakSubstring,++    -- * Searching ShortByteStrings++    -- ** Searching by equality+    elem,++    -- ** Searching with a predicate+    find,+    filter,+    partition,++    -- * Indexing ShortByteStrings+    index,+    indexMaybe,+    (!?),+    elemIndex,+    elemIndices,+    count,+    findIndex,+    findIndices,++    -- * Low level conversions+    -- ** Packing 'Foreign.C.String.CString's and pointers+    packCString,+    packCStringLen,++    -- ** Using ShortByteStrings as 'Foreign.C.String.CString's+    useAsCString,+    useAsCStringLen,+  ) where++import Data.ByteString.Short.Internal
+ System/OsPath/Data/ByteString/Short/Internal.hs view
@@ -0,0 +1,436 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ViewPatterns #-}++-- |+-- Module      :  System.OsPath.Data.ByteString.Short.Internal+-- Copyright   :  © 2022 Julian Ospald+-- License     :  MIT+--+-- Maintainer  :  Julian Ospald <hasufell@posteo.de>+-- Stability   :  experimental+-- Portability :  portable+--+-- Internal low-level utilities mostly for 'System.OsPath.Data.ByteString.Short.Word16',+-- such as byte-array operations and other stuff not meant to be exported from Word16 module.+module System.OsPath.Data.ByteString.Short.Internal where++import Control.Monad.ST+import Control.Exception (assert, throwIO)+import Data.ByteString.Short.Internal (ShortByteString(..), length)+#if !MIN_VERSION_base(4,11,0)+import Data.Semigroup+  ( Semigroup((<>)) )+#endif+#if !MIN_VERSION_bytestring(0,10,9)+import Foreign.Marshal.Alloc (allocaBytes)+import Foreign.C.String ( CString, CStringLen )+import Foreign.C.Types ( CSize(..) )+import Foreign.Storable (pokeByteOff)+#endif+import Foreign.Marshal.Array (withArray0, peekArray0, newArray0, withArrayLen, peekArray)+import GHC.Exts+import GHC.Word+import GHC.ST+    ( ST (ST) )+import GHC.Stack ( HasCallStack )+import Prelude hiding+    ( length )++import qualified Data.ByteString.Short.Internal as BS+import qualified Data.Char as C+import qualified Data.List as List+++_nul :: Word16+_nul = 0x00++isSpace :: Word16 -> Bool+isSpace = C.isSpace . word16ToChar++-- | Total conversion to char.+word16ToChar :: Word16 -> Char+word16ToChar = C.chr . fromIntegral++create :: Int -> (forall s. MBA s -> ST s ()) -> ShortByteString+create len fill =+    runST $ do+      mba <- newByteArray len+      fill mba+      BA# ba# <- unsafeFreezeByteArray mba+      return (SBS ba#)+{-# INLINE create #-}+++asBA :: ShortByteString -> BA+asBA (SBS ba#) = BA# ba#++++data BA    = BA# ByteArray#+data MBA s = MBA# (MutableByteArray# s)+++newPinnedByteArray :: Int -> ST s (MBA s)+newPinnedByteArray (I# len#) =+    ST $ \s -> case newPinnedByteArray# len# s of+                 (# s', mba# #) -> (# s', MBA# mba# #)++newByteArray :: Int -> ST s (MBA s)+newByteArray (I# len#) =+    ST $ \s -> case newByteArray# len# s of+                 (# s', mba# #) -> (# s', MBA# mba# #)++copyByteArray :: BA -> Int -> MBA s -> Int -> Int -> ST s ()+copyByteArray (BA# src#) (I# src_off#) (MBA# dst#) (I# dst_off#) (I# len#) =+    ST $ \s -> case copyByteArray# src# src_off# dst# dst_off# len# s of+                 s' -> (# s', () #)++unsafeFreezeByteArray :: MBA s -> ST s BA+unsafeFreezeByteArray (MBA# mba#) =+    ST $ \s -> case unsafeFreezeByteArray# mba# s of+                 (# s', ba# #) -> (# s', BA# ba# #)++copyAddrToByteArray :: Ptr a -> MBA RealWorld -> Int -> Int -> ST RealWorld ()+copyAddrToByteArray (Ptr src#) (MBA# dst#) (I# dst_off#) (I# len#) =+    ST $ \s -> case copyAddrToByteArray# src# dst# dst_off# len# s of+                 s' -> (# s', () #)+++-- this is a copy-paste from bytestring+#if !MIN_VERSION_bytestring(0,10,9)+------------------------------------------------------------------------+-- Primop replacements++-- ---------------------------------------------------------------------+--+-- Standard C functions+--++foreign import ccall unsafe "string.h strlen" c_strlen+    :: CString -> IO CSize+++-- ---------------------------------------------------------------------+--+-- Uses our C code+--++-- | /O(n)./ Construct a new @ShortByteString@ from a @CString@. The+-- resulting @ShortByteString@ is an immutable copy of the original+-- @CString@, and is managed on the Haskell heap. The original+-- @CString@ must be null terminated.+--+-- @since 0.10.10.0+packCString :: CString -> IO ShortByteString+packCString cstr = do+  len <- c_strlen cstr+  packCStringLen (cstr, fromIntegral len)++-- | /O(n)./ Construct a new @ShortByteString@ from a @CStringLen@. The+-- resulting @ShortByteString@ is an immutable copy of the original @CStringLen@.+-- The @ShortByteString@ is a normal Haskell value and will be managed on the+-- Haskell heap.+--+-- @since 0.10.10.0+packCStringLen :: CStringLen -> IO ShortByteString+packCStringLen (cstr, len) | len >= 0 = BS.createFromPtr cstr len+packCStringLen (_, len) =+  moduleErrorIO "packCStringLen" ("negative length: " ++ show len)++-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a+-- null-terminated @CString@.  The @CString@ is a copy and will be freed+-- automatically; it must not be stored or used after the+-- subcomputation finishes.+--+-- @since 0.10.10.0+useAsCString :: ShortByteString -> (CString -> IO a) -> IO a+useAsCString bs action =+  allocaBytes (l+1) $ \buf -> do+      BS.copyToPtr bs 0 buf (fromIntegral l)+      pokeByteOff buf l (0::Word8)+      action buf+  where l = length bs++-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a @CStringLen@.+-- As for @useAsCString@ this function makes a copy of the original @ShortByteString@.+-- It must not be stored or used after the subcomputation finishes.+--+-- @since 0.10.10.0+useAsCStringLen :: ShortByteString -> (CStringLen -> IO a) -> IO a+useAsCStringLen bs action =+  allocaBytes l $ \buf -> do+      BS.copyToPtr bs 0 buf (fromIntegral l)+      action (buf, l)+  where l = length bs+++#endif+++-- | /O(n)./ Construct a new @ShortByteString@ from a @CWString@. The+-- resulting @ShortByteString@ is an immutable copy of the original+-- @CWString@, and is managed on the Haskell heap. The original+-- @CWString@ must be null terminated.+--+-- @since 0.10.10.0+packCWString :: Ptr Word16 -> IO ShortByteString+packCWString cwstr = do+  cs <- peekArray0 _nul cwstr+  return (packWord16 cs)++-- | /O(n)./ Construct a new @ShortByteString@ from a @CWStringLen@. The+-- resulting @ShortByteString@ is an immutable copy of the original @CWStringLen@.+-- The @ShortByteString@ is a normal Haskell value and will be managed on the+-- Haskell heap.+--+-- @since 0.10.10.0+packCWStringLen :: (Ptr Word16, Int) -> IO ShortByteString+packCWStringLen (cp, len) = do+  cs <- peekArray len cp+  return (packWord16 cs)+++-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a+-- null-terminated @CWString@.  The @CWString@ is a copy and will be freed+-- automatically; it must not be stored or used after the+-- subcomputation finishes.+--+-- @since 0.10.10.0+useAsCWString :: ShortByteString -> (Ptr Word16 -> IO a) -> IO a+useAsCWString = withArray0 _nul . unpackWord16++-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a @CWStringLen@.+-- As for @useAsCWString@ this function makes a copy of the original @ShortByteString@.+-- It must not be stored or used after the subcomputation finishes.+--+-- @since 0.10.10.0+useAsCWStringLen :: ShortByteString -> ((Ptr Word16, Int) -> IO a) -> IO a+useAsCWStringLen bs action = withArrayLen (unpackWord16 bs) $ \ len ptr -> action (ptr, len)++-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a @CWStringLen@.+-- As for @useAsCWString@ this function makes a copy of the original @ShortByteString@.+-- It must not be stored or used after the subcomputation finishes.+--+-- @since 0.10.10.0+newCWString :: ShortByteString -> IO (Ptr Word16)+newCWString = newArray0 _nul . unpackWord16+++++ -- ---------------------------------------------------------------------+-- Internal utilities++moduleErrorIO :: String -> String -> IO a+moduleErrorIO fun msg = throwIO . userError $ moduleErrorMsg fun msg+{-# NOINLINE moduleErrorIO #-}++moduleErrorMsg :: String -> String -> String+moduleErrorMsg fun msg = "System.OsPath.Data.ByteString.Short." ++ fun ++ ':':' ':msg++packWord16 :: [Word16] -> ShortByteString+packWord16 cs = packLenWord16 (List.length cs) cs++packLenWord16 :: Int -> [Word16] -> ShortByteString+packLenWord16 len ws0 =+    create (len * 2) (\mba -> go mba 0 ws0)+  where+    go :: MBA s -> Int -> [Word16] -> ST s ()+    go !_   !_ []     = return ()+    go !mba !i (w:ws) = do+      writeWord16Array mba i w+      go mba (i+2) ws+++unpackWord16 :: ShortByteString -> [Word16]+unpackWord16 sbs = go len []+  where+    len = length sbs+    go !i !acc+      | i < 1     = acc+      | otherwise = let !w = indexWord16Array (asBA sbs) (i - 2)+                    in go (i - 2) (w:acc)++packWord16Rev :: [Word16] -> ShortByteString+packWord16Rev cs = packLenWord16Rev (List.length cs * 2) cs++packLenWord16Rev :: Int -> [Word16] -> ShortByteString+packLenWord16Rev len ws0 =+    create len (\mba -> go mba len ws0)+  where+    go :: MBA s -> Int -> [Word16] -> ST s ()+    go !_   !_ []     = return ()+    go !mba !i (w:ws) = do+      writeWord16Array mba (i - 2) w+      go mba (i - 2) ws+++-- | This isn't strictly Word16 array write. Instead it's two consecutive Word8 array+-- writes to avoid endianness issues due to primops doing automatic alignment based+-- on host platform. We want to always write LE to the byte array.+writeWord16Array :: MBA s+                 -> Int      -- ^ Word8 index (not Word16)+                 -> Word16+                 -> ST s ()+writeWord16Array (MBA# mba#) (I# i#) (W16# w#) =+  case encodeWord16LE# w# of+    (# lsb#, msb# #) ->+      ST (\s -> case writeWord8Array# mba# i# lsb# s of+          s' -> (# s', () #)) >>+      ST (\s -> case writeWord8Array# mba# (i# +# 1#) msb# s of+          s' -> (# s', () #))++-- | This isn't strictly Word16 array read. Instead it's two Word8 array reads+-- to avoid endianness issues due to primops doing automatic alignment based+-- on host platform. We expect the byte array to be LE always.+indexWord16Array :: BA+                 -> Int      -- ^ Word8 index (not Word16)+                 -> Word16+indexWord16Array (BA# ba#) (I# i#) =+  case (# indexWord8Array# ba# i#, indexWord8Array# ba# (i# +# 1#) #) of+    (# lsb#, msb# #) -> W16# (decodeWord16LE# (# lsb#, msb# #))++#if !MIN_VERSION_base(4,16,0)++encodeWord16LE# :: Word#              -- ^ Word16+                -> (# Word#, Word# #) -- ^ Word8 (LSB, MSB)+encodeWord16LE# x# = (# x# `and#` int2Word# 0xff#+                     ,  x# `and#` int2Word# 0xff00# `shiftRL#` 8# #)++decodeWord16LE# :: (# Word#, Word# #) -- ^ Word8 (LSB, MSB)+                -> Word#              -- ^ Word16+decodeWord16LE# (# lsb#, msb# #) = msb# `shiftL#` 8# `or#` lsb#++#else++encodeWord16LE# :: Word16#              -- ^ Word16+                -> (# Word8#, Word8# #) -- ^ Word8 (LSB, MSB)+encodeWord16LE# x# = (# word16ToWord8# x#+                     ,  word16ToWord8# (x# `uncheckedShiftRLWord16#` 8#) #)+  where+    word16ToWord8# y = wordToWord8# (word16ToWord# y)++decodeWord16LE# :: (# Word8#, Word8# #) -- ^ Word8 (LSB, MSB)+                -> Word16#              -- ^ Word16+decodeWord16LE# (# lsb#, msb# #) = ((word8ToWord16# msb# `uncheckedShiftLWord16#` 8#) `orWord16#` word8ToWord16# lsb#)+  where+    word8ToWord16# y = wordToWord16# (word8ToWord# y)++#endif++setByteArray :: MBA s -> Int -> Int -> Int -> ST s ()+setByteArray (MBA# dst#) (I# off#) (I# len#) (I# c#) =+    ST $ \s -> case setByteArray# dst# off# len# c# s of+                 s' -> (# s', () #)++copyMutableByteArray :: MBA s -> Int -> MBA s -> Int -> Int -> ST s ()+copyMutableByteArray (MBA# src#) (I# src_off#) (MBA# dst#) (I# dst_off#) (I# len#) =+    ST $ \s -> case copyMutableByteArray# src# src_off# dst# dst_off# len# s of+                 s' -> (# s', () #)++-- | Given the maximum size needed and a function to make the contents+-- of a ShortByteString, createAndTrim makes the 'ShortByteString'.+-- The generating function is required to return the actual final size+-- (<= the maximum size) and the result value. The resulting byte array+-- is realloced to this size.+createAndTrim :: Int -> (forall s. MBA s -> ST s (Int, a)) -> (ShortByteString, a)+createAndTrim l fill =+    runST $ do+      mba <- newByteArray l+      (l', res) <- fill mba+      if assert (l' <= l) $ l' >= l+          then do+            BA# ba# <- unsafeFreezeByteArray mba+            return (SBS ba#, res)+          else do+            mba2 <- newByteArray l'+            copyMutableByteArray mba 0 mba2 0 l'+            BA# ba# <- unsafeFreezeByteArray mba2+            return (SBS ba#, res)+{-# INLINE createAndTrim #-}++createAndTrim' :: Int -> (forall s. MBA s -> ST s Int) -> ShortByteString+createAndTrim' l fill =+    runST $ do+      mba <- newByteArray l+      l' <- fill mba+      if assert (l' <= l) $ l' >= l+          then do+            BA# ba# <- unsafeFreezeByteArray mba+            return (SBS ba#)+          else do+            mba2 <- newByteArray l'+            copyMutableByteArray mba 0 mba2 0 l'+            BA# ba# <- unsafeFreezeByteArray mba2+            return (SBS ba#)+{-# INLINE createAndTrim' #-}++createAndTrim'' :: Int -> (forall s. MBA s -> MBA s -> ST s (Int, Int)) -> (ShortByteString, ShortByteString)+createAndTrim'' l fill =+    runST $ do+      mba1 <- newByteArray l+      mba2 <- newByteArray l+      (l1, l2) <- fill mba1 mba2+      sbs1 <- freeze' l1 mba1+      sbs2 <- freeze' l2 mba2+      pure (sbs1, sbs2)+  where+    freeze' :: Int -> MBA s -> ST s ShortByteString+    freeze' l' mba =+      if assert (l' <= l) $ l' >= l+          then do+            BA# ba# <- unsafeFreezeByteArray mba+            return (SBS ba#)+          else do+            mba2 <- newByteArray l'+            copyMutableByteArray mba 0 mba2 0 l'+            BA# ba# <- unsafeFreezeByteArray mba2+            return (SBS ba#)+{-# INLINE createAndTrim'' #-}++-- Returns the index of the first match or the length of the whole+-- bytestring if nothing matched.+findIndexOrLength :: (Word16 -> Bool) -> ShortByteString -> Int+findIndexOrLength k (assertEven -> sbs) = go 0+  where+    l = BS.length sbs+    ba = asBA sbs+    w = indexWord16Array ba+    go !n | n >= l     = l `div` 2+          | k (w n)    = n `div` 2+          | otherwise  = go (n + 2)+{-# INLINE findIndexOrLength #-}+++-- | Returns the length of the substring matching, not the index.+-- If no match, returns 0.+findFromEndUntil :: (Word16 -> Bool) -> ShortByteString -> Int+findFromEndUntil k sbs = go (BS.length sbs - 2)+  where+    ba = asBA sbs+    w = indexWord16Array ba+    go !n | n < 0     = 0+          | k (w n)   = (n `div` 2) + 1+          | otherwise = go (n - 2)+{-# INLINE findFromEndUntil #-}+++assertEven :: ShortByteString -> ShortByteString+assertEven sbs@(SBS barr#)+  | even (I# (sizeofByteArray# barr#)) = sbs+  | otherwise = error ("Uneven number of bytes: " <> show (BS.length sbs) <> ". This is not a Word16 bytestream.")+++-- Common up near identical calls to `error' to reduce the number+-- constant strings created when compiled:+errorEmptySBS :: HasCallStack => String -> a+errorEmptySBS fun = moduleError fun "empty ShortByteString"+{-# NOINLINE errorEmptySBS #-}++moduleError :: HasCallStack => String -> String -> a+moduleError fun msg = error (moduleErrorMsg fun msg)+{-# NOINLINE moduleError #-}
+ System/OsPath/Data/ByteString/Short/Word16.hs view
@@ -0,0 +1,862 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-# OPTIONS_GHC -fno-warn-name-shadowing -fexpose-all-unfoldings #-}++-- |+-- Module      :  System.OsPath.Data.ByteString.Short.Word16+-- Copyright   :  © 2022 Julian Ospald+-- License     :  MIT+--+-- Maintainer  :  Julian Ospald <hasufell@posteo.de>+-- Stability   :  experimental+-- Portability :  portable+--+-- ShortByteStrings encoded as UTF16-LE, suitable for windows FFI calls.+--+-- Word16s are *always* in BE encoding (both input and output), so e.g. 'pack'+-- takes a list of BE encoded @[Word16]@ and produces a UTF16-LE encoded ShortByteString.+--+-- Likewise, 'unpack' takes a UTF16-LE encoded ShortByteString and produces a list of BE encoded @[Word16]@.+--+-- Indices and lengths are always in respect to Word16, not Word8.+--+-- All functions will error out if the input string is not a valid UTF16 stream (uneven number of bytes).+-- So use this module with caution.+module System.OsPath.Data.ByteString.Short.Word16 (+    -- * The @ShortByteString@ type and representation+    ShortByteString(..),++    -- * Introducing and eliminating 'ShortByteString's+    empty,+    singleton,+    pack,+    unpack,+    fromShort,+    toShort,++    -- * Basic interface+    snoc,+    cons,+    append,+    last,+    tail,+    uncons,+    head,+    init,+    unsnoc,+    null,+    length,+    numWord16,++    -- * Transforming ShortByteStrings+    map,+    reverse,+    intercalate,++    -- * Reducing 'ShortByteString's (folds)+    foldl,+    foldl',+    foldl1,+    foldl1',++    foldr,+    foldr',+    foldr1,+    foldr1',++    -- ** Special folds+    all,+    any,+    concat,++    -- ** Generating and unfolding ByteStrings+    replicate,+    unfoldr,+    unfoldrN,++    -- * Substrings++    -- ** Breaking strings+    take,+    takeEnd,+    takeWhileEnd,+    takeWhile,+    drop,+    dropEnd,+    dropWhile,+    dropWhileEnd,+    breakEnd,+    break,+    span,+    spanEnd,+    splitAt,+    split,+    splitWith,+    stripSuffix,+    stripPrefix,++    -- * Predicates+    isInfixOf,+    isPrefixOf,+    isSuffixOf,++    -- ** Search for arbitrary substrings+    breakSubstring,++    -- * Searching ShortByteStrings++    -- ** Searching by equality+    elem,++    -- ** Searching with a predicate+    find,+    filter,+    partition,++    -- * Indexing ShortByteStrings+    index,+    indexMaybe,+    (!?),+    elemIndex,+    elemIndices,+    count,+    findIndex,+    findIndices,++    -- ** Encoding validation+    -- isValidUtf8,++    -- * Low level conversions+    -- ** Packing 'CString's and pointers+    packCWString,+    packCWStringLen,+    newCWString,+   +    -- ** Using ShortByteStrings as 'CString's+    useAsCWString,+    useAsCWStringLen+  )+where+import System.OsPath.Data.ByteString.Short ( append, intercalate, concat, stripSuffix, stripPrefix, isInfixOf, isPrefixOf, isSuffixOf, breakSubstring, length, empty, null, ShortByteString(..), fromShort, toShort )+import System.OsPath.Data.ByteString.Short.Internal+import Data.Bits+    ( shiftR )+import Data.Word+import Prelude hiding+    ( all+    , any+    , reverse+    , break+    , concat+    , drop+    , dropWhile+    , elem+    , filter+    , foldl+    , foldl1+    , foldr+    , foldr1+    , head+    , init+    , last+    , length+    , map+    , null+    , replicate+    , span+    , splitAt+    , tail+    , take+    , takeWhile+    )+import qualified Data.Foldable as Foldable+import GHC.ST ( ST )+import GHC.Stack ( HasCallStack )++import qualified Data.ByteString.Short.Internal as BS+import qualified Data.List as List+++-- -----------------------------------------------------------------------------+-- Introducing and eliminating 'ShortByteString's++-- | /O(1)/ Convert a 'Word16' into a 'ShortByteString'+singleton :: Word16 -> ShortByteString+singleton = \w -> create 2 (\mba -> writeWord16Array mba 0 w)+++-- | /O(n)/. Convert a list into a 'ShortByteString'+pack :: [Word16] -> ShortByteString+pack = packWord16+++-- | /O(n)/. Convert a 'ShortByteString' into a list.+unpack :: ShortByteString -> [Word16]+unpack = unpackWord16 . assertEven+++-- ---------------------------------------------------------------------+-- Basic interface++-- | This is like 'length', but the number of 'Word16', not 'Word8'.+numWord16 :: ShortByteString -> Int+numWord16 = (`shiftR` 1) . BS.length . assertEven++infixr 5 `cons` --same as list (:)+infixl 5 `snoc`++-- | /O(n)/ Append a Word16 to the end of a 'ShortByteString'+-- +-- Note: copies the entire byte array+snoc :: ShortByteString -> Word16 -> ShortByteString+snoc = \(assertEven -> sbs) c -> let l = BS.length sbs+                                     nl = l + 2+  in create nl $ \mba -> do+      copyByteArray (asBA sbs) 0 mba 0 l+      writeWord16Array mba l c++-- | /O(n)/ 'cons' is analogous to (:) for lists.+--+-- Note: copies the entire byte array+cons :: Word16 -> ShortByteString -> ShortByteString+cons c = \(assertEven -> sbs) -> let l = BS.length sbs+                                     nl = l + 2+  in create nl $ \mba -> do+      writeWord16Array mba 0 c+      copyByteArray (asBA sbs) 0 mba 2 l++-- | /O(1)/ Extract the last element of a ShortByteString, which must be finite and at least one Word16.+-- An exception will be thrown in the case of an empty ShortByteString.+last :: HasCallStack => ShortByteString -> Word16+last = \(assertEven -> sbs) -> case null sbs of+  True -> errorEmptySBS "last"+  False -> indexWord16Array (asBA sbs) (BS.length sbs - 2)++-- | /O(n)/ Extract the elements after the head of a ShortByteString, which must at least one Word16.+-- An exception will be thrown in the case of an empty ShortByteString.+--+-- Note: copies the entire byte array+tail :: HasCallStack => ShortByteString -> ShortByteString+tail = \(assertEven -> sbs) -> +  let l = BS.length sbs+      nl = l - 2+  in if+      | l <= 0 -> errorEmptySBS "tail"+      | otherwise -> create nl $ \mba -> copyByteArray (asBA sbs) 2 mba 0 nl++-- | /O(n)/ Extract the head and tail of a ByteString, returning Nothing+-- if it is empty.+uncons :: ShortByteString -> Maybe (Word16, ShortByteString)+uncons = \(assertEven -> sbs) ->+  let l  = BS.length sbs+      nl = l - 2+  in if | l <= 0 -> Nothing+        | otherwise -> let h = indexWord16Array (asBA sbs) 0+                           t = create nl $ \mba -> copyByteArray (asBA sbs) 2 mba 0 nl+                       in Just (h, t)++-- | /O(1)/ Extract the first element of a ShortByteString, which must be at least one Word16.+-- An exception will be thrown in the case of an empty ShortByteString.+head :: HasCallStack => ShortByteString -> Word16+head = \(assertEven -> sbs) -> case null sbs of+  True -> errorEmptySBS "last"+  False -> indexWord16Array (asBA sbs) 0++-- | /O(n)/ Return all the elements of a 'ShortByteString' except the last one.+-- An exception will be thrown in the case of an empty ShortByteString.+--+-- Note: copies the entire byte array+init :: HasCallStack => ShortByteString -> ShortByteString+init = \(assertEven -> sbs) ->+  let l = BS.length sbs+      nl = l - 2+  in if+      | l <= 0 -> errorEmptySBS "tail"+      | otherwise   -> create nl $ \mba -> copyByteArray (asBA sbs) 0 mba 0 nl++-- | /O(n)/ Extract the 'init' and 'last' of a ByteString, returning Nothing+-- if it is empty.+unsnoc :: ShortByteString -> Maybe (ShortByteString, Word16)+unsnoc = \(assertEven -> sbs) ->+  let l  = BS.length sbs+      nl = l - 2+  in if | l <= 0 -> Nothing+        | otherwise -> let l' = indexWord16Array (asBA sbs) (l - 2)+                           i  = create nl $ \mba -> copyByteArray (asBA sbs) 0 mba 0 nl+                       in Just (i, l')+++-- ---------------------------------------------------------------------+-- Transformations++-- | /O(n)/ 'map' @f xs@ is the ShortByteString obtained by applying @f@ to each+-- element of @xs@.+map :: (Word16 -> Word16) -> ShortByteString -> ShortByteString+map f = \(assertEven -> sbs) ->+    let l = BS.length sbs+        ba = asBA sbs+    in create l (\mba -> go ba mba 0 l)+  where+    go :: BA -> MBA s -> Int -> Int -> ST s ()+    go !ba !mba !i !l+      | i >= l = return ()+      | otherwise = do+          let w = indexWord16Array ba i+          writeWord16Array mba i (f w)+          go ba mba (i+2) l++-- TODO: implement more efficiently+-- | /O(n)/ 'reverse' @xs@ efficiently returns the elements of @xs@ in reverse order.+reverse :: ShortByteString -> ShortByteString+reverse = \(assertEven -> sbs) ->+    let l = BS.length sbs+        ba = asBA sbs+    in create l (\mba -> go ba mba 0 l)+  where+    go :: BA -> MBA s -> Int -> Int -> ST s ()+    go !ba !mba !i !l+      | i >= l = return ()+      | otherwise = do+          let w = indexWord16Array ba i+          writeWord16Array mba (l - 2 - i) w+          go ba mba (i+2) l+++-- ---------------------------------------------------------------------+-- Special folds++-- | /O(n)/ Applied to a predicate and a 'ShortByteString', 'all' determines+-- if all elements of the 'ShortByteString' satisfy the predicate.+all :: (Word16 -> Bool) -> ShortByteString -> Bool+all k = \(assertEven -> sbs) -> +  let l = BS.length sbs+      ba = asBA sbs+      w = indexWord16Array ba+      go !n | n >= l = True+            | otherwise = k (w n) && go (n + 2)+  in go 0+++-- | /O(n)/ Applied to a predicate and a ByteString, 'any' determines if+-- any element of the 'ByteString' satisfies the predicate.+any :: (Word16 -> Bool) -> ShortByteString -> Bool+any k = \(assertEven -> sbs) ->+  let l = BS.length sbs+      ba = asBA sbs+      w = indexWord16Array ba+      go !n | n >= l = False+            | otherwise = k (w n) || go (n + 2)+  in go 0+++-- ---------------------------------------------------------------------+-- Unfolds and replicates+++-- | /O(n)/ 'replicate' @n x@ is a ByteString of length @n@ with @x@+-- the value of every element. The following holds:+--+-- > replicate w c = unfoldr w (\u -> Just (u,u)) c+replicate :: Int -> Word16 -> ShortByteString+replicate w c+    | w <= 0    = empty+    -- can't use setByteArray here, because we write UTF-16LE+    | otherwise = create (w * 2) (`go` 0)+  where+    go mba ix+      | ix < 0 || ix >= w * 2 = pure ()+      | otherwise = writeWord16Array mba ix c >> go mba (ix + 2)++-- | /O(n)/, where /n/ is the length of the result.  The 'unfoldr'+-- function is analogous to the List \'unfoldr\'.  'unfoldr' builds a+-- ShortByteString from a seed value.  The function takes the element and+-- returns 'Nothing' if it is done producing the ShortByteString 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 @[Word16]@+-- and run the generator until it returns `Nothing`, otherwise recurse infinitely,+-- then finally create a 'ShortByteString'.+--+-- Examples:+--+-- >    unfoldr (\x -> if x <= 5 then Just (x, x + 1) else Nothing) 0+-- > == pack [0, 1, 2, 3, 4, 5]+--+unfoldr :: (a -> Maybe (Word16, a)) -> a -> ShortByteString+unfoldr f x0 = packWord16Rev $ go x0 mempty+ where+   go x words' = case f x of+                    Nothing -> words'+                    Just (w, x') -> go x' (w:words')++-- | /O(n)/ Like 'unfoldr', 'unfoldrN' builds a ShortByteString 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)+--+unfoldrN :: forall a.+            Int  -- ^ number of 'Word16'+         -> (a -> Maybe (Word16, a))+         -> a+         -> (ShortByteString, Maybe a)+unfoldrN i f = \x0 ->+  if | i < 0     -> (empty, Just x0)+     | otherwise -> createAndTrim (i * 2) $ \mba -> go mba x0 0++  where+    go :: forall s. MBA s -> a -> Int -> ST s (Int, Maybe a)+    go !mba !x !n = go' x n+      where+        go' :: a -> Int -> ST s (Int, Maybe a)+        go' !x' !n'+          | n' == i * 2 = return (n', Just x')+          | otherwise   = case f x' of+                          Nothing       -> return (n', Nothing)+                          Just (w, x'') -> do+                                             writeWord16Array mba n' w+                                             go' x'' (n'+2)+++-- --------------------------------------------------------------------+-- Predicates++++-- ---------------------------------------------------------------------+-- Substrings++-- | /O(n)/ 'take' @n@, applied to a ShortByteString @xs@, returns the prefix+-- of @xs@ of length @n@, or @xs@ itself if @n > 'length' xs@.+--+-- Note: copies the entire byte array+take :: Int  -- ^ number of Word16+     -> ShortByteString+     -> ShortByteString+take = \n (assertEven -> sbs) ->+                     let sl   = numWord16 sbs+                         len8 = n * 2+                     in if | n >= sl   -> sbs+                           | n <= 0    -> empty+                           | otherwise ->+                               create len8 $ \mba -> copyByteArray (asBA sbs) 0 mba 0 len8+++-- | /O(1)/ @'takeEnd' n xs@ is equivalent to @'drop' ('length' xs - n) xs@.+-- Takes @n@ elements from end of bytestring.+--+-- >>> takeEnd 3 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL"+-- "e\NULf\NULg\NUL"+-- >>> takeEnd 0 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL"+-- ""+-- >>> takeEnd 4 "a\NULb\NULc\NUL"+-- "a\NULb\NULc\NUL"+takeEnd :: Int  -- ^ number of 'Word16'+        -> ShortByteString+        -> ShortByteString+takeEnd n = \(assertEven -> sbs) ->+                    let sl = BS.length sbs+                        n2 = n * 2+                    in if | n2 >= sl  -> sbs+                          | n2 <= 0   -> empty+                          | otherwise -> create n2 $ \mba -> copyByteArray (asBA sbs) (max 0 (sl - n2)) mba 0 n2++-- | Similar to 'P.takeWhile',+-- returns the longest (possibly empty) prefix of elements+-- satisfying the predicate.+takeWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString+takeWhile f ps = take (findIndexOrLength (not . f) ps) ps++-- | Returns the longest (possibly empty) suffix of elements+-- satisfying the predicate.+--+-- @'takeWhileEnd' p@ is equivalent to @'reverse' . 'takeWhile' p . 'reverse'@.+takeWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString+takeWhileEnd f ps = drop (findFromEndUntil (not . f) ps) ps+++-- | /O(n)/ 'drop' @n@ @xs@ returns the suffix of @xs@ after the first n elements, or @[]@ if @n > 'length' xs@.+--+-- Note: copies the entire byte array+drop  :: Int  -- ^ number of 'Word16'+      -> ShortByteString+      -> ShortByteString+drop = \n' (assertEven -> sbs) ->+  let len = BS.length sbs+      n   = n' * 2+  in if | n <= 0    -> sbs+        | n >= len  -> empty+        | otherwise ->+            let newLen = len - n+            in create newLen $ \mba -> copyByteArray (asBA sbs) n mba 0 newLen++-- | /O(1)/ @'dropEnd' n xs@ is equivalent to @'take' ('length' xs - n) xs@.+-- Drops @n@ elements from end of bytestring.+--+-- >>> dropEnd 3 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL"+-- "a\NULb\NULc\NULd\NUL"+-- >>> dropEnd 0 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL"+-- "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL"+-- >>> dropEnd 4 "a\NULb\NULc\NUL"+-- ""+dropEnd :: Int  -- ^ number of 'Word16'+        -> ShortByteString+        -> ShortByteString+dropEnd n' = \(assertEven -> sbs) ->+                    let sl = BS.length sbs+                        nl = sl - n+                        n  = n' * 2+                    in if | n >= sl   -> empty+                          | n <= 0    -> sbs+                          | otherwise -> create nl $ \mba -> copyByteArray (asBA sbs) 0 mba 0 nl++-- | Similar to 'P.dropWhile',+-- drops the longest (possibly empty) prefix of elements+-- satisfying the predicate and returns the remainder.+--+-- Note: copies the entire byte array+dropWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString+dropWhile f = \(assertEven -> ps) -> drop (findIndexOrLength (not . f) ps) ps++-- | Similar to 'P.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 0.10.12.0+dropWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString+dropWhileEnd f = \(assertEven -> ps) -> take (findFromEndUntil (not . f) ps) ps++-- | 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))@.+breakEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)+breakEnd p = \(assertEven -> sbs) -> splitAt (findFromEndUntil p sbs) sbs++-- | Similar to 'P.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))@.+break :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)+break = \p (assertEven -> ps) -> case findIndexOrLength p ps of n -> splitAt n ps++-- | Similar to 'P.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)@.+--+span :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)+{- HLINT ignore "Use span" -}+span p = break (not . p) . assertEven++-- | 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) ps+-- >    ==+-- > let (x, y) = span (not . isSpace) (reverse ps) in (reverse y, reverse x)+--+spanEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)+spanEnd  p = \(assertEven -> ps) -> splitAt (findFromEndUntil (not.p) ps) ps++-- | /O(n)/ 'splitAt' @n xs@ is equivalent to @('take' n xs, 'drop' n xs)@.+--+-- Note: copies the substrings+splitAt :: Int -- ^ number of Word16+        -> ShortByteString+        -> (ShortByteString, ShortByteString)+splitAt n' = \(assertEven -> sbs) -> if+  | n <= 0 -> (empty, sbs)+  | otherwise ->+      let slen = BS.length sbs+      in if | n >= BS.length sbs -> (sbs, empty)+            | otherwise ->+                let llen = min slen (max 0 n)+                    rlen = max 0 (slen - max 0 n)+                    lsbs = create llen $ \mba -> copyByteArray (asBA sbs) 0 mba 0 llen+                    rsbs = create rlen $ \mba -> copyByteArray (asBA sbs) n mba 0 rlen+                in (lsbs, rsbs)+ where+  n = n' * 2++-- | /O(n)/ Break a 'ShortByteString' 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 . (==)+--+-- Note: copies the substrings+split :: Word16 -> ShortByteString -> [ShortByteString]+split w = splitWith (== w) . assertEven+++-- | /O(n)/ Splits a 'ShortByteString' 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 [""]+--+splitWith :: (Word16 -> Bool) -> ShortByteString -> [ShortByteString]+splitWith p = \(assertEven -> sbs) -> if+  | BS.null sbs -> []+  | otherwise -> go sbs+  where+    go sbs'+      | BS.null sbs' = [mempty]+      | otherwise =+          case break p sbs' of+            (a, b)+              | BS.null b -> [a]+              | otherwise -> a : go (tail b)+++-- ---------------------------------------------------------------------+-- Reducing 'ByteString's++-- | 'foldl', applied to a binary operator, a starting value (typically+-- the left-identity of the operator), and a ShortByteString, reduces the+-- ShortByteString using the binary operator, from left to right.+--+foldl :: (a -> Word16 -> a) -> a -> ShortByteString -> a+foldl f v = List.foldl f v . unpack . assertEven++-- | 'foldl'' is like 'foldl', but strict in the accumulator.+--+foldl' :: (a -> Word16 -> a) -> a -> ShortByteString -> a+foldl' f v = List.foldl' f v . unpack . assertEven++-- | 'foldr', applied to a binary operator, a starting value+-- (typically the right-identity of the operator), and a ShortByteString,+-- reduces the ShortByteString using the binary operator, from right to left.+foldr :: (Word16 -> a -> a) -> a -> ShortByteString -> a+foldr f v = List.foldr f v . unpack . assertEven++-- | 'foldr'' is like 'foldr', but strict in the accumulator.+foldr' :: (Word16 -> a -> a) -> a -> ShortByteString -> a+foldr' k v = Foldable.foldr' k v . unpack . assertEven++-- | 'foldl1' is a variant of 'foldl' that has no starting value+-- argument, and thus must be applied to non-empty 'ShortByteString's.+-- An exception will be thrown in the case of an empty ShortByteString.+foldl1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16+foldl1 k = List.foldl1 k . unpack . assertEven++-- | 'foldl1'' is like 'foldl1', but strict in the accumulator.+-- An exception will be thrown in the case of an empty ShortByteString.+foldl1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16+foldl1' k = List.foldl1' k . unpack . assertEven++-- | 'foldr1' is a variant of 'foldr' that has no starting value argument,+-- and thus must be applied to non-empty 'ShortByteString's+-- An exception will be thrown in the case of an empty ShortByteString.+foldr1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16+foldr1 k = List.foldr1 k . unpack . assertEven++-- | 'foldr1'' is a variant of 'foldr1', but is strict in the+-- accumulator.+foldr1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16+foldr1' k = \(assertEven -> sbs) -> if null sbs then errorEmptySBS "foldr1'" else foldr' k (last sbs) (init sbs)+++-- --------------------------------------------------------------------+-- Searching ShortByteString++-- | /O(1)/ 'ShortByteString' index (subscript) operator, starting from 0.+index :: HasCallStack+      => ShortByteString+      -> Int  -- ^ number of 'Word16'+      -> Word16+index = \(assertEven -> sbs) i -> if+  | i >= 0 && i < numWord16 sbs -> unsafeIndex sbs i+  | otherwise                   -> indexError sbs i++-- | /O(1)/ 'ShortByteString' index, starting from 0, that returns 'Just' if:+--+-- > 0 <= n < length bs+--+-- @since 0.11.0.0+indexMaybe :: ShortByteString+           -> Int  -- ^ number of 'Word16'+           -> Maybe Word16+indexMaybe = \(assertEven -> sbs) i -> if+  | i >= 0 && i < numWord16 sbs -> Just $! unsafeIndex sbs i+  | otherwise                   -> Nothing+{-# INLINE indexMaybe #-}++unsafeIndex :: ShortByteString+            -> Int  -- ^ number of 'Word16'+            -> Word16+unsafeIndex sbs i = indexWord16Array (asBA sbs) (i * 2)++indexError :: HasCallStack => ShortByteString -> Int -> a+indexError sbs i =+  moduleError "index" $ "error in array index: " ++ show i+                        ++ " not in range [0.." ++ show (numWord16 sbs) ++ "]"++-- | /O(1)/ 'ShortByteString' index, starting from 0, that returns 'Just' if:+--+-- > 0 <= n < length bs+--+-- @since 0.11.0.0+(!?) :: ShortByteString+     -> Int  -- ^ number of 'Word16'+     -> Maybe Word16+(!?) = indexMaybe+{-# INLINE (!?) #-}++-- | /O(n)/ 'elem' is the 'ShortByteString' membership predicate.+elem :: Word16 -> ShortByteString -> Bool+elem c = \(assertEven -> sbs) -> case elemIndex c sbs of Nothing -> False ; _ -> True++-- | /O(n)/ 'filter', applied to a predicate and a ByteString,+-- returns a ByteString containing those characters that satisfy the+-- predicate.+filter :: (Word16 -> Bool) -> ShortByteString -> ShortByteString+filter k = \(assertEven -> sbs) ->+                   let l = BS.length sbs+                   in if | l <= 0    -> sbs+                         | otherwise -> createAndTrim' l $ \mba -> go mba (asBA sbs) l+  where+    go :: forall s. MBA s -- mutable output bytestring+       -> BA              -- input bytestring+       -> Int             -- length of input bytestring+       -> ST s Int+    go !mba ba !l = go' 0 0+      where+        go' :: Int -- bytes read+            -> Int -- bytes written+            -> ST s Int+        go' !br !bw+          | br >= l   = return bw+          | otherwise = do+              let w = indexWord16Array ba br+              if k w+              then do+                writeWord16Array mba bw w+                go' (br+2) (bw+2)+              else+                go' (br+2) bw++-- | /O(n)/ The 'find' function takes a predicate and a ByteString,+-- 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+--+find :: (Word16 -> Bool) -> ShortByteString -> Maybe Word16+find f = \(assertEven -> sbs) -> case findIndex f sbs of+                    Just n -> Just (sbs `index` n)+                    _      -> Nothing++-- | /O(n)/ The 'partition' function takes a predicate a ByteString and returns+-- the pair of ByteStrings with elements which do and do not satisfy the+-- predicate, respectively; i.e.,+--+-- > partition p bs == (filter p xs, filter (not . p) xs)+--+partition :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)+partition k = \(assertEven -> sbs) ->+                   let l = BS.length sbs+                   in if | l <= 0    -> (sbs, sbs)+                         | otherwise -> createAndTrim'' l $ \mba1 mba2 -> go mba1 mba2 (asBA sbs) l+  where+    go :: forall s.+          MBA s           -- mutable output bytestring1+       -> MBA s           -- mutable output bytestring2+       -> BA              -- input bytestring+       -> Int             -- length of input bytestring+       -> ST s (Int, Int) -- (length mba1, length mba2)+    go !mba1 !mba2 ba !l = go' 0 0+      where+        go' :: Int -- bytes read+            -> Int -- bytes written to bytestring 1+            -> ST s (Int, Int) -- (length mba1, length mba2)+        go' !br !bw1+          | br >= l   = return (bw1, br - bw1)+          | otherwise = do+              let w = indexWord16Array ba br+              if k w+              then do+                writeWord16Array mba1 bw1 w+                go' (br+2) (bw1+2)+              else do+                writeWord16Array mba2 (br - bw1) w+                go' (br+2) bw1++-- --------------------------------------------------------------------+-- Indexing ShortByteString++-- | /O(n)/ The 'elemIndex' function returns the index of the first+-- element in the given 'ShortByteString' which is equal to the query+-- element, or 'Nothing' if there is no such element.+elemIndex :: Word16+          -> ShortByteString+          -> Maybe Int  -- ^ number of 'Word16'+{- HLINT ignore "Use elemIndex" -}+elemIndex k = findIndex (==k) . assertEven++-- | /O(n)/ The 'elemIndices' function extends 'elemIndex', by returning+-- the indices of all elements equal to the query element, in ascending order.+elemIndices :: Word16 -> ShortByteString -> [Int]+{- HLINT ignore "Use elemIndices" -}+elemIndices k = findIndices (==k) . assertEven++-- | count returns the number of times its argument appears in the ShortByteString+count :: Word16 -> ShortByteString -> Int+count w = List.length . elemIndices w . assertEven++-- | /O(n)/ The 'findIndex' function takes a predicate and a 'ShortByteString' and+-- returns the index of the first element in the ByteString+-- satisfying the predicate.+findIndex :: (Word16 -> Bool) -> ShortByteString -> Maybe Int+findIndex k = \(assertEven -> sbs) ->+  let l = BS.length sbs+      ba = asBA sbs+      w = indexWord16Array ba+      go !n | n >= l    = Nothing+            | k (w n)   = Just (n `shiftR` 1)+            | otherwise = go (n + 2)+  in go 0++-- | /O(n)/ The 'findIndices' function extends 'findIndex', by returning the+-- indices of all elements satisfying the predicate, in ascending order.+findIndices :: (Word16 -> Bool) -> ShortByteString -> [Int]+findIndices k = \(assertEven -> sbs) ->+  let l = BS.length sbs+      ba = asBA sbs+      w = indexWord16Array ba+      go !n | n >= l    = []+            | k (w n)   = (n `shiftR` 1) : go (n + 2)+            | otherwise = go (n + 2)+  in go 0+
+ System/OsPath/Encoding.hs view
@@ -0,0 +1,31 @@+module System.OsPath.Encoding+  (+  -- * Types+    EncodingException(..)+  , showEncodingException++  -- * UCS-2+  , ucs2le+  , mkUcs2le+  , ucs2le_DF+  , ucs2le_EF+  , ucs2le_decode+  , ucs2le_encode++  -- * UTF-16LE_b+  , utf16le_b+  , mkUTF16le_b+  , utf16le_b_DF+  , utf16le_b_EF+  , utf16le_b_decode+  , utf16le_b_encode++  -- * base encoding+  , encodeWithBasePosix+  , decodeWithBasePosix+  , encodeWithBaseWindows+  , decodeWithBaseWindows+  )+  where++import System.OsPath.Encoding.Internal
+ System/OsPath/Encoding/Internal.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE NoImplicitPrelude+           , BangPatterns+           , TypeApplications+           , MultiWayIf+  #-}+{-# OPTIONS_GHC  -funbox-strict-fields #-}+++module System.OsPath.Encoding.Internal where++import qualified System.OsPath.Data.ByteString.Short as BS8+import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16++import GHC.Base+import GHC.Real+import GHC.Num+-- import GHC.IO+import GHC.IO.Buffer+import GHC.IO.Encoding.Failure+import GHC.IO.Encoding.Types+import Data.Bits+import Control.Exception (SomeException, try, Exception (displayException), evaluate)+import qualified GHC.Foreign as GHC+import Data.Either (Either)+import GHC.IO (unsafePerformIO)+import Control.DeepSeq (force, NFData (rnf))+import Data.Bifunctor (first)+import Data.Data (Typeable)+import GHC.Show (Show (show))+import Numeric (showHex)+import Foreign.C (CStringLen)+import Data.Char (chr)+import Foreign+import Prelude (FilePath)+import GHC.IO.Encoding (getFileSystemEncoding)++-- -----------------------------------------------------------------------------+-- UCS-2 LE+--++ucs2le :: TextEncoding+ucs2le = mkUcs2le ErrorOnCodingFailure++mkUcs2le :: CodingFailureMode -> TextEncoding+mkUcs2le cfm = TextEncoding { textEncodingName = "UCS-2LE",+                              mkTextDecoder = ucs2le_DF cfm,+                              mkTextEncoder = ucs2le_EF cfm }++ucs2le_DF :: CodingFailureMode -> IO (TextDecoder ())+ucs2le_DF cfm =+  return (BufferCodec {+             encode   = ucs2le_decode,+             recover  = recoverDecode cfm,+             close    = return (),+             getState = return (),+             setState = const $ return ()+          })++ucs2le_EF :: CodingFailureMode -> IO (TextEncoder ())+ucs2le_EF cfm =+  return (BufferCodec {+             encode   = ucs2le_encode,+             recover  = recoverEncode cfm,+             close    = return (),+             getState = return (),+             setState = const $ return ()+          })+++ucs2le_decode :: DecodeBuffer+ucs2le_decode+  input@Buffer{  bufRaw=iraw, bufL=ir0, bufR=iw,  bufSize=_  }+  output@Buffer{ bufRaw=oraw, bufL=_,   bufR=ow0, bufSize=os }+ = let+       loop !ir !ow+         | ow >= os     = done OutputUnderflow ir ow+         | ir >= iw     = done InputUnderflow ir ow+         | ir + 1 == iw = done InputUnderflow ir ow+         | otherwise = do+              c0 <- readWord8Buf iraw ir+              c1 <- readWord8Buf iraw (ir+1)+              let x1 = fromIntegral c1 `shiftL` 8 + fromIntegral c0+              ow' <- writeCharBuf oraw ow (unsafeChr x1)+              loop (ir+2) ow'++       -- lambda-lifted, to avoid thunks being built in the inner-loop:+       done why !ir !ow = return (why,+                                  if ir == iw then input{ bufL=0, bufR=0 }+                                              else input{ bufL=ir },+                                  output{ bufR=ow })+    in+    loop ir0 ow0+++ucs2le_encode :: EncodeBuffer+ucs2le_encode+  input@Buffer{  bufRaw=iraw, bufL=ir0, bufR=iw,  bufSize=_  }+  output@Buffer{ bufRaw=oraw, bufL=_,   bufR=ow0, bufSize=os }+ = let+      done why !ir !ow = return (why,+                                 if ir == iw then input{ bufL=0, bufR=0 }+                                             else input{ bufL=ir },+                                 output{ bufR=ow })+      loop !ir !ow+        | ir >= iw     =  done InputUnderflow ir ow+        | os - ow < 2  =  done OutputUnderflow ir ow+        | otherwise = do+           (c,ir') <- readCharBuf iraw ir+           case ord c of+             x | x < 0x10000 -> do+                     writeWord8Buf oraw ow     (fromIntegral x)+                     writeWord8Buf oraw (ow+1) (fromIntegral (x `shiftR` 8))+                     loop ir' (ow+2)+               | otherwise -> done InvalidSequence ir ow+    in+    loop ir0 ow0++-- -----------------------------------------------------------------------------+-- UTF-16b+--++-- | Mimics the base encoding for filesystem operations. This should be total on all inputs (word16 byte arrays).+--+-- Note that this has a subtle difference to 'encodeWithBaseWindows'/'decodeWithBaseWindows': it doesn't care for+-- the @0x0000@ end marker and will as such produce different results. Use @takeWhile (/= '\NUL')@ on the input+-- to recover this behavior.+utf16le_b :: TextEncoding+utf16le_b = mkUTF16le_b ErrorOnCodingFailure++mkUTF16le_b :: CodingFailureMode -> TextEncoding+mkUTF16le_b cfm = TextEncoding { textEncodingName = "UTF-16LE_b",+                                 mkTextDecoder = utf16le_b_DF cfm,+                                 mkTextEncoder = utf16le_b_EF cfm }++utf16le_b_DF :: CodingFailureMode -> IO (TextDecoder ())+utf16le_b_DF cfm =+  return (BufferCodec {+             encode   = utf16le_b_decode,+             recover  = recoverDecode cfm,+             close    = return (),+             getState = return (),+             setState = const $ return ()+          })++utf16le_b_EF :: CodingFailureMode -> IO (TextEncoder ())+utf16le_b_EF cfm =+  return (BufferCodec {+             encode   = utf16le_b_encode,+             recover  = recoverEncode cfm,+             close    = return (),+             getState = return (),+             setState = const $ return ()+          })+++utf16le_b_decode :: DecodeBuffer+utf16le_b_decode+  input@Buffer{  bufRaw=iraw, bufL=ir0, bufR=iw,  bufSize=_  }+  output@Buffer{ bufRaw=oraw, bufL=_,   bufR=ow0, bufSize=os }+ = let+       loop !ir !ow+         | ow >= os     = done OutputUnderflow ir ow+         | ir >= iw     = done InputUnderflow ir ow+         | ir + 1 == iw = done InputUnderflow ir ow+         | otherwise = do+              c0 <- readWord8Buf iraw ir+              c1 <- readWord8Buf iraw (ir+1)+              let x1 = fromIntegral c1 `shiftL` 8 + fromIntegral c0+              if | iw - ir >= 4 -> do+                      c2 <- readWord8Buf iraw (ir+2)+                      c3 <- readWord8Buf iraw (ir+3)+                      let x2 = fromIntegral c3 `shiftL` 8 + fromIntegral c2+                      if | 0xd800 <= x1 && x1 <= 0xdbff+                         , 0xdc00 <= x2 && x2 <= 0xdfff -> do+                             ow' <- writeCharBuf oraw ow (unsafeChr ((x1 - 0xd800)*0x400 + (x2 - 0xdc00) + 0x10000))+                             loop (ir+4) ow'+                         | otherwise -> do+                             ow' <- writeCharBuf oraw ow (unsafeChr x1)+                             loop (ir+2) ow'+                 | iw - ir >= 2 -> do+                        ow' <- writeCharBuf oraw ow (unsafeChr x1)+                        loop (ir+2) ow'+                 | otherwise -> done InputUnderflow ir ow++       -- lambda-lifted, to avoid thunks being built in the inner-loop:+       done why !ir !ow = return (why,+                                  if ir == iw then input{ bufL=0, bufR=0 }+                                              else input{ bufL=ir },+                                  output{ bufR=ow })+    in+    loop ir0 ow0+++utf16le_b_encode :: EncodeBuffer+utf16le_b_encode+  input@Buffer{  bufRaw=iraw, bufL=ir0, bufR=iw,  bufSize=_  }+  output@Buffer{ bufRaw=oraw, bufL=_,   bufR=ow0, bufSize=os }+ = let+      done why !ir !ow = return (why,+                                 if ir == iw then input{ bufL=0, bufR=0 }+                                             else input{ bufL=ir },+                                 output{ bufR=ow })+      loop !ir !ow+        | ir >= iw     =  done InputUnderflow ir ow+        | os - ow < 2  =  done OutputUnderflow ir ow+        | otherwise = do+           (c,ir') <- readCharBuf iraw ir+           case ord c of+             x | x < 0x10000 -> do+                     writeWord8Buf oraw ow     (fromIntegral x)+                     writeWord8Buf oraw (ow+1) (fromIntegral (x `shiftR` 8))+                     loop ir' (ow+2)+               | otherwise ->+                     if os - ow < 4 then done OutputUnderflow ir ow else do+                     let x' = x - 0x10000+                         w1 = x' `div` 0x400 + 0xd800+                         w2 = x' `mod` 0x400 + 0xdc00+                     writeWord8Buf oraw ow     (fromIntegral w1)+                     writeWord8Buf oraw (ow+1) (fromIntegral (w1 `shiftR` 8))+                     writeWord8Buf oraw (ow+2) (fromIntegral w2)+                     writeWord8Buf oraw (ow+3) (fromIntegral (w2 `shiftR` 8))+                     loop ir' (ow+4)+    in+    loop ir0 ow0++-- -----------------------------------------------------------------------------+-- Windows encoding (ripped off from base)+--++cWcharsToChars_UCS2 :: [Word16] -> [Char]+cWcharsToChars_UCS2 = map (chr . fromIntegral)+++-- On Windows, wchar_t is 16 bits wide and CWString uses the UTF-16 encoding.++-- coding errors generate Chars in the surrogate range+cWcharsToChars :: [Word16] -> [Char]+cWcharsToChars = map chr . fromUTF16 . map fromIntegral+ where+  fromUTF16 :: [Int] -> [Int]+  fromUTF16 (c1:c2:wcs)+    | 0xd800 <= c1 && c1 <= 0xdbff && 0xdc00 <= c2 && c2 <= 0xdfff =+      ((c1 - 0xd800)*0x400 + (c2 - 0xdc00) + 0x10000) : fromUTF16 wcs+  fromUTF16 (c:wcs) = c : fromUTF16 wcs+  fromUTF16 [] = []++charsToCWchars :: [Char] -> [Word16]+charsToCWchars = foldr (utf16Char . ord) []+ where+  utf16Char :: Int -> [Word16] -> [Word16]+  utf16Char c wcs+    | c < 0x10000 = fromIntegral c : wcs+    | otherwise   = let c' = c - 0x10000 in+                    fromIntegral (c' `div` 0x400 + 0xd800) :+                    fromIntegral (c' `mod` 0x400 + 0xdc00) : wcs++-- -----------------------------------------------------------------------------++-- -----------------------------------------------------------------------------+-- FFI+--++withFilePathWin :: FilePath -> (Int -> Ptr Word16 -> IO a) -> IO a+withFilePathWin = withArrayLen . charsToCWchars++peekFilePathWin :: (Ptr Word16, Int) -> IO FilePath+peekFilePathWin (cp, l) = do+  cs <- peekArray l cp+  return (cWcharsToChars cs)++withFilePathPosix :: FilePath -> (CStringLen -> IO a) -> IO a+withFilePathPosix fp f = getFileSystemEncoding >>= \enc -> GHC.withCStringLen enc fp f++peekFilePathPosix :: CStringLen -> IO FilePath+peekFilePathPosix fp = getFileSystemEncoding >>= \enc -> GHC.peekCStringLen enc fp++-- | Decode with the given 'TextEncoding'.+decodeWithTE :: TextEncoding -> BS8.ShortByteString -> Either EncodingException String+decodeWithTE enc ba = unsafePerformIO $ do+  r <- try @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen enc fp+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r++-- | Encode with the given 'TextEncoding'.+encodeWithTE :: TextEncoding -> String -> Either EncodingException BS8.ShortByteString+encodeWithTE enc str = unsafePerformIO $ do+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> BS8.packCStringLen cstr+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r++-- -----------------------------------------------------------------------------+-- Encoders / decoders+--++-- | This mimics the filepath decoder base uses on unix,+-- with the small distinction that we're not truncating at NUL bytes (because we're not at+-- the outer FFI layer).+decodeWithBasePosix :: BS8.ShortByteString -> IO String+decodeWithBasePosix ba = BS8.useAsCStringLen ba $ \fp -> peekFilePathPosix fp++-- | This mimics the filepath dencoder base uses on unix,+-- with the small distinction that we're not truncating at NUL bytes (because we're not at+-- the outer FFI layer).+encodeWithBasePosix :: String -> IO BS8.ShortByteString+encodeWithBasePosix str = withFilePathPosix str $ \cstr -> BS8.packCStringLen cstr++-- | This mimics the filepath decoder base uses on windows,+-- with the small distinction that we're not truncating at NUL bytes (because we're not at+-- the outer FFI layer).+decodeWithBaseWindows :: BS16.ShortByteString -> IO String+decodeWithBaseWindows ba = BS16.useAsCWStringLen ba $ \fp -> peekFilePathWin fp++-- | This mimics the filepath dencoder base uses on windows,+-- with the small distinction that we're not truncating at NUL bytes (because we're not at+-- the outer FFI layer).+encodeWithBaseWindows :: String -> IO BS16.ShortByteString+encodeWithBaseWindows str = withFilePathWin str $ \l cstr -> BS16.packCWStringLen (cstr, l)+++-- -----------------------------------------------------------------------------+-- Types+--++data EncodingException =+    EncodingError String (Maybe Word8)+    -- ^ Could not decode a byte sequence because it was invalid under+    -- the given encoding, or ran out of input in mid-decode.+    deriving (Eq, Typeable)+++showEncodingException :: EncodingException -> String+showEncodingException (EncodingError desc (Just w))+    = "Cannot decode byte '\\x" ++ showHex w ("': " ++ desc)+showEncodingException (EncodingError desc Nothing)+    = "Cannot decode input: " ++ desc++instance Show EncodingException where+    show = showEncodingException++instance Exception EncodingException++instance NFData EncodingException where+    rnf (EncodingError desc w) = rnf desc `seq` rnf w+++-- -----------------------------------------------------------------------------+-- Words+--++wNUL :: Word16+wNUL = 0x00
+ System/OsPath/Internal.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UnliftedFFITypes #-}++module System.OsPath.Internal where++import {-# SOURCE #-} System.OsPath+    ( isValid )+import System.OsPath.Types+import qualified System.OsString.Internal as OS++import Control.Monad.Catch+    ( MonadThrow )+import Data.ByteString+    ( ByteString )+import Language.Haskell.TH.Quote+    ( QuasiQuoter (..) )+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )++import System.OsString.Internal.Types+import System.OsPath.Encoding+import Control.Monad (when)+import System.IO+    ( TextEncoding )++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+import qualified System.OsPath.Windows as PF+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+#else+import qualified System.OsPath.Posix as PF+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+#endif++++-- | Partial unicode friendly encoding.+--+-- On windows this encodes as UTF16-LE (strictly), which is a pretty good guess.+-- On unix this encodes as UTF8 (strictly), which is a good guess.+--+-- Throws a 'EncodingException' if encoding fails.+encodeUtf :: MonadThrow m => FilePath -> m OsPath+encodeUtf = OS.encodeUtf++-- | Encode a 'FilePath' with the specified encoding.+encodeWith :: TextEncoding  -- ^ unix text encoding+           -> TextEncoding  -- ^ windows text encoding+           -> FilePath+           -> Either EncodingException OsPath+encodeWith = OS.encodeWith++-- | Like 'encodeUtf', except this mimics the behavior of the base library when doing filesystem+-- operations, which is:+--+-- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,+--    but PEP 383 only works properly on UTF-8 encodings, so good luck)+-- 2. on windows does permissive UTF-16 encoding, where coding errors generate+--    Chars in the surrogate range+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+encodeFS :: FilePath -> IO OsPath+encodeFS = OS.encodeFS+++-- | Partial unicode friendly decoding.+--+-- On windows this decodes as UTF16-LE (strictly), which is a pretty good guess.+-- On unix this decodes as UTF8 (strictly), which is a good guess.+--+-- Throws a 'EncodingException' if decoding fails.+decodeUtf :: MonadThrow m => OsPath -> m FilePath+decodeUtf = OS.decodeUtf++-- | Decode an 'OsPath' with the specified encoding.+decodeWith :: TextEncoding  -- ^ unix text encoding+           -> TextEncoding  -- ^ windows text encoding+           -> OsPath+           -> Either EncodingException FilePath+decodeWith = OS.decodeWith++-- | Like 'decodeUtf', except this mimics the behavior of the base library when doing filesystem+-- operations, which is:+--+-- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,+--    but PEP 383 only works properly on UTF-8 encodings, so good luck)+-- 2. on windows does permissive UTF-16 encoding, where coding errors generate+--    Chars in the surrogate range+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+decodeFS :: OsPath -> IO FilePath+decodeFS = OS.decodeFS+++-- | Constructs an @OsPath@ from a ByteString.+--+-- On windows, this ensures valid UCS-2LE, on unix it is passed unchanged/unchecked.+--+-- Throws 'EncodingException' on invalid UCS-2LE on windows (although unlikely).+fromBytes :: MonadThrow m+          => ByteString+          -> m OsPath+fromBytes = OS.fromBytes++++-- | QuasiQuote an 'OsPath'. This accepts Unicode characters+-- and encodes as UTF-8 on unix and UTF-16LE on windows. Runs 'isValid'+-- on the input.+osp :: QuasiQuoter+osp = QuasiQuoter+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+  { quoteExp = \s -> do+      osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF16le ErrorOnCodingFailure) $ s+      when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')+      lift osp'+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#else+  { quoteExp = \s -> do+      osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF8 ErrorOnCodingFailure) $ s+      when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')+      lift osp'+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#endif+++-- | Unpack an 'OsPath' to a list of 'OsChar'.+unpack :: OsPath -> [OsChar]+unpack = OS.unpack+++-- | Pack a list of 'OsChar' to an 'OsPath'.+--+-- Note that using this in conjunction with 'unsafeFromChar' to+-- convert from @[Char]@ to 'OsPath' is probably not what+-- you want, because it will truncate unicode code points.+pack :: [OsChar] -> OsPath+pack = OS.pack+
+ System/OsPath/Posix.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}++#undef  WINDOWS+#define POSIX+#define IS_WINDOWS False+#define FILEPATH_NAME PosixPath+#define OSSTRING_NAME PosixString+#define WORD_NAME PosixChar++#include "Common.hs"++-- | QuasiQuote a 'PosixPath'. This accepts Unicode characters+-- and encodes as UTF-8. Runs 'isValid' on the input.+pstr :: QuasiQuoter+pstr =+  QuasiQuoter+  { quoteExp = \s -> do+      ps <- either (fail . show) pure $ encodeWith (mkUTF8 ErrorOnCodingFailure) s+      when (not $ isValid ps) $ fail ("filepath not valid: " ++ show ps)+      lift ps+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }
+ System/OsPath/Posix/Internal.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE CPP #-}++#undef WINDOWS+#define OS_PATH+#define IS_WINDOWS False+#define MODULE_NAME Posix++#include "../../FilePath/Internal.hs"
+ System/OsPath/Types.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE CPP #-}++module System.OsPath.Types+  (+  -- * FilePath types+    OsPath+  , WindowsPath+  , PosixPath+  , PlatformPath++  -- * OsString reexports+  , WindowsString+  , PosixString+  , WindowsChar+  , PosixChar+  , OsString+  , OsChar+  )+where++import System.OsString.Internal.Types+++-- | Filepaths are @wchar_t*@ data on windows as passed to syscalls.+type WindowsPath = WindowsString++-- | Filepaths are @char[]@ data on unix as passed to syscalls.+type PosixPath = PosixString++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+-- | Ifdef around current platform (either 'WindowsPath' or 'PosixPath').+type PlatformPath = WindowsPath+#else+-- | Ifdef around current platform (either 'WindowsPath' or 'PosixPath').+type PlatformPath = PosixPath+#endif+++-- | Type representing filenames\/pathnames.+--+-- This type doesn't add any guarantees over 'OsString'.+type OsPath = OsString
+ System/OsPath/Windows.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE CPP #-}++#undef  POSIX+#define IS_WINDOWS True+#define WINDOWS+#define FILEPATH_NAME WindowsPath+#define OSSTRING_NAME WindowsString+#define WORD_NAME WindowsChar++#include "Common.hs"+++-- | QuasiQuote a 'WindowsPath'. This accepts Unicode characters+-- and encodes as UTF-16LE. Runs 'isValid' on the input.+pstr :: QuasiQuoter+pstr =+  QuasiQuoter+  { quoteExp = \s -> do+      ps <- either (fail . show) pure $ encodeWith (mkUTF16le ErrorOnCodingFailure) s+      when (not $ isValid ps) $ fail ("filepath not valid: " ++ show ps)+      lift ps+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }
+ System/OsPath/Windows/Internal.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE CPP #-}++#undef  POSIX+#define WINDOWS+#define OS_PATH+#define IS_WINDOWS True+#define MODULE_NAME Windows++#include "../../FilePath/Internal.hs"
+ System/OsString.hs view
@@ -0,0 +1,60 @@+-- |+-- Module      :  OsString+-- Copyright   :  © 2021 Julian Ospald+-- License     :  MIT+--+-- Maintainer  :  Julian Ospald <hasufell@posteo.de>+-- Stability   :  experimental+-- Portability :  portable+--+-- An implementation of platform specific short 'OsString', which is:+--+-- 1. on windows wide char bytes (@[Word16]@)+-- 2. on unix char bytes (@[Word8]@)+--+-- It captures the notion of syscall specific encoding (or the lack thereof) to avoid roundtrip issues+-- and memory fragmentation by using unpinned byte arrays. Bytes are not touched or interpreted.+module System.OsString+  (+  -- * String types+    OsString++  -- * OsString construction+  , encodeUtf+  , encodeWith+  , encodeFS+  , osstr+  , pack++  -- * OsString deconstruction+  , decodeUtf+  , decodeWith+  , decodeFS+  , unpack++  -- * Word types+  , OsChar++  -- * Word construction+  , unsafeFromChar++  -- * Word deconstruction+  , toChar+  )+where++import System.OsString.Internal+    ( unsafeFromChar+    , toChar+    , encodeUtf+    , encodeWith+    , encodeFS+    , osstr+    , pack+    , decodeUtf+    , decodeWith+    , decodeFS+    , unpack+    )+import System.OsString.Internal.Types+    ( OsString, OsChar )
+ System/OsString/Common.hs view
@@ -0,0 +1,315 @@+{- HLINT ignore "Unused LANGUAGE pragma" -}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE PatternSynonyms #-}+-- This template expects CPP definitions for:+--     MODULE_NAME = Posix | Windows+--     IS_WINDOWS  = False | True+--+#if defined(WINDOWS)+#define WINDOWS_DOC+#else+#define POSIX_DOC+#endif++module System.OsString.MODULE_NAME+  (+  -- * Types+#ifdef WINDOWS+    WindowsString+  , WindowsChar+#else+    PosixString+  , PosixChar+#endif++  -- * String construction+  , encodeUtf+  , encodeWith+  , encodeFS+  , fromBytes+  , pstr+  , pack++  -- * String deconstruction+  , decodeUtf+  , decodeWith+  , decodeFS+  , unpack++  -- * Word construction+  , unsafeFromChar++  -- * Word deconstruction+  , toChar+  )+where++++import System.OsString.Internal.Types (+#ifdef WINDOWS+  WindowsString(..), WindowsChar(..)+#else+  PosixString(..), PosixChar(..)+#endif+  )++import Data.Char+import Control.Monad.Catch+    ( MonadThrow, throwM )+import Data.ByteString.Internal+    ( ByteString )+import Control.Exception+    ( SomeException, try, displayException )+import Control.DeepSeq ( force )+import Data.Bifunctor ( first )+import GHC.IO+    ( evaluate, unsafePerformIO )+import qualified GHC.Foreign as GHC+import Language.Haskell.TH.Quote+    ( QuasiQuoter (..) )+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )+++import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+#ifdef WINDOWS+import System.OsPath.Encoding+import System.IO+    ( TextEncoding, utf16le )+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16+import qualified System.OsPath.Data.ByteString.Short as BS8+#else+import System.OsPath.Encoding+import System.IO+    ( TextEncoding, utf8 )+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import qualified System.OsPath.Data.ByteString.Short as BS+#endif++++#ifdef WINDOWS_DOC+-- | Partial unicode friendly encoding.+--+-- This encodes as UTF16-LE (strictly), which is a pretty good guess.+--+-- Throws an 'EncodingException' if encoding fails.+#else+-- | Partial unicode friendly encoding.+--+-- This encodes as UTF8 (strictly), which is a good guess.+--+-- Throws an 'EncodingException' if encoding fails.+#endif+encodeUtf :: MonadThrow m => String -> m PLATFORM_STRING+#ifdef WINDOWS+encodeUtf = either throwM pure . encodeWith utf16le+#else+encodeUtf = either throwM pure . encodeWith utf8+#endif++-- | Encode a 'String' with the specified encoding.+encodeWith :: TextEncoding+           -> String+           -> Either EncodingException PLATFORM_STRING+encodeWith enc str = unsafePerformIO $ do+#ifdef WINDOWS+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> WindowsString <$> BS8.packCStringLen cstr+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r+#else+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> PosixString <$> BS.packCStringLen cstr+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r+#endif++#ifdef WINDOWS_DOC+-- | This mimics the behavior of the base library when doing filesystem+-- operations, which does permissive UTF-16 encoding, where coding errors generate+-- Chars in the surrogate range.+--+-- The reason this is in IO is because it unifies with the Posix counterpart,+-- which does require IO. This is safe to 'unsafePerformIO'/'unsafeDupablePerformIO'.+#else+-- | This mimics the behavior of the base library when doing filesystem+-- operations, which uses shady PEP 383 style encoding (based on the current locale,+-- but PEP 383 only works properly on UTF-8 encodings, so good luck).+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+#endif+encodeFS :: String -> IO PLATFORM_STRING+#ifdef WINDOWS+encodeFS = fmap WindowsString . encodeWithBaseWindows+#else+encodeFS = fmap PosixString . encodeWithBasePosix+#endif+++#ifdef WINDOWS_DOC+-- | Partial unicode friendly decoding.+--+-- This decodes as UTF16-LE (strictly), which is a pretty good.+--+-- Throws a 'EncodingException' if decoding fails.+#else+-- | Partial unicode friendly decoding.+--+-- This decodes as UTF8 (strictly), which is a good guess. Note that+-- filenames on unix are encoding agnostic char arrays.+--+-- Throws a 'EncodingException' if decoding fails.+#endif+decodeUtf :: MonadThrow m => PLATFORM_STRING -> m String+#ifdef WINDOWS+decodeUtf = either throwM pure . decodeWith utf16le+#else+decodeUtf = either throwM pure . decodeWith utf8+#endif++#ifdef WINDOWS+-- | Decode a 'WindowsString' with the specified encoding.+--+-- The String is forced into memory to catch all exceptions.+decodeWith :: TextEncoding+           -> PLATFORM_STRING+           -> Either EncodingException String+decodeWith winEnc (WindowsString ba) = unsafePerformIO $ do+  r <- try @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen winEnc fp+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r+#else+-- | Decode a 'PosixString' with the specified encoding.+--+-- The String is forced into memory to catch all exceptions.+decodeWith :: TextEncoding+       -> PLATFORM_STRING+       -> Either EncodingException String+decodeWith unixEnc (PosixString ba) = unsafePerformIO $ do+  r <- try @SomeException $ BS.useAsCStringLen ba $ \fp -> GHC.peekCStringLen unixEnc fp+  evaluate $ force $ first (flip EncodingError Nothing . displayException) r+#endif+++#ifdef WINDOWS_DOC+-- | Like 'decodeUtf', except this mimics the behavior of the base library when doing filesystem+-- operations, which does permissive UTF-16 encoding, where coding errors generate+-- Chars in the surrogate range.+--+-- The reason this is in IO is because it unifies with the Posix counterpart,+-- which does require IO. 'unsafePerformIO'/'unsafeDupablePerformIO' are safe, however.+#else+-- | This mimics the behavior of the base library when doing filesystem+-- operations, which uses shady PEP 383 style encoding (based on the current locale,+-- but PEP 383 only works properly on UTF-8 encodings, so good luck).+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+#endif+decodeFS :: PLATFORM_STRING -> IO String+#ifdef WINDOWS+decodeFS (WindowsString ba) = decodeWithBaseWindows ba+#else+decodeFS (PosixString ba) = decodeWithBasePosix ba+#endif+++#ifdef WINDOWS_DOC+-- | Constructs a platform string from a ByteString.+--+-- This ensures valid UCS-2LE.+-- Note that this doesn't expand Word8 to Word16 on windows, so you may get invalid UTF-16.+--+-- Throws 'EncodingException' on invalid UCS-2LE (although unlikely).+#else+-- | Constructs a platform string from a ByteString.+--+-- This is a no-op.+#endif+fromBytes :: MonadThrow m+          => ByteString+          -> m PLATFORM_STRING+#ifdef WINDOWS+fromBytes bs =+  let ws = WindowsString . BS16.toShort $ bs+  in either throwM (const . pure $ ws) $ decodeWith ucs2le ws+#else+fromBytes = pure . PosixString . BS.toShort+#endif+++#ifdef WINDOWS_DOC+-- | QuasiQuote a 'WindowsString'. This accepts Unicode characters+-- and encodes as UTF-16LE on windows.+#else+-- | QuasiQuote a 'PosixString'. This accepts Unicode characters+-- and encodes as UTF-8 on unix.+#endif+pstr :: QuasiQuoter+pstr =+  QuasiQuoter+#ifdef WINDOWS+  { quoteExp = \s -> do+      ps <- either (fail . show) pure $ encodeWith (mkUTF16le ErrorOnCodingFailure) s+      lift ps+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#else+  { quoteExp = \s -> do+      ps <- either (fail . show) pure $ encodeWith (mkUTF8 ErrorOnCodingFailure) s+      lift ps+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#endif+++-- | Unpack a platform string to a list of platform words.+unpack :: PLATFORM_STRING -> [PLATFORM_WORD]+#ifdef WINDOWS+unpack (WindowsString ba) = WindowsChar <$> BS16.unpack ba+#else+unpack (PosixString ba) = PosixChar <$> BS.unpack ba+#endif+++-- | Pack a list of platform words to a platform string.+--+-- Note that using this in conjunction with 'unsafeFromChar' to+-- convert from @[Char]@ to platform string is probably not what+-- you want, because it will truncate unicode code points.+pack :: [PLATFORM_WORD] -> PLATFORM_STRING+#ifdef WINDOWS+pack = WindowsString . BS16.pack . fmap (\(WindowsChar w) -> w)+#else+pack = PosixString . BS.pack . fmap (\(PosixChar w) -> w)+#endif+++#ifdef WINDOWS+-- | Truncates to 2 octets.+unsafeFromChar :: Char -> PLATFORM_WORD+unsafeFromChar = WindowsChar . fromIntegral . fromEnum+#else+-- | Truncates to 1 octet.+unsafeFromChar :: Char -> PLATFORM_WORD+unsafeFromChar = PosixChar . fromIntegral . fromEnum+#endif++-- | Converts back to a unicode codepoint (total).+toChar :: PLATFORM_WORD -> Char+#ifdef WINDOWS+toChar (WindowsChar w) = chr $ fromIntegral w+#else+toChar (PosixChar w) = chr $ fromIntegral w+#endif
+ System/OsString/Internal.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UnliftedFFITypes #-}++module System.OsString.Internal where++import System.OsString.Internal.Types++import Control.Monad.Catch+    ( MonadThrow )+import Data.ByteString+    ( ByteString )+import Data.Char+import Language.Haskell.TH.Quote+    ( QuasiQuoter (..) )+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )+import System.IO+    ( TextEncoding )++import System.OsPath.Encoding ( EncodingException(..) )+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import qualified System.OsString.Windows as PF+#else+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import qualified System.OsString.Posix as PF+#endif+++++-- | Partial unicode friendly encoding.+--+-- On windows this encodes as UTF16-LE (strictly), which is a pretty good guess.+-- On unix this encodes as UTF8 (strictly), which is a good guess.+--+-- Throws a 'EncodingException' if encoding fails.+encodeUtf :: MonadThrow m => String -> m OsString+encodeUtf = fmap OsString . PF.encodeUtf++-- | Encode an 'OsString' given the platform specific encodings.+encodeWith :: TextEncoding  -- ^ unix text encoding+           -> TextEncoding  -- ^ windows text encoding+           -> String+           -> Either EncodingException OsString+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+encodeWith _ winEnc str = OsString <$> PF.encodeWith winEnc str+#else+encodeWith unixEnc _ str = OsString <$> PF.encodeWith unixEnc str+#endif++-- | Like 'encodeUtf', except this mimics the behavior of the base library when doing filesystem+-- operations, which is:+--+-- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,+--    but PEP 383 only works properly on UTF-8 encodings, so good luck)+-- 2. on windows does permissive UTF-16 encoding, where coding errors generate+--    Chars in the surrogate range+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+encodeFS :: String -> IO OsString+encodeFS = fmap OsString . PF.encodeFS+++-- | Partial unicode friendly decoding.+--+-- On windows this decodes as UTF16-LE (strictly), which is a pretty good guess.+-- On unix this decodes as UTF8 (strictly), which is a good guess. Note that+-- filenames on unix are encoding agnostic char arrays.+--+-- Throws a 'EncodingException' if decoding fails.+decodeUtf :: MonadThrow m => OsString -> m String+decodeUtf (OsString x) = PF.decodeUtf x++-- | Decode an 'OsString' with the specified encoding.+--+-- The String is forced into memory to catch all exceptions.+decodeWith :: TextEncoding  -- ^ unix text encoding+           -> TextEncoding  -- ^ windows text encoding+           -> OsString+           -> Either EncodingException String+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+decodeWith _ winEnc (OsString x) = PF.decodeWith winEnc x+#else+decodeWith unixEnc _ (OsString x) = PF.decodeWith unixEnc x+#endif+++-- | Like 'decodeUtf', except this mimics the behavior of the base library when doing filesystem+-- operations, which is:+--+-- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,+--    but PEP 383 only works properly on UTF-8 encodings, so good luck)+-- 2. on windows does permissive UTF-16 encoding, where coding errors generate+--    Chars in the surrogate range+--+-- Looking up the locale requires IO. If you're not worried about calls+-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure+-- to deeply evaluate the result to catch exceptions).+decodeFS :: OsString -> IO String+decodeFS (OsString x) = PF.decodeFS x+++-- | Constructs an @OsString@ from a ByteString.+--+-- On windows, this ensures valid UCS-2LE, on unix it is passed unchanged/unchecked.+--+-- Throws 'EncodingException' on invalid UCS-2LE on windows (although unlikely).+fromBytes :: MonadThrow m+          => ByteString+          -> m OsString+fromBytes = fmap OsString . PF.fromBytes+++-- | QuasiQuote an 'OsString'. This accepts Unicode characters+-- and encodes as UTF-8 on unix and UTF-16 on windows.+osstr :: QuasiQuoter+osstr =+  QuasiQuoter+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+  { quoteExp = \s -> do+      osp <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF16le ErrorOnCodingFailure) $ s+      lift osp+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#else+  { quoteExp = \s -> do+      osp <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF8 ErrorOnCodingFailure) $ s+      lift osp+  , quotePat  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"+  , quoteType = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a type)"+  , quoteDec  = \_ ->+      fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"+  }+#endif+++-- | Unpack an 'OsString' to a list of 'OsChar'.+unpack :: OsString -> [OsChar]+unpack (OsString x) = OsChar <$> PF.unpack x+++-- | Pack a list of 'OsChar' to an 'OsString'+--+-- Note that using this in conjunction with 'unsafeFromChar' to+-- convert from @[Char]@ to 'OsString' is probably not what+-- you want, because it will truncate unicode code points.+pack :: [OsChar] -> OsString+pack = OsString . PF.pack . fmap (\(OsChar x) -> x)+++-- | Truncates on unix to 1 and on Windows to 2 octets.+unsafeFromChar :: Char -> OsChar+unsafeFromChar = OsChar . PF.unsafeFromChar++-- | Converts back to a unicode codepoint (total).+toChar :: OsChar -> Char+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+toChar (OsChar (WindowsChar w)) = chr $ fromIntegral w+#else+toChar (OsChar (PosixChar w)) = chr $ fromIntegral w+#endif+
+ System/OsString/Internal/Types.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE PatternSynonyms #-}++module System.OsString.Internal.Types+  (+    WindowsString(..)+  , pattern WS+  , unWS+  , PosixString(..)+  , unPS+  , pattern PS+  , PlatformString+  , WindowsChar(..)+  , unWW+  , pattern WW+  , PosixChar(..)+  , unPW+  , pattern PW+  , PlatformChar+  , OsString(..)+  , OsChar(..)+  )+where+++import Control.DeepSeq+import Data.Data+import Data.Word+import Language.Haskell.TH.Syntax+    ( Lift (..), lift )+#if !MIN_VERSION_base(4,11,0)+import Data.Semigroup+#endif+import GHC.Generics (Generic)++import System.OsPath.Encoding.Internal+import qualified System.OsPath.Data.ByteString.Short as BS+import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16+#if MIN_VERSION_template_haskell(2,16,0)+import qualified Language.Haskell.TH.Syntax as TH+#endif++-- Using unpinned bytearrays to avoid Heap fragmentation and+-- which are reasonably cheap to pass to FFI calls+-- wrapped with typeclass-friendly types allowing to avoid CPP+--+-- Note that, while unpinned bytearrays incur a memcpy on each+-- FFI call, this overhead is generally much preferable to+-- the memory fragmentation of pinned bytearrays++-- | Commonly used windows string as wide character bytes.+newtype WindowsString = WindowsString { getWindowsString :: BS.ShortByteString }+  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData)++-- | Decodes as UCS-2.+instance Show WindowsString where+  -- cWcharsToChars_UCS2 is total+  show = show . cWcharsToChars_UCS2 . BS16.unpack . getWindowsString++-- | Just a short bidirectional synonym for 'WindowsString' constructor.+pattern WS :: BS.ShortByteString -> WindowsString+pattern WS { unWS } <- WindowsString unWS where+  WS a = WindowsString a+#if __GLASGOW_HASKELL__ >= 802+{-# COMPLETE WS #-}+#endif+++instance Lift WindowsString where+  lift (WindowsString bs)+    = [| WindowsString (BS.pack $(lift $ BS.unpack bs)) :: WindowsString |]+#if MIN_VERSION_template_haskell(2,17,0)+  liftTyped = TH.unsafeCodeCoerce . TH.lift+#elif MIN_VERSION_template_haskell(2,16,0)+  liftTyped = TH.unsafeTExpCoerce . TH.lift+#endif++-- | Commonly used Posix string as uninterpreted @char[]@+-- array.+newtype PosixString = PosixString { getPosixString :: BS.ShortByteString }+  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData)++-- | Prints the raw bytes without decoding.+instance Show PosixString where+  show (PosixString ps) = show ps++-- | Just a short bidirectional synonym for 'PosixString' constructor.+pattern PS :: BS.ShortByteString -> PosixString+pattern PS { unPS } <- PosixString unPS where+  PS a = PosixString a+#if __GLASGOW_HASKELL__ >= 802+{-# COMPLETE PS #-}+#endif++instance Lift PosixString where+  lift (PosixString bs)+    = [| PosixString (BS.pack $(lift $ BS.unpack bs)) :: PosixString |]+#if MIN_VERSION_template_haskell(2,17,0)+  liftTyped = TH.unsafeCodeCoerce . TH.lift+#elif MIN_VERSION_template_haskell(2,16,0)+  liftTyped = TH.unsafeTExpCoerce . TH.lift+#endif+++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+type PlatformString = WindowsString+#else+type PlatformString = PosixString+#endif++newtype WindowsChar = WindowsChar { getWindowsChar :: Word16 }+  deriving (Eq, Ord, Typeable, Generic, NFData)++instance Show WindowsChar where+  show (WindowsChar wc) = show wc++newtype PosixChar   = PosixChar { getPosixChar :: Word8 }+  deriving (Eq, Ord, Typeable, Generic, NFData)++instance Show PosixChar where+  show (PosixChar pc) = show pc++-- | Just a short bidirectional synonym for 'WindowsChar' constructor.+pattern WW :: Word16 -> WindowsChar+pattern WW { unWW } <- WindowsChar unWW where+  WW a = WindowsChar a+#if __GLASGOW_HASKELL__ >= 802+{-# COMPLETE WW #-}+#endif++-- | Just a short bidirectional synonym for 'PosixChar' constructor.+pattern PW :: Word8 -> PosixChar+pattern PW { unPW } <- PosixChar unPW where+  PW a = PosixChar a+#if __GLASGOW_HASKELL__ >= 802+{-# COMPLETE PW #-}+#endif++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+type PlatformChar = WindowsChar+#else+type PlatformChar = PosixChar+#endif+++-- | Newtype representing short operating system specific strings.+--+-- Internally this is either 'WindowsString' or 'PosixString',+-- depending on the platform. Both use unpinned+-- 'ShortByteString' for efficiency.+--+-- The constructor is only exported via "System.OsString.Internal.Types", since+-- dealing with the internals isn't generally recommended, but supported+-- in case you need to write platform specific code.+newtype OsString = OsString { getOsString :: PlatformString }+  deriving (Typeable, Generic, NFData)++-- | On windows, decodes as UCS-2. On unix prints the raw bytes without decoding.+instance Show OsString where+  show (OsString os) = show os++-- | Byte equality of the internal representation.+instance Eq OsString where+  (OsString a) == (OsString b) = a == b++-- | Byte ordering of the internal representation.+instance Ord OsString where+  compare (OsString a) (OsString b) = compare a b+++-- | \"String-Concatenation\" for 'OsString'. This is __not__ the same+-- as '(</>)'.+instance Monoid OsString where+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+    mempty      = OsString (WindowsString BS.empty)+#if MIN_VERSION_base(4,16,0)+    mappend = (<>)+#else+    mappend (OsString (WindowsString a)) (OsString (WindowsString b))+      = OsString (WindowsString (mappend a b))+#endif+#else+    mempty      = OsString (PosixString BS.empty)+#if MIN_VERSION_base(4,16,0)+    mappend = (<>)+#else+    mappend (OsString (PosixString a)) (OsString (PosixString b))+      = OsString (PosixString (mappend a b))+#endif+#endif+#if MIN_VERSION_base(4,11,0)+instance Semigroup OsString where+#if MIN_VERSION_base(4,16,0)+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+    (<>) (OsString (WindowsString a)) (OsString (WindowsString b))+      = OsString (WindowsString (mappend a b))+#else+    (<>) (OsString (PosixString a)) (OsString (PosixString b))+      = OsString (PosixString (mappend a b))+#endif+#else+    (<>) = mappend+#endif+#endif+++instance Lift OsString where+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+  lift (OsString (WindowsString bs))+    = [| OsString (WindowsString (BS.pack $(lift $ BS.unpack bs))) :: OsString |]+#else+  lift (OsString (PosixString bs))+    = [| OsString (PosixString (BS.pack $(lift $ BS.unpack bs))) :: OsString |]+#endif+#if MIN_VERSION_template_haskell(2,17,0)+  liftTyped = TH.unsafeCodeCoerce . TH.lift+#elif MIN_VERSION_template_haskell(2,16,0)+  liftTyped = TH.unsafeTExpCoerce . TH.lift+#endif+++-- | Newtype representing a code unit.+--+-- On Windows, this is restricted to two-octet codepoints 'Word16',+-- on POSIX one-octet ('Word8').+newtype OsChar = OsChar { getOsChar :: PlatformChar }+  deriving (Typeable, Generic, NFData)++instance Show OsChar where+  show (OsChar pc) = show pc++-- | Byte equality of the internal representation.+instance Eq OsChar where+  (OsChar a) == (OsChar b) = a == b++-- | Byte ordering of the internal representation.+instance Ord OsChar where+  compare (OsChar a) (OsChar b) = compare a b+
+ System/OsString/Posix.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE CPP #-}+#undef WINDOWS+#define MODULE_NAME     Posix+#define PLATFORM_STRING PosixString+#define PLATFORM_WORD   PosixChar+#define IS_WINDOWS      False+#include "Common.hs"
+ System/OsString/Windows.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE CPP #-}+#undef POSIX+#define MODULE_NAME     Windows+#define PLATFORM_STRING WindowsString+#define PLATFORM_WORD   WindowsChar+#define IS_WINDOWS      True+#define WINDOWS+#include "Common.hs"+#undef MODULE_NAME+#undef FILEPATH_NAME+#undef OSSTRING_NAME+#undef IS_WINDOWS+#undef WINDOWS
+ bench/BenchFilePath.hs view
@@ -0,0 +1,311 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE QuasiQuotes #-}++module Main where++import System.OsPath.Types+import System.OsPath.Encoding ( ucs2le )+import System.Environment+import qualified System.OsString.Internal.Types as OST+import qualified Data.ByteString.Short as SBS++import TastyBench+import Data.List+import Data.Maybe+import GHC.IO.Encoding++import qualified System.FilePath.Posix as PF+import qualified System.FilePath.Posix as WF+import qualified System.OsString.Posix as OSP+import qualified System.OsString.Windows as WSP+import qualified System.OsPath.Posix as APF+import qualified System.OsPath.Windows as AWF+++data Config = Config {+    format  :: Format+  , stdev   :: Double+  , timeout :: Integer+  }++data Format = Print+            | CSV+  deriving (Read, Show)++defaultConfig :: Config+defaultConfig = Config defaultFormat defaultStdev defaultTimeout++defaultFormat :: Format+defaultFormat = Print++defaultStdev :: Double+defaultStdev = 0.02++defaultTimeout :: Integer+defaultTimeout = 800000++parseConfig :: [String] -> Config+parseConfig [] = defaultConfig+parseConfig xs =+  let format'  = maybe defaultFormat  (read . fromJust . stripPrefix "--format=" ) $ find ("--format="  `isPrefixOf`) xs+      stdev'   = maybe defaultStdev   (read . fromJust . stripPrefix "--stdev="  ) $ find ("--stdev="   `isPrefixOf`) xs+      timeout' = maybe defaultTimeout (read . fromJust . stripPrefix "--timeout=") $ find ("--timeout=" `isPrefixOf`) xs+  in Config format' stdev' timeout'+++main :: IO ()+main = do+  setLocaleEncoding utf8+  args <- getArgs+  let config = parseConfig args+  benchGroup config+    [ ("filepath (string)",+      [("splitExtension (posix)"      , nf PF.splitExtension posixPath)+      ,("splitExtension (windows)"    , nf WF.splitExtension windowsPath)+      ,("takeExtension (posix)"       , nf PF.takeExtension posixPath)+      ,("takeExtension (windows)"     , nf WF.takeExtension windowsPath)+      ,("replaceExtension (posix)"    , nf (PF.replaceExtension ".lol") posixPath)+      ,("replaceExtension (windows)"  , nf (WF.replaceExtension ".lol") windowsPath)+      ,("dropExtension (posix)"       , nf PF.dropExtension posixPath)+      ,("dropExtension (windows)"     , nf WF.dropExtension windowsPath)+      ,("addExtension (posix)"        , nf (PF.addExtension ".lol") posixPath)+      ,("addExtension (windows)"      , nf (WF.addExtension ".lol") windowsPath)+      ,("hasExtension (posix)"        , nf PF.hasExtension posixPath)+      ,("hasExtension (windows)"      , nf WF.hasExtension windowsPath)+      ,("splitExtensions (posix)"     , nf PF.splitExtensions posixPath)+      ,("splitExtensions (windows)"   , nf WF.splitExtensions windowsPath)+      ,("dropExtensions (posix)"      , nf PF.dropExtensions posixPath)+      ,("dropExtensions (windows)"    , nf WF.dropExtensions windowsPath)+      ,("takeExtensions (posix)"      , nf PF.takeExtensions posixPath)+      ,("takeExtensions (windows)"    , nf WF.takeExtensions windowsPath)+      ,("replaceExtensions (posix)"   , nf (PF.replaceExtensions ".lol") posixPath)+      ,("replaceExtensions (windows)" , nf (WF.replaceExtensions ".lol") windowsPath)+      ,("isExtensionOf (posix)"       , nf (PF.isExtensionOf ".lol") posixPath)+      ,("isExtensionOf (windows)"     , nf (WF.isExtensionOf ".lol") windowsPath)+      ,("stripExtension (posix)"      , nf (PF.stripExtension ".lol") posixPath)+      ,("stripExtension (windows)"    , nf (WF.stripExtension ".lol") windowsPath)++      ,("splitFileName (posix)"       , nf PF.splitFileName posixPath)+      ,("splitFileName (windows)"     , nf WF.splitFileName windowsPath)+      ,("takeFileName (posix)"        , nf PF.takeFileName posixPath)+      ,("takeFileName (windows)"      , nf WF.takeFileName windowsPath)+      ,("replaceFileName (posix)"     , nf (PF.replaceFileName "lol") posixPath)+      ,("replaceFileName (windows)"   , nf (WF.replaceFileName "lol") windowsPath)+      ,("dropFileName (posix)"        , nf PF.dropFileName posixPath)+      ,("dropFileName (windows)"      , nf WF.dropFileName windowsPath)+      ,("takeBaseName (posix)"        , nf PF.takeBaseName posixPath)+      ,("takeBaseName (windows)"      , nf WF.takeBaseName windowsPath)+      ,("replaceBaseName (posix)"     , nf (PF.replaceBaseName "lol") posixPath)+      ,("replaceBaseName (windows)"   , nf (WF.replaceBaseName "lol") windowsPath)+      ,("takeDirectory (posix)"       , nf PF.takeDirectory posixPath)+      ,("takeDirectory (windows)"     , nf WF.takeDirectory windowsPath)+      ,("replaceDirectory (posix)"    , nf (PF.replaceDirectory "lol") posixPath)+      ,("replaceDirectory (windows)"  , nf (WF.replaceDirectory "lol") windowsPath)+      ,("combine (posix)"             , nf (PF.combine "lol") posixPath)+      ,("combine (windows)"           , nf (WF.combine "lol") windowsPath)+      ,("splitPath (posix)"           , nf PF.splitPath    posixPath)+      ,("splitPath (windows)"         , nf WF.splitPath    windowsPath)+      ,("joinPath (posix)"            , nf PF.joinPath     (PF.splitPath posixPath))+      ,("joinPath (windows)"          , nf WF.joinPath     (WF.splitPath windowsPath))+      ,("splitDirectories (posix)"    , nf PF.splitDirectories    posixPath)+      ,("splitDirectories (windows)"  , nf WF.splitDirectories    windowsPath)++      ,("splitDrive (posix)"          , nf PF.splitDrive    posixPath)+      ,("splitDrive (windows)"        , nf WF.splitDrive    windowsPath)+      ,("joinDrive (posix)"           , nf (PF.joinDrive "/")    posixPath)+      ,("joinDrive (windows)"         , nf (WF.joinDrive "C:\\")  windowsPath)+      ,("takeDrive (posix)"           , nf PF.takeDrive    posixPath)+      ,("takeDrive (windows)"         , nf WF.takeDrive    windowsPath)+      ,("hasDrive (posix)"            , nf PF.hasDrive    posixPath)+      ,("hasDrive (windows)"          , nf WF.hasDrive    windowsPath)+      ,("dropDrive (posix)"           , nf PF.dropDrive    posixPath)+      ,("dropDrive (windows)"         , nf WF.dropDrive    windowsPath)+      ,("isDrive (posix)"             , nf PF.isDrive    posixPath)+      ,("isDrive (windows)"           , nf WF.isDrive    windowsPath)++      ,("hasTrailingPathSeparator (posix)"    , nf PF.hasTrailingPathSeparator    posixPath)+      ,("hasTrailingPathSeparator (windows)"  , nf WF.hasTrailingPathSeparator    windowsPath)+      ,("addTrailingPathSeparator (posix)"    , nf PF.addTrailingPathSeparator    posixPath)+      ,("addTrailingPathSeparator (windows)"  , nf WF.addTrailingPathSeparator    windowsPath)+      ,("dropTrailingPathSeparator (posix)"   , nf PF.addTrailingPathSeparator    posixPath)+      ,("dropTrailingPathSeparator (windows)" , nf WF.addTrailingPathSeparator    windowsPath)++      ,("normalise (posix)"           , nf PF.normalise    posixPath)+      ,("normalise (windows)"         , nf WF.normalise    windowsPath)+      ,("equalFilePath (posix)"       , nf (PF.equalFilePath "abc/def/zs")   posixPath)+      ,("equalFilePath (windows)"     , nf (WF.equalFilePath "abc/def/zs")   windowsPath)+      ,("makeRelative (posix)"        , nf (PF.makeRelative "abc/def/zs")   posixPath)+      ,("makeRelative (windows)"      , nf (WF.makeRelative "abc/def/zs")   windowsPath)+      ,("isRelative (posix)"          , nf PF.isRelative    posixPath)+      ,("isRelative (windows)"        , nf WF.isRelative    windowsPath)+      ,("isAbsolute (posix)"          , nf PF.isAbsolute    posixPath)+      ,("isAbsolute (windows)"        , nf WF.isAbsolute    windowsPath)+      ,("isValid (posix)"             , nf PF.isValid    posixPath)+      ,("isValid (windows)"           , nf WF.isValid    windowsPath)+      ,("makeValid (posix)"           , nf PF.makeValid    posixPath)+      ,("makeValid (windows)"         , nf WF.makeValid    windowsPath)++      ,("splitSearchPath (posix)"    , nf PF.splitSearchPath posixSearchPath)+      ,("splitSearchPath (windows)"  , nf WF.splitSearchPath windowsSearchPath)+      ]+      )++    , ("filepath (AFPP)",+      [ ("splitExtension (posix)"      , nf APF.splitExtension posixPathAFPP)+      , ("splitExtension (windows)"    , nf AWF.splitExtension windowsPathAFPP)+      , ("takeExtension (posix)"       , nf APF.takeExtension posixPathAFPP)+      , ("takeExtension (windows)"     , nf AWF.takeExtension windowsPathAFPP)+      , ("replaceExtension (posix)"    , nf (APF.replaceExtension [OSP.pstr|.lol|]) posixPathAFPP)+      , ("replaceExtension (windows)"  , nf (AWF.replaceExtension [WSP.pstr|.lol|]) windowsPathAFPP)+      , ("dropExtension (posix)"       , nf APF.dropExtension posixPathAFPP)+      , ("dropExtension (windows)"     , nf AWF.dropExtension windowsPathAFPP)+      , ("addExtension (posix)"        , nf (APF.addExtension [OSP.pstr|.lol|]) posixPathAFPP)+      , ("addExtension (windows)"      , nf (AWF.addExtension [WSP.pstr|.lol|]) windowsPathAFPP)+      , ("hasExtension (posix)"        , nf APF.hasExtension posixPathAFPP)+      , ("hasExtension (windows)"      , nf AWF.hasExtension windowsPathAFPP)+      , ("splitExtensions (posix)"     , nf APF.splitExtensions posixPathAFPP)+      , ("splitExtensions (windows)"   , nf AWF.splitExtensions windowsPathAFPP)+      , ("dropExtensions (posix)"      , nf APF.dropExtensions posixPathAFPP)+      , ("dropExtensions (windows)"    , nf AWF.dropExtensions windowsPathAFPP)+      , ("takeExtensions (posix)"      , nf APF.takeExtensions posixPathAFPP)+      , ("takeExtensions (windows)"    , nf AWF.takeExtensions windowsPathAFPP)+      , ("replaceExtensions (posix)"   , nf (APF.replaceExtensions [OSP.pstr|.lol|]) posixPathAFPP)+      , ("replaceExtensions (windows)" , nf (AWF.replaceExtensions [WSP.pstr|.lol|]) windowsPathAFPP)+      , ("isExtensionOf (posix)"       , nf (APF.isExtensionOf [OSP.pstr|.lol|]) posixPathAFPP)+      , ("isExtensionOf (windows)"     , nf (AWF.isExtensionOf [WSP.pstr|.lol|]) windowsPathAFPP)+      , ("stripExtension (posix)"      , nf (APF.stripExtension [OSP.pstr|.lol|]) posixPathAFPP)+      , ("stripExtension (windows)"    , nf (AWF.stripExtension [WSP.pstr|.lol|]) windowsPathAFPP)++      , ("splitFileName (posix)"       , nf APF.splitFileName posixPathAFPP)+      , ("splitFileName (windows)"     , nf AWF.splitFileName windowsPathAFPP)+      , ("takeFileName (posix)"        , nf APF.takeFileName posixPathAFPP)+      , ("takeFileName (windows)"      , nf AWF.takeFileName windowsPathAFPP)+      , ("replaceFileName (posix)"     , nf (APF.replaceFileName [OSP.pstr|lol|]) posixPathAFPP)+      , ("replaceFileName (windows)"   , nf (AWF.replaceFileName [WSP.pstr|lol|]) windowsPathAFPP)+      , ("dropFileName (posix)"        , nf APF.dropFileName posixPathAFPP)+      , ("dropFileName (windows)"      , nf AWF.dropFileName windowsPathAFPP)+      , ("takeBaseName (posix)"        , nf APF.takeBaseName posixPathAFPP)+      , ("takeBaseName (windows)"      , nf AWF.takeBaseName windowsPathAFPP)+      , ("replaceBaseName (posix)"     , nf (APF.replaceBaseName [OSP.pstr|lol|]) posixPathAFPP)+      , ("replaceBaseName (windows)"   , nf (AWF.replaceBaseName [WSP.pstr|lol|]) windowsPathAFPP)+      , ("takeDirectory (posix)"       , nf APF.takeDirectory posixPathAFPP)+      , ("takeDirectory (windows)"     , nf AWF.takeDirectory windowsPathAFPP)+      , ("replaceDirectory (posix)"    , nf (APF.replaceDirectory [OSP.pstr|lol|]) posixPathAFPP)+      , ("replaceDirectory (windows)"  , nf (AWF.replaceDirectory [WSP.pstr|lol|]) windowsPathAFPP)+      , ("combine (posix)"             , nf (APF.combine [OSP.pstr|lol|]) posixPathAFPP)+      , ("combine (windows)"           , nf (AWF.combine [WSP.pstr|lol|]) windowsPathAFPP)+      , ("splitPath (posix)"           , nf APF.splitPath    posixPathAFPP)+      , ("splitPath (windows)"         , nf AWF.splitPath    windowsPathAFPP)+      , ("joinPath (posix)"            , nf APF.joinPath     (APF.splitPath posixPathAFPP))+      , ("joinPath (windows)"          , nf AWF.joinPath     (AWF.splitPath windowsPathAFPP))+      , ("splitDirectories (posix)"    , nf APF.splitDirectories    posixPathAFPP)+      , ("splitDirectories (windows)"  , nf AWF.splitDirectories    windowsPathAFPP)++      , ("splitDrive (posix)"          , nf APF.splitDrive    posixPathAFPP)+      , ("splitDrive (windows)"        , nf AWF.splitDrive    windowsPathAFPP)+      , ("joinDrive (posix)"           , nf (APF.joinDrive [OSP.pstr|/|])    posixPathAFPP)+      , ("joinDrive (windows)"         , nf (AWF.joinDrive [WSP.pstr|C:\|])    windowsPathAFPP)+      , ("takeDrive (posix)"           , nf APF.takeDrive    posixPathAFPP)+      , ("takeDrive (windows)"         , nf AWF.takeDrive    windowsPathAFPP)+      , ("hasDrive (posix)"            , nf APF.hasDrive    posixPathAFPP)+      , ("hasDrive (windows)"          , nf AWF.hasDrive    windowsPathAFPP)+      , ("dropDrive (posix)"           , nf APF.dropDrive    posixPathAFPP)+      , ("dropDrive (windows)"         , nf AWF.dropDrive    windowsPathAFPP)+      , ("isDrive (posix)"             , nf APF.isDrive    posixPathAFPP)+      , ("isDrive (windows)"           , nf AWF.isDrive    windowsPathAFPP)++      , ("hasTrailingPathSeparator (posix)"    , nf APF.hasTrailingPathSeparator    posixPathAFPP)+      , ("hasTrailingPathSeparator (windows)"  , nf AWF.hasTrailingPathSeparator    windowsPathAFPP)+      , ("addTrailingPathSeparator (posix)"    , nf APF.addTrailingPathSeparator    posixPathAFPP)+      , ("addTrailingPathSeparator (windows)"  , nf AWF.addTrailingPathSeparator    windowsPathAFPP)+      , ("dropTrailingPathSeparator (posix)"   , nf APF.addTrailingPathSeparator    posixPathAFPP)+      , ("dropTrailingPathSeparator (windows)" , nf AWF.addTrailingPathSeparator    windowsPathAFPP)++      , ("normalise (posix)"           , nf APF.normalise    posixPathAFPP)+      , ("normalise (windows)"         , nf AWF.normalise    windowsPathAFPP)+      , ("equalFilePath (posix)"       , nf (APF.equalFilePath [OSP.pstr|abc/def/zs|])   posixPathAFPP)+      , ("equalFilePath (windows)"     , nf (AWF.equalFilePath [WSP.pstr|abc/def/zs|])   windowsPathAFPP)+      , ("makeRelative (posix)"        , nf (APF.makeRelative [OSP.pstr|abc/def/zs|])   posixPathAFPP)+      , ("makeRelative (windows)"      , nf (AWF.makeRelative [WSP.pstr|abc/def/zs|])   windowsPathAFPP)+      , ("isRelative (posix)"          , nf APF.isRelative    posixPathAFPP)+      , ("isRelative (windows)"        , nf AWF.isRelative    windowsPathAFPP)+      , ("isAbsolute (posix)"          , nf APF.isAbsolute    posixPathAFPP)+      , ("isAbsolute (windows)"        , nf AWF.isAbsolute    windowsPathAFPP)+      , ("isValid (posix)"             , nf APF.isValid    posixPathAFPP)+      , ("isValid (windows)"           , nf AWF.isValid    windowsPathAFPP)+      , ("makeValid (posix)"           , nf APF.makeValid    posixPathAFPP)+      , ("makeValid (windows)"         , nf AWF.makeValid    windowsPathAFPP)++      , ("splitSearchPath (posix)"     , nf APF.splitSearchPath posixSearchPathAFPP)+      , ("splitSearchPath (windows)"   , nf AWF.splitSearchPath windowsSearchPathAFPP)+      ]+      )++    , ("encoding/decoding",+      [ ("decodeUtf (posix)"                  , nf (APF.decodeUtf @Maybe) posixPathAFPP)+      , ("decodeUtf (windows)"                , nf (AWF.decodeUtf @Maybe) windowsPathAFPP)+      , ("decodeWith (windows)"               , nf (AWF.decodeWith ucs2le) windowsPathAFPP)++      , ("encodeUtf (posix)"                  , nf (APF.encodeUtf @Maybe) posixPath)+      , ("encodeUtf (windows)"                , nf (AWF.encodeUtf @Maybe) windowsPath)+      , ("encodeWith (windows)"               , nf (AWF.encodeWith ucs2le) windowsPath)++      , ("unpack PlatformString (posix)"       , nf APF.unpack posixPathAFPP)+      , ("unpack PlatformString (windows)"     , nf AWF.unpack windowsPathAFPP)+      , ("pack PlatformString (posix)"         , nf APF.pack (APF.unpack posixPathAFPP))+      , ("pack PlatformString (windows)"       , nf AWF.pack (AWF.unpack windowsPathAFPP))++      , ("fromBytes (posix)"                  , nf (OSP.fromBytes @Maybe) (SBS.fromShort . OST.getPosixString $ posixPathAFPP))+      , ("fromBytes (windows)"                , nf (WSP.fromBytes @Maybe) (SBS.fromShort . OST.getWindowsString $ windowsPathAFPP))+      ]+      )+    ]+++posixPath :: FilePath+posixPath = "/foo/bar/bath/baz/baz/tz/fooooooooooooooo/laaaaaaaaaaaaaaa/baaaaaaaaaaaaar/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk/kkkkkkkkkkkkkkkkkk/h/h/h/a/s/r/a/h/gt/r/r/r/s/s.txt"++windowsPath :: FilePath+windowsPath = "C:\\foo\\bar\\bath\\baz\\baz\\tz\\fooooooooooooooo\\laaaaaaaaaaaaaaa\\baaaaaaaaaaaaar\\zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\\zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\\kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk\\kkkkkkkkkkkkkkkkkk\\h\\h\\h\\a\\s\\r\\a\\h\\gt\\r\\r\\r\\s\\s.txt"++posixPathAFPP :: PosixPath+posixPathAFPP = [OSP.pstr|/foo/bar/bath/baz/baz/tz/fooooooooooooooo/laaaaaaaaaaaaaaa/baaaaaaaaaaaaar/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk/kkkkkkkkkkkkkkkkkk/h/h/h/a/s/r/a/h/gt/r/r/r/s/s.txt|]++windowsPathAFPP :: WindowsPath+windowsPathAFPP = [WSP.pstr|C:\\foo\\bar\\bath\\baz\\baz\\tz\\fooooooooooooooo\\laaaaaaaaaaaaaaa\\baaaaaaaaaaaaar\\zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\\zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\\kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk\\kkkkkkkkkkkkkkkkkk\\h\\h\\h\\a\\s\\r\\a\\h\\gt\\r\\r\\r\\s\\s.txt|]++posixSearchPath :: FilePath+posixSearchPath = ":foo:bar:bath:baz:baz:tz:fooooooooooooooo:laaaaaaaaaaaaaaa:baaaaaaaaaaaaar:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk:kkkkkkkkkkkkkkkkkk:h:h:h:a:s:r:a:h:gt:r:r:r:s:s.txt"++windowsSearchPath :: FilePath+windowsSearchPath = "foo;bar;bath;baz;baz;tz;fooooooooooooooo;laaaaaaaaaaaaaaa;baaaaaaaaaaaaar;zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;kkkkkkkkkkkkkkkkkk;h;h;h;a;s;r;a;h;gt;r;r;r;s;s.txt"++posixSearchPathAFPP :: PosixString+posixSearchPathAFPP = [OSP.pstr|:foo:bar:bath:baz:baz:tz:fooooooooooooooo:laaaaaaaaaaaaaaa:baaaaaaaaaaaaar:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk:kkkkkkkkkkkkkkkkkk:h:h:h:a:s:r:a:h:gt:r:r:r:s:s.txt|]++windowsSearchPathAFPP :: WindowsString+windowsSearchPathAFPP = [WSP.pstr|foo;bar;bath;baz;baz;tz;fooooooooooooooo;laaaaaaaaaaaaaaa;baaaaaaaaaaaaar;zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz;kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;kkkkkkkkkkkkkkkkkk;h;h;h;a;s;r;a;h;gt;r;r;r;s;s.txt|]+++benchGroup :: Config -> [(String, [(String, Benchmarkable)])] -> IO ()+benchGroup _ [] = pure ()+benchGroup format ((name, benchs):xs) = do+  putStrLn name+  bench format benchs+  benchGroup format xs++bench :: Config -> [(String, Benchmarkable)] -> IO ()+bench _ [] = pure ()+bench config@Config{..} (x:xs) = do+  let (name, benchmarkable) = x+  case format of+    CSV -> putStr (name ++ ",")+    Print -> putStr ("    " ++ name ++ ": ")+  est <- measureUntil CpuTime False (Timeout timeout "") (RelStDev stdev) benchmarkable+  case format of+    CSV -> putStr $ csvEstimate est+    Print -> putStr $ "\n      " ++ prettyEstimate est+  putStr "\n"+  bench config xs+
+ bench/TastyBench.hs view
@@ -0,0 +1,526 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}++module TastyBench where++import Prelude hiding (Int, Integer)+import qualified Prelude+import Control.DeepSeq (NFData, force)+import Control.Exception (evaluate)+import Data.Data (Typeable)+import Data.Word (Word64)+#if !MIN_VERSION_base(4,10,0)+import Data.Int ( Int64 )+#endif+#if MIN_VERSION_base(4,6,0)+import GHC.Stats+#endif+import System.CPUTime+import System.IO+import System.IO.Unsafe+import System.Mem+import Text.Printf (printf)+++data Timeout+  = Timeout+    Prelude.Integer -- ^ number of microseconds (e. g., 200000)+    String          -- ^ textual representation (e. g., @"0.2s"@)+  | NoTimeout+  deriving (Show)+++-- | In addition to @--stdev@ command-line option,+-- one can adjust target relative standard deviation+-- for individual benchmarks and groups of benchmarks+-- using 'adjustOption' and 'localOption'.+--+-- E. g., set target relative standard deviation to 2% as follows:+--+-- > import Test.Tasty (localOption)+-- > localOption (RelStDev 0.02) (bgroup [...])+--+-- If you set 'RelStDev' to infinity,+-- a benchmark will be executed+-- only once and its standard deviation will be recorded as zero.+-- This is rather a blunt approach, but it might be a necessary evil+-- for extremely long benchmarks. If you wish to run all benchmarks+-- only once, use command-line option @--stdev@ @Infinity@.+--+-- @since 0.2+newtype RelStDev = RelStDev Double+  deriving (Show, Read, Typeable)++-- | Whether to measure CPU time or wall-clock time.+-- Normally 'CpuTime' is a better option (and default),+-- but consider switching to 'WallTime'+-- to measure multithreaded algorithms or time spent in external processes.+--+-- One can switch the default measurement mode globally+-- using @--time-mode@ command-line option,+-- but it is usually better to adjust the mode locally:+--+-- > import Test.Tasty (localOption)+-- > localOption WallTime (bgroup [...])+--+-- @since 0.3.2+data TimeMode = CpuTime+  -- ^ Measure CPU time.+  deriving (Typeable)++-- | Something that can be benchmarked, produced by 'nf', 'whnf', 'nfIO', 'whnfIO',+-- 'nfAppIO', 'whnfAppIO' below.+--+-- Drop-in replacement for @Criterion.@'Criterion.Benchmarkable' and+-- @Gauge.@'Gauge.Benchmarkable'.+--+-- @since 0.1+newtype Benchmarkable =+    -- | @since 0.3+    Benchmarkable+  { unBenchmarkable :: Word64 -> IO () -- ^ Run benchmark given number of times.+  } deriving (Typeable)+++data Measurement = Measurement+  { measTime   :: !Word64 -- ^ time in picoseconds+  , measAllocs :: !Word64 -- ^ allocations in bytes+  , measCopied :: !Word64 -- ^ copied bytes+  , measMaxMem :: !Word64 -- ^ max memory in use+  } deriving (Show, Read)++data Estimate = Estimate+  { estMean  :: !Measurement+  , estStdev :: !Word64  -- ^ stdev in picoseconds+  } deriving (Show, Read)+++predict+  :: Measurement -- ^ time for one run+  -> Measurement -- ^ time for two runs+  -> Estimate+predict (Measurement t1 a1 c1 m1) (Measurement t2 a2 c2 m2) = Estimate+  { estMean  = Measurement t (fit a1 a2) (fit c1 c2) (max m1 m2)+  , estStdev = truncate (sqrt d :: Double)+  }+  where+    fit x1 x2 = x1 `quot` 5 + 2 * (x2 `quot` 5)+    t = fit t1 t2+    sqr x = x * x+    d = sqr (word64ToDouble t1 -     word64ToDouble t)+      + sqr (word64ToDouble t2 - 2 * word64ToDouble t)++predictPerturbed :: Measurement -> Measurement -> Estimate+predictPerturbed t1 t2 = Estimate+  { estMean = estMean (predict t1 t2)+  , estStdev = max+    (estStdev (predict (lo t1) (hi t2)))+    (estStdev (predict (hi t1) (lo t2)))+  }+  where+    prec = max (fromInteger cpuTimePrecision) 1000000000 -- 1 ms+    hi meas = meas { measTime = measTime meas + prec }+    lo meas = meas { measTime = measTime meas - prec }++hasGCStats :: Bool+#if MIN_VERSION_base(4,10,0)+hasGCStats = unsafePerformIO getRTSStatsEnabled+#elif MIN_VERSION_base(4,6,0)+hasGCStats = unsafePerformIO getGCStatsEnabled+#else+hasGCStats = False+#endif++getAllocsAndCopied :: IO (Word64, Word64, Word64)+getAllocsAndCopied = do+  if not hasGCStats then pure (0, 0, 0) else+#if MIN_VERSION_base(4,10,0)+    (\s -> (allocated_bytes s, copied_bytes s, max_mem_in_use_bytes s)) <$> getRTSStats+#elif MIN_VERSION_base(4,6,0)+    (\s -> (int64ToWord64 $ bytesAllocated s, int64ToWord64 $ bytesCopied s, int64ToWord64 $ peakMegabytesAllocated s * 1024 * 1024)) <$> getGCStats+#else+    pure (0, 0, 0)+#endif++getTimePicoSecs :: TimeMode -> IO Word64+getTimePicoSecs timeMode = case timeMode of+  CpuTime -> fromInteger <$> getCPUTime++measure :: TimeMode -> Word64 -> Benchmarkable -> IO Measurement+measure timeMode n (Benchmarkable act) = do+  let getTimePicoSecs' = getTimePicoSecs timeMode+  performGC+  startTime <- getTimePicoSecs'+  (startAllocs, startCopied, startMaxMemInUse) <- getAllocsAndCopied+  act n+  endTime <- getTimePicoSecs'+  (endAllocs, endCopied, endMaxMemInUse) <- getAllocsAndCopied+  let meas = Measurement+        { measTime   = endTime - startTime+        , measAllocs = endAllocs - startAllocs+        , measCopied = endCopied - startCopied+        , measMaxMem = max endMaxMemInUse startMaxMemInUse+        }+  pure meas++measureUntil :: TimeMode -> Bool -> Timeout -> RelStDev -> Benchmarkable -> IO Estimate+measureUntil timeMode _ _ (RelStDev targetRelStDev) b+  | isInfinite targetRelStDev, targetRelStDev > 0 = do+  t1 <- measure timeMode 1 b+  pure $ Estimate { estMean = t1, estStdev = 0 }+measureUntil timeMode warnIfNoTimeout timeout (RelStDev targetRelStDev) b = do+  t1 <- measure' 1 b+  go 1 t1 0+  where+    measure' = measure timeMode++    go :: Word64 -> Measurement -> Word64 -> IO Estimate+    go n t1 sumOfTs = do+      t2 <- measure' (2 * n) b++      let Estimate (Measurement meanN allocN copiedN maxMemN) stdevN = predictPerturbed t1 t2+          isTimeoutSoon = case timeout of+            NoTimeout -> False+            -- multiplying by 12/10 helps to avoid accidental timeouts+            Timeout micros _ -> (sumOfTs' + 3 * measTime t2) `quot` (1000000 * 10 `quot` 12) >= fromInteger micros+          isStDevInTargetRange = stdevN < truncate (max 0 targetRelStDev * word64ToDouble meanN)+          scale = (`quot` n)+          sumOfTs' = sumOfTs + measTime t1++      case timeout of+        NoTimeout | warnIfNoTimeout, sumOfTs' + measTime t2 > 100 * 1000000000000+          -> hPutStrLn stderr "This benchmark takes more than 100 seconds. Consider setting --timeout, if this is unexpected (or to silence this warning)."+        _ -> pure ()++      if isStDevInTargetRange || isTimeoutSoon+        then pure $ Estimate+          { estMean  = Measurement (scale meanN) (scale allocN) (scale copiedN) maxMemN+          , estStdev = scale stdevN }+        else go (2 * n) t2 sumOfTs'++-- | An internal routine to measure CPU execution time in seconds+-- for a given timeout (put 'NoTimeout', or 'mkTimeout' 100000000 for 100 seconds)+-- and a target relative standard deviation+-- (put 'RelStDev' 0.05 for 5% or 'RelStDev' (1/0) to run only one iteration).+--+-- 'Timeout' takes soft priority over 'RelStDev': this function prefers+-- to finish in time even if at cost of precision. However, timeout is guidance+-- not guarantee: 'measureCpuTime' can take longer, if there is not enough time+-- to run at least thrice or an iteration takes unusually long.+--+-- @since 0.3+measureCpuTime :: Timeout -> RelStDev -> Benchmarkable -> IO Double+measureCpuTime+    = ((fmap ((/ 1e12) . word64ToDouble . measTime . estMean) .) .)+    . measureUntil CpuTime False++++funcToBench :: (b -> c) -> (a -> b) -> a -> Benchmarkable+funcToBench frc = (Benchmarkable .) . benchLoop+  where+    -- Here we rely on the fact that GHC (unless spurred by+    -- -fstatic-argument-transformation) is not smart enough:+    -- it does not notice that `f` and `x` arguments are loop invariant+    -- and could be floated, and the whole `f x` expression shared.+    -- If we create a closure with `f` and `x` bound in the environment,+    -- then GHC is smart enough to share computation of `f x`.+    --+    -- For perspective, gauge and criterion < 1.4 mark similar functions as INLINE,+    -- while criterion >= 1.4 switches to NOINLINE.+    -- If we mark `benchLoop` NOINLINE then benchmark results are slightly larger+    -- (noticeable in bench-fibo), because the loop body is slightly bigger,+    -- since GHC does not unbox numbers or inline `Eq @Word64` dictionary.+    --+    -- This function is called `benchLoop` instead of, say, `go`,+    -- so it is easier to spot in Core dumps.+    benchLoop f x n+      | n == 0    = pure ()+      | otherwise = do+        _ <- evaluate (frc (f x))+        benchLoop f x (n - 1)+{-# INLINE funcToBench #-}++-- | 'nf' @f@ @x@ measures time to compute+-- a normal form (by means of 'force') of an application of @f@ to @x@.+-- This does not include time to evaluate @f@ or @x@ themselves.+-- Ideally @x@ should be a primitive data type like 'Data.Int.Int'.+--+-- The same thunk of @x@ is shared by multiple calls of @f@. We cannot evaluate+-- @x@ beforehand: there is no 'NFData' @a@ constraint, and potentially @x@ may+-- be an infinite structure. Thus @x@ will be evaluated in course of the first+-- application of @f@. This noisy measurement is to be discarded soon,+-- but if @x@ is not a primitive data type, consider forcing its evaluation+-- separately, e. g., via 'env' or 'withResource'.+--+-- Here is a textbook anti-pattern: 'nf' 'sum' @[1..1000000]@.+-- Since an input list is shared by multiple invocations of 'sum',+-- it will be allocated in memory in full, putting immense pressure+-- on garbage collector. Also no list fusion will happen.+-- A better approach is 'nf' (@\\n@ @->@ 'sum' @[1..n]@) @1000000@.+--+-- If you are measuring an inlinable function,+-- it is prudent to ensure that its invocation is fully saturated,+-- otherwise inlining will not happen. That's why one can often+-- see 'nf' (@\\n@ @->@ @f@ @n@) @x@ instead of 'nf' @f@ @x@.+-- Same applies to rewrite rules.+--+-- While @tasty-bench@ is capable to perform micro- and even nanobenchmarks,+-- such measurements are noisy and involve an overhead. Results are more reliable+-- when @f@ @x@ takes at least several milliseconds.+--+-- Note that forcing a normal form requires an additional+-- traverse of the structure. In certain scenarios (imagine benchmarking 'tail'),+-- especially when 'NFData' instance is badly written,+-- this traversal may take non-negligible time and affect results.+--+-- Drop-in replacement for @Criterion.@'Criterion.nf' and+-- @Gauge.@'Gauge.nf'.+--+-- @since 0.1+nf :: NFData b => (a -> b) -> a -> Benchmarkable+nf = funcToBench force+{-# INLINE nf #-}++-- | 'whnf' @f@ @x@ measures time to compute+-- a weak head normal form of an application of @f@ to @x@.+-- This does not include time to evaluate @f@ or @x@ themselves.+-- Ideally @x@ should be a primitive data type like 'Data.Int.Int'.+--+-- The same thunk of @x@ is shared by multiple calls of @f@. We cannot evaluate+-- @x@ beforehand: there is no 'NFData' @a@ constraint, and potentially @x@ may+-- be an infinite structure. Thus @x@ will be evaluated in course of the first+-- application of @f@. This noisy measurement is to be discarded soon,+-- but if @x@ is not a primitive data type, consider forcing its evaluation+-- separately, e. g., via 'env' or 'withResource'.+--+-- Computing only a weak head normal form is+-- rarely what intuitively is meant by "evaluation".+-- Beware that many educational materials contain examples with 'whnf':+-- this is a wrong default.+-- Unless you understand precisely, what is measured,+-- it is recommended to use 'nf' instead.+--+-- Here is a textbook anti-pattern: 'whnf' ('Data.List.replicate' @1000000@) @1@.+-- This will succeed in a matter of nanoseconds, because weak head+-- normal form forces only the first element of the list.+--+-- Drop-in replacement for @Criterion.@'Criterion.whnf' and @Gauge.@'Gauge.whnf'.+--+-- @since 0.1+whnf :: (a -> b) -> a -> Benchmarkable+whnf = funcToBench id+{-# INLINE whnf #-}++ioToBench :: (b -> c) -> IO b -> Benchmarkable+ioToBench frc act = Benchmarkable go+  where+    go n+      | n == 0    = pure ()+      | otherwise = do+        val <- act+        _ <- evaluate (frc val)+        go (n - 1)+{-# INLINE ioToBench #-}++-- | 'nfIO' @x@ measures time to evaluate side-effects of @x@+-- and compute its normal form (by means of 'force').+--+-- Pure subexpression of an effectful computation @x@+-- may be evaluated only once and get cached.+-- To avoid surprising results it is usually preferable+-- to use 'nfAppIO' instead.+--+-- Note that forcing a normal form requires an additional+-- traverse of the structure. In certain scenarios,+-- especially when 'NFData' instance is badly written,+-- this traversal may take non-negligible time and affect results.+--+-- A typical use case is 'nfIO' ('readFile' @"foo.txt"@).+-- However, if your goal is not to benchmark I\/O per se,+-- but just read input data from a file, it is cleaner to+-- use 'env' or 'withResource'.+--+-- Drop-in replacement for @Criterion.@'Criterion.nfIO' and @Gauge.@'Gauge.nfIO'.+--+-- @since 0.1+nfIO :: NFData a => IO a -> Benchmarkable+nfIO = ioToBench force+{-# INLINE nfIO #-}++-- | 'whnfIO' @x@ measures time to evaluate side-effects of @x@+-- and compute its weak head normal form.+--+-- Pure subexpression of an effectful computation @x@+-- may be evaluated only once and get cached.+-- To avoid surprising results it is usually preferable+-- to use 'whnfAppIO' instead.+--+-- Computing only a weak head normal form is+-- rarely what intuitively is meant by "evaluation".+-- Unless you understand precisely, what is measured,+-- it is recommended to use 'nfIO' instead.+--+-- Lazy I\/O is treacherous.+-- If your goal is not to benchmark I\/O per se,+-- but just read input data from a file, it is cleaner to+-- use 'env' or 'withResource'.+--+-- Drop-in replacement for @Criterion.@'Criterion.whnfIO' and @Gauge.@'Gauge.whnfIO'.+--+-- @since 0.1+whnfIO :: IO a -> Benchmarkable+whnfIO = ioToBench id+{-# INLINE whnfIO #-}++ioFuncToBench :: (b -> c) -> (a -> IO b) -> a -> Benchmarkable+ioFuncToBench frc = (Benchmarkable .) . go+  where+    go f x n+      | n == 0    = pure ()+      | otherwise = do+        val <- f x+        _ <- evaluate (frc val)+        go f x (n - 1)+{-# INLINE ioFuncToBench #-}++-- | 'nfAppIO' @f@ @x@ measures time to evaluate side-effects of+-- an application of @f@ to @x@.+-- and compute its normal form (by means of 'force').+-- This does not include time to evaluate @f@ or @x@ themselves.+-- Ideally @x@ should be a primitive data type like 'Data.Int.Int'.+--+-- The same thunk of @x@ is shared by multiple calls of @f@. We cannot evaluate+-- @x@ beforehand: there is no 'NFData' @a@ constraint, and potentially @x@ may+-- be an infinite structure. Thus @x@ will be evaluated in course of the first+-- application of @f@. This noisy measurement is to be discarded soon,+-- but if @x@ is not a primitive data type, consider forcing its evaluation+-- separately, e. g., via 'env' or 'withResource'.+--+-- Note that forcing a normal form requires an additional+-- traverse of the structure. In certain scenarios,+-- especially when 'NFData' instance is badly written,+-- this traversal may take non-negligible time and affect results.+--+-- A typical use case is 'nfAppIO' 'readFile' @"foo.txt"@.+-- However, if your goal is not to benchmark I\/O per se,+-- but just read input data from a file, it is cleaner to+-- use 'env' or 'withResource'.+--+-- Drop-in replacement for @Criterion.@'Criterion.nfAppIO' and @Gauge.@'Gauge.nfAppIO'.+--+-- @since 0.1+nfAppIO :: NFData b => (a -> IO b) -> a -> Benchmarkable+nfAppIO = ioFuncToBench force+{-# INLINE nfAppIO #-}++-- | 'whnfAppIO' @f@ @x@ measures time to evaluate side-effects of+-- an application of @f@ to @x@.+-- and compute its weak head normal form.+-- This does not include time to evaluate @f@ or @x@ themselves.+-- Ideally @x@ should be a primitive data type like 'Data.Int.Int'.+--+-- The same thunk of @x@ is shared by multiple calls of @f@. We cannot evaluate+-- @x@ beforehand: there is no 'NFData' @a@ constraint, and potentially @x@ may+-- be an infinite structure. Thus @x@ will be evaluated in course of the first+-- application of @f@. This noisy measurement is to be discarded soon,+-- but if @x@ is not a primitive data type, consider forcing its evaluation+-- separately, e. g., via 'env' or 'withResource'.+--+-- Computing only a weak head normal form is+-- rarely what intuitively is meant by "evaluation".+-- Unless you understand precisely, what is measured,+-- it is recommended to use 'nfAppIO' instead.+--+-- Lazy I\/O is treacherous.+-- If your goal is not to benchmark I\/O per se,+-- but just read input data from a file, it is cleaner to+-- use 'env' or 'withResource'.+--+-- Drop-in replacement for @Criterion.@'Criterion.whnfAppIO' and @Gauge.@'Gauge.whnfAppIO'.+--+-- @since 0.1+whnfAppIO :: (a -> IO b) -> a -> Benchmarkable+whnfAppIO = ioFuncToBench id+{-# INLINE whnfAppIO #-}+++word64ToDouble :: Word64 -> Double+word64ToDouble = fromIntegral++#if !MIN_VERSION_base(4,10,0) && MIN_VERSION_base(4,6,0)+int64ToWord64 :: Int64 -> Word64+int64ToWord64 = fromIntegral+#endif++prettyEstimate :: Estimate -> String+prettyEstimate (Estimate m stdev) =+  showPicos4 (measTime m)+  ++ (if stdev == 0 then "         " else " ± " ++ showPicos3 (2 * stdev))++-- | Show picoseconds, fitting number in 4 characters.+showPicos4 :: Word64 -> String+showPicos4 i+  | t < 995   = printf "%3.0f  ps"  t+  | t < 995e1 = printf "%4.2f ns"  (t / 1e3)+  | t < 995e2 = printf "%4.1f ns"  (t / 1e3)+  | t < 995e3 = printf "%3.0f  ns" (t / 1e3)+  | t < 995e4 = printf "%4.2f μs"  (t / 1e6)+  | t < 995e5 = printf "%4.1f μs"  (t / 1e6)+  | t < 995e6 = printf "%3.0f  μs" (t / 1e6)+  | t < 995e7 = printf "%4.2f ms"  (t / 1e9)+  | t < 995e8 = printf "%4.1f ms"  (t / 1e9)+  | t < 995e9 = printf "%3.0f  ms" (t / 1e9)+  | otherwise = printf "%4.3f s"   (t / 1e12)+  where+    t = word64ToDouble i++-- | Show picoseconds, fitting number in 3 characters.+showPicos3 :: Word64 -> String+showPicos3 i+  | t < 995   = printf "%3.0f ps" t+  | t < 995e1 = printf "%3.1f ns" (t / 1e3)+  | t < 995e3 = printf "%3.0f ns" (t / 1e3)+  | t < 995e4 = printf "%3.1f μs" (t / 1e6)+  | t < 995e6 = printf "%3.0f μs" (t / 1e6)+  | t < 995e7 = printf "%3.1f ms" (t / 1e9)+  | t < 995e9 = printf "%3.0f ms" (t / 1e9)+  | otherwise = printf "%4.2f s"  (t / 1e12)+  where+    t = word64ToDouble i++prettyEstimateWithGC :: Estimate -> String+prettyEstimateWithGC (Estimate m stdev) =+  showPicos4 (measTime m)+  ++ (if stdev == 0 then ",          " else " ± " ++ showPicos3 (2 * stdev) ++ ", ")+  ++ showBytes (measAllocs m) ++ " allocated, "+  ++ showBytes (measCopied m) ++ " copied, "+  ++ showBytes (measMaxMem m) ++ " peak memory"++csvEstimate :: Estimate -> String+csvEstimate (Estimate m stdev) = show (measTime m) ++ "," ++ show (2 * stdev)++csvEstimateWithGC :: Estimate -> String+csvEstimateWithGC (Estimate m stdev) = show (measTime m) ++ "," ++ show (2 * stdev)+  ++ "," ++ show (measAllocs m) ++ "," ++ show (measCopied m) ++ "," ++ show (measMaxMem m)++showBytes :: Word64 -> String+showBytes i+  | t < 1000                 = printf "%3.0f B " t+  | t < 10189                = printf "%3.1f KB" (t / 1024)+  | t < 1023488              = printf "%3.0f KB" (t / 1024)+  | t < 10433332             = printf "%3.1f MB" (t / 1048576)+  | t < 1048051712           = printf "%3.0f MB" (t / 1048576)+  | t < 10683731149          = printf "%3.1f GB" (t / 1073741824)+  | t < 1073204953088        = printf "%3.0f GB" (t / 1073741824)+  | t < 10940140696372       = printf "%3.1f TB" (t / 1099511627776)+  | t < 1098961871962112     = printf "%3.0f TB" (t / 1099511627776)+  | t < 11202704073084108    = printf "%3.1f PB" (t / 1125899906842624)+  | t < 1125336956889202624  = printf "%3.0f PB" (t / 1125899906842624)+  | t < 11471568970838126592 = printf "%3.1f EB" (t / 1152921504606846976)+  | otherwise                = printf "%3.0f EB" (t / 1152921504606846976)+  where+    t = word64ToDouble i
changelog.md view
@@ -2,6 +2,11 @@  _Note: below all `FilePath` values are unquoted, so `\\` really means two backslashes._ +## 1.4.100.0 *July 2022*++Implementation of the [Abstract FilePath Proposal](https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path)+in user-space as a separate type.+ ## 1.4.2.2 *Dec 2021*  This release is purely a documentation release, fixing the broken haddock links.
filepath.cabal view
@@ -1,68 +1,203 @@-cabal-version:  1.18-name:           filepath-version:        1.4.2.2+cabal-version:      2.2+name:               filepath+version:            1.4.100.0+ -- NOTE: Don't forget to update ./changelog.md-license:        BSD3-license-file:   LICENSE-author:         Neil Mitchell <ndmitchell@gmail.com>-maintainer:     Julian Ospald <hasufell@posteo.de>-copyright:      Neil Mitchell 2005-2020-bug-reports:    https://github.com/haskell/filepath/issues-homepage:       https://github.com/haskell/filepath#readme-category:       System-build-type:     Simple-synopsis:       Library for manipulating FilePaths in a cross platform way.-tested-with:    GHC==9.2.1, GHC==9.0.1, GHC==8.10.7, GHC==8.8.4, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2+license:            BSD-3-Clause+license-file:       LICENSE+author:             Neil Mitchell <ndmitchell@gmail.com>+maintainer:         Julian Ospald <hasufell@posteo.de>+copyright:          Neil Mitchell 2005-2020, Julain Ospald 2021-2022+bug-reports:        https://gitlab.haskell.org/haskell/filepath/-/issues+homepage:+  https://gitlab.haskell.org/haskell/filepath/-/blob/master/README.md++category:           System+build-type:         Simple+synopsis:           Library for manipulating FilePaths in a cross platform way.+tested-with:+  GHC ==8.0.2+   || ==8.2.2+   || ==8.4.4+   || ==8.6.5+   || ==8.8.4+   || ==8.10.7+   || ==9.0.2+   || ==9.2.3+ description:-    This package provides functionality for manipulating @FilePath@ values, and is shipped with both <https://www.haskell.org/ghc/ GHC> and the <https://www.haskell.org/platform/ Haskell Platform>. It provides three modules:-    .-    * "System.FilePath.Posix" manipulates POSIX\/Linux style @FilePath@ values (with @\/@ as the path separator).-    .-    * "System.FilePath.Windows" manipulates Windows style @FilePath@ values (with either @\\@ or @\/@ as the path separator, and deals with drives).-    .-    * "System.FilePath" is an alias for the module appropriate to your platform.-    .-    All three modules provide the same API, and the same documentation (calling out differences in the different variants).+  This package provides functionality for manipulating @FilePath@ values, and is shipped with <https://www.haskell.org/ghc/ GHC>. It provides two variants for filepaths:+  .+  1. legacy filepaths: @type FilePath = String@+  .+  2. operating system abstracted filepaths (@OsPath@): internally unpinned @ShortByteString@ (platform-dependent encoding)+  .+  It is recommended to use @OsPath@ when possible, because it is more correct.+  .+  For each variant there are three main modules:+  .+  * "System.FilePath.Posix" / "System.OsPath.Posix" manipulates POSIX\/Linux style @FilePath@ values (with @\/@ as the path separator).+  .+  * "System.FilePath.Windows" / "System.OsPath.Windows" manipulates Windows style @FilePath@ values (with either @\\@ or @\/@ as the path separator, and deals with drives).+  .+  * "System.FilePath" / "System.OsPath" for dealing with current platform-specific filepaths+  .+  "System.OsString" is like "System.OsPath", but more general purpose. Refer to the documentation of+  those modules for more information.  extra-source-files:-    System/FilePath/Internal.hs-    Makefile+  Generate.hs+  Makefile+  System/FilePath/Internal.hs+  System/OsPath/Common.hs+  System/OsString/Common.hs+  tests/bytestring-tests/Properties/Common.hs+ extra-doc-files:-    README.md-    HACKING.md-    changelog.md+  changelog.md+  HACKING.md+  README.md +flag cpphs+  description: Use cpphs (fixes haddock source links)+  default:     False+  manual:      True+ source-repository head-    type:     git-    location: https://github.com/haskell/filepath.git+  type:     git+  location: https://gitlab.haskell.org/haskell/filepath  library-    default-language: Haskell2010-    other-extensions:-        CPP-        PatternGuards-    if impl(GHC >= 7.2)-        other-extensions: Safe+  exposed-modules:+    System.FilePath+    System.FilePath.Posix+    System.FilePath.Windows+    System.OsPath+    System.OsPath.Data.ByteString.Short+    System.OsPath.Data.ByteString.Short.Internal+    System.OsPath.Data.ByteString.Short.Word16+    System.OsPath.Encoding+    System.OsPath.Encoding.Internal+    System.OsPath.Internal+    System.OsPath.Posix+    System.OsPath.Posix.Internal+    System.OsPath.Types+    System.OsPath.Windows+    System.OsPath.Windows.Internal+    System.OsString+    System.OsString.Internal+    System.OsString.Internal.Types+    System.OsString.Posix+    System.OsString.Windows -    exposed-modules:-        System.FilePath-        System.FilePath.Posix-        System.FilePath.Windows+  other-extensions:+    CPP+    PatternGuards -    build-depends:-        base >= 4.9 && < 4.17+  if impl(ghc >=7.2)+    other-extensions: Safe -    ghc-options: -Wall+  default-language: Haskell2010+  build-depends:+    , base              >=4.9      && <4.18+    , bytestring        >=0.11.3.0+    , deepseq+    , exceptions+    , template-haskell +  ghc-options:      -Wall++  if flag(cpphs)+    ghc-options:        -pgmPcpphs -optP--cpp+    build-tool-depends: cpphs:cpphs -any+ test-suite filepath-tests-    type: exitcode-stdio-1.0-    default-language: Haskell2010-    main-is: Test.hs-    hs-source-dirs: tests-    other-modules:-        TestGen-        TestUtil-    build-depends:-        filepath,-        base,-        QuickCheck >= 2.7 && < 2.15+  type:             exitcode-stdio-1.0+  main-is:          Test.hs+  hs-source-dirs:   tests tests/filepath-tests+  other-modules:+    TestGen+    TestUtil++  build-depends:+    , base+    , bytestring  >=0.11.3.0+    , filepath+    , QuickCheck  >=2.7      && <2.15++  default-language: Haskell2010+  ghc-options:      -Wall++test-suite filepath-equivalent-tests+  default-language: Haskell2010+  ghc-options:      -Wall+  type:             exitcode-stdio-1.0+  main-is:          TestEquiv.hs+  hs-source-dirs:   tests tests/filepath-equivalent-tests+  other-modules:+    Legacy.System.FilePath+    Legacy.System.FilePath.Posix+    Legacy.System.FilePath.Windows+    TestUtil++  build-depends:+    , base+    , bytestring  >=0.11.3.0+    , filepath+    , QuickCheck  >=2.7      && <2.15++test-suite bytestring-tests+  default-language: Haskell2010+  ghc-options:      -Wall+  type:             exitcode-stdio-1.0+  main-is:          Main.hs+  hs-source-dirs:   tests tests/bytestring-tests+  other-modules:+    Properties.ShortByteString+    Properties.ShortByteString.Word16+    TestUtil++  build-depends:+    , base+    , bytestring  >=0.11.3.0+    , filepath+    , QuickCheck  >=2.7      && <2.15++test-suite abstract-filepath+  default-language: Haskell2010+  ghc-options:      -Wall+  type:             exitcode-stdio-1.0+  main-is:          Test.hs+  hs-source-dirs:   tests tests/abstract-filepath+  other-modules:+    Arbitrary+    EncodingSpec+    OsPathSpec+    TestUtil++  build-depends:+    , base+    , bytestring  >=0.11.3.0+    , checkers    ^>=0.5.6+    , deepseq+    , filepath+    , QuickCheck  >=2.7      && <2.15++benchmark bench-filepath+  default-language: Haskell2010+  ghc-options:      -Wall+  type:             exitcode-stdio-1.0+  main-is:          BenchFilePath.hs+  hs-source-dirs:   bench+  other-modules:    TastyBench+  build-depends:+    , base+    , bytestring  >=0.11.3.0+    , deepseq+    , filepath++  if impl(ghc >=8.10)+    ghc-options: "-with-rtsopts=-A32m --nonmoving-gc"++  else+    ghc-options: -with-rtsopts=-A32m
− tests/Test.hs
@@ -1,30 +0,0 @@--module Main where--import System.Environment-import TestGen-import Control.Monad-import Data.Maybe-import Test.QuickCheck---main :: IO ()-main = do-    args <- getArgs-    let count = case args of i:_ -> read i; _ -> 10000-    putStrLn $ "Testing with " ++ show count ++ " repetitions"-    let total = length tests-    let showOutput x = show x{output=""} ++ "\n" ++ output x-    bad <- fmap catMaybes $ forM (zip [1..] tests) $ \(i,(msg,prop)) -> do-        putStrLn $ "Test " ++ show i ++ " of " ++ show total ++ ": " ++ msg-        res <- quickCheckWithResult stdArgs{chatty=False, maxSuccess=count} prop-        case res of-            Success{} -> pure Nothing-            bad -> do putStrLn $ showOutput bad; putStrLn "TEST FAILURE!"; pure $ Just (msg,bad)-    if null bad then-        putStrLn $ "Success, " ++ show total ++ " tests passed"-     else do-        putStrLn $ show (length bad) ++ " FAILURES\n"-        forM_ (zip [1..] bad) $ \(i,(a,b)) ->-            putStrLn $ "FAILURE " ++ show i ++ ": " ++ a ++ "\n" ++ showOutput b ++ "\n"-        fail $ "FAILURE, failed " ++ show (length bad) ++ " of " ++ show total ++ " tests"
− tests/TestGen.hs
@@ -1,466 +0,0 @@--- GENERATED CODE: See ../Generate.hs-module TestGen(tests) where-import TestUtil-import qualified System.FilePath.Windows as W-import qualified System.FilePath.Posix as P-{-# ANN module "HLint: ignore" #-}-tests :: [(String, Property)]-tests =-    [("W.pathSeparator == '\\\\'", property $ W.pathSeparator == '\\')-    ,("P.pathSeparator == '/'", property $ P.pathSeparator == '/')-    ,("P.isPathSeparator P.pathSeparator", property $ P.isPathSeparator P.pathSeparator)-    ,("W.isPathSeparator W.pathSeparator", property $ W.isPathSeparator W.pathSeparator)-    ,("W.pathSeparators == ['\\\\', '/']", property $ W.pathSeparators == ['\\', '/'])-    ,("P.pathSeparators == ['/']", property $ P.pathSeparators == ['/'])-    ,("P.pathSeparator `elem` P.pathSeparators", property $ P.pathSeparator `elem` P.pathSeparators)-    ,("W.pathSeparator `elem` W.pathSeparators", property $ W.pathSeparator `elem` W.pathSeparators)-    ,("P.isPathSeparator a == (a `elem` P.pathSeparators)", property $ \a -> P.isPathSeparator a == (a `elem` P.pathSeparators))-    ,("W.isPathSeparator a == (a `elem` W.pathSeparators)", property $ \a -> W.isPathSeparator a == (a `elem` W.pathSeparators))-    ,("W.searchPathSeparator == ';'", property $ W.searchPathSeparator == ';')-    ,("P.searchPathSeparator == ':'", property $ P.searchPathSeparator == ':')-    ,("P.isSearchPathSeparator a == (a == P.searchPathSeparator)", property $ \a -> P.isSearchPathSeparator a == (a == P.searchPathSeparator))-    ,("W.isSearchPathSeparator a == (a == W.searchPathSeparator)", property $ \a -> W.isSearchPathSeparator a == (a == W.searchPathSeparator))-    ,("P.extSeparator == '.'", property $ P.extSeparator == '.')-    ,("W.extSeparator == '.'", property $ W.extSeparator == '.')-    ,("P.isExtSeparator a == (a == P.extSeparator)", property $ \a -> P.isExtSeparator a == (a == P.extSeparator))-    ,("W.isExtSeparator a == (a == W.extSeparator)", property $ \a -> W.isExtSeparator a == (a == W.extSeparator))-    ,("P.splitSearchPath \"File1:File2:File3\" == [\"File1\", \"File2\", \"File3\"]", property $ P.splitSearchPath "File1:File2:File3" == ["File1", "File2", "File3"])-    ,("P.splitSearchPath \"File1::File2:File3\" == [\"File1\", \".\", \"File2\", \"File3\"]", property $ P.splitSearchPath "File1::File2:File3" == ["File1", ".", "File2", "File3"])-    ,("W.splitSearchPath \"File1;File2;File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;File2;File3" == ["File1", "File2", "File3"])-    ,("W.splitSearchPath \"File1;;File2;File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;;File2;File3" == ["File1", "File2", "File3"])-    ,("W.splitSearchPath \"File1;\\\"File2\\\";File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;\"File2\";File3" == ["File1", "File2", "File3"])-    ,("P.splitExtension \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ P.splitExtension "/directory/path.ext" == ("/directory/path", ".ext"))-    ,("W.splitExtension \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ W.splitExtension "/directory/path.ext" == ("/directory/path", ".ext"))-    ,("uncurry (++) (P.splitExtension x) == x", property $ \(QFilePath x) -> uncurry (++) (P.splitExtension x) == x)-    ,("uncurry (++) (W.splitExtension x) == x", property $ \(QFilePath x) -> uncurry (++) (W.splitExtension x) == x)-    ,("uncurry P.addExtension (P.splitExtension x) == x", property $ \(QFilePathValidP x) -> uncurry P.addExtension (P.splitExtension x) == x)-    ,("uncurry W.addExtension (W.splitExtension x) == x", property $ \(QFilePathValidW x) -> uncurry W.addExtension (W.splitExtension x) == x)-    ,("P.splitExtension \"file.txt\" == (\"file\", \".txt\")", property $ P.splitExtension "file.txt" == ("file", ".txt"))-    ,("W.splitExtension \"file.txt\" == (\"file\", \".txt\")", property $ W.splitExtension "file.txt" == ("file", ".txt"))-    ,("P.splitExtension \"file\" == (\"file\", \"\")", property $ P.splitExtension "file" == ("file", ""))-    ,("W.splitExtension \"file\" == (\"file\", \"\")", property $ W.splitExtension "file" == ("file", ""))-    ,("P.splitExtension \"file/file.txt\" == (\"file/file\", \".txt\")", property $ P.splitExtension "file/file.txt" == ("file/file", ".txt"))-    ,("W.splitExtension \"file/file.txt\" == (\"file/file\", \".txt\")", property $ W.splitExtension "file/file.txt" == ("file/file", ".txt"))-    ,("P.splitExtension \"file.txt/boris\" == (\"file.txt/boris\", \"\")", property $ P.splitExtension "file.txt/boris" == ("file.txt/boris", ""))-    ,("W.splitExtension \"file.txt/boris\" == (\"file.txt/boris\", \"\")", property $ W.splitExtension "file.txt/boris" == ("file.txt/boris", ""))-    ,("P.splitExtension \"file.txt/boris.ext\" == (\"file.txt/boris\", \".ext\")", property $ P.splitExtension "file.txt/boris.ext" == ("file.txt/boris", ".ext"))-    ,("W.splitExtension \"file.txt/boris.ext\" == (\"file.txt/boris\", \".ext\")", property $ W.splitExtension "file.txt/boris.ext" == ("file.txt/boris", ".ext"))-    ,("P.splitExtension \"file/path.txt.bob.fred\" == (\"file/path.txt.bob\", \".fred\")", property $ P.splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob", ".fred"))-    ,("W.splitExtension \"file/path.txt.bob.fred\" == (\"file/path.txt.bob\", \".fred\")", property $ W.splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob", ".fred"))-    ,("P.splitExtension \"file/path.txt/\" == (\"file/path.txt/\", \"\")", property $ P.splitExtension "file/path.txt/" == ("file/path.txt/", ""))-    ,("W.splitExtension \"file/path.txt/\" == (\"file/path.txt/\", \"\")", property $ W.splitExtension "file/path.txt/" == ("file/path.txt/", ""))-    ,("P.takeExtension \"/directory/path.ext\" == \".ext\"", property $ P.takeExtension "/directory/path.ext" == ".ext")-    ,("W.takeExtension \"/directory/path.ext\" == \".ext\"", property $ W.takeExtension "/directory/path.ext" == ".ext")-    ,("P.takeExtension x == snd (P.splitExtension x)", property $ \(QFilePath x) -> P.takeExtension x == snd (P.splitExtension x))-    ,("W.takeExtension x == snd (W.splitExtension x)", property $ \(QFilePath x) -> W.takeExtension x == snd (W.splitExtension x))-    ,("P.takeExtension (P.addExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeExtension (P.addExtension x "ext") == ".ext")-    ,("W.takeExtension (W.addExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeExtension (W.addExtension x "ext") == ".ext")-    ,("P.takeExtension (P.replaceExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeExtension (P.replaceExtension x "ext") == ".ext")-    ,("W.takeExtension (W.replaceExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeExtension (W.replaceExtension x "ext") == ".ext")-    ,("\"/directory/path.txt\" P.-<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" P.-<.> "ext" == "/directory/path.ext")-    ,("\"/directory/path.txt\" W.-<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" W.-<.> "ext" == "/directory/path.ext")-    ,("\"/directory/path.txt\" P.-<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" P.-<.> ".ext" == "/directory/path.ext")-    ,("\"/directory/path.txt\" W.-<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" W.-<.> ".ext" == "/directory/path.ext")-    ,("\"foo.o\" P.-<.> \"c\" == \"foo.c\"", property $ "foo.o" P.-<.> "c" == "foo.c")-    ,("\"foo.o\" W.-<.> \"c\" == \"foo.c\"", property $ "foo.o" W.-<.> "c" == "foo.c")-    ,("P.replaceExtension \"/directory/path.txt\" \"ext\" == \"/directory/path.ext\"", property $ P.replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext")-    ,("W.replaceExtension \"/directory/path.txt\" \"ext\" == \"/directory/path.ext\"", property $ W.replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext")-    ,("P.replaceExtension \"/directory/path.txt\" \".ext\" == \"/directory/path.ext\"", property $ P.replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext")-    ,("W.replaceExtension \"/directory/path.txt\" \".ext\" == \"/directory/path.ext\"", property $ W.replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext")-    ,("P.replaceExtension \"file.txt\" \".bob\" == \"file.bob\"", property $ P.replaceExtension "file.txt" ".bob" == "file.bob")-    ,("W.replaceExtension \"file.txt\" \".bob\" == \"file.bob\"", property $ W.replaceExtension "file.txt" ".bob" == "file.bob")-    ,("P.replaceExtension \"file.txt\" \"bob\" == \"file.bob\"", property $ P.replaceExtension "file.txt" "bob" == "file.bob")-    ,("W.replaceExtension \"file.txt\" \"bob\" == \"file.bob\"", property $ W.replaceExtension "file.txt" "bob" == "file.bob")-    ,("P.replaceExtension \"file\" \".bob\" == \"file.bob\"", property $ P.replaceExtension "file" ".bob" == "file.bob")-    ,("W.replaceExtension \"file\" \".bob\" == \"file.bob\"", property $ W.replaceExtension "file" ".bob" == "file.bob")-    ,("P.replaceExtension \"file.txt\" \"\" == \"file\"", property $ P.replaceExtension "file.txt" "" == "file")-    ,("W.replaceExtension \"file.txt\" \"\" == \"file\"", property $ W.replaceExtension "file.txt" "" == "file")-    ,("P.replaceExtension \"file.fred.bob\" \"txt\" == \"file.fred.txt\"", property $ P.replaceExtension "file.fred.bob" "txt" == "file.fred.txt")-    ,("W.replaceExtension \"file.fred.bob\" \"txt\" == \"file.fred.txt\"", property $ W.replaceExtension "file.fred.bob" "txt" == "file.fred.txt")-    ,("P.replaceExtension x y == P.addExtension (P.dropExtension x) y", property $ \(QFilePath x) (QFilePath y) -> P.replaceExtension x y == P.addExtension (P.dropExtension x) y)-    ,("W.replaceExtension x y == W.addExtension (W.dropExtension x) y", property $ \(QFilePath x) (QFilePath y) -> W.replaceExtension x y == W.addExtension (W.dropExtension x) y)-    ,("\"/directory/path\" P.<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path" P.<.> "ext" == "/directory/path.ext")-    ,("\"/directory/path\" W.<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path" W.<.> "ext" == "/directory/path.ext")-    ,("\"/directory/path\" P.<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path" P.<.> ".ext" == "/directory/path.ext")-    ,("\"/directory/path\" W.<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path" W.<.> ".ext" == "/directory/path.ext")-    ,("P.dropExtension \"/directory/path.ext\" == \"/directory/path\"", property $ P.dropExtension "/directory/path.ext" == "/directory/path")-    ,("W.dropExtension \"/directory/path.ext\" == \"/directory/path\"", property $ W.dropExtension "/directory/path.ext" == "/directory/path")-    ,("P.dropExtension x == fst (P.splitExtension x)", property $ \(QFilePath x) -> P.dropExtension x == fst (P.splitExtension x))-    ,("W.dropExtension x == fst (W.splitExtension x)", property $ \(QFilePath x) -> W.dropExtension x == fst (W.splitExtension x))-    ,("P.addExtension \"/directory/path\" \"ext\" == \"/directory/path.ext\"", property $ P.addExtension "/directory/path" "ext" == "/directory/path.ext")-    ,("W.addExtension \"/directory/path\" \"ext\" == \"/directory/path.ext\"", property $ W.addExtension "/directory/path" "ext" == "/directory/path.ext")-    ,("P.addExtension \"file.txt\" \"bib\" == \"file.txt.bib\"", property $ P.addExtension "file.txt" "bib" == "file.txt.bib")-    ,("W.addExtension \"file.txt\" \"bib\" == \"file.txt.bib\"", property $ W.addExtension "file.txt" "bib" == "file.txt.bib")-    ,("P.addExtension \"file.\" \".bib\" == \"file..bib\"", property $ P.addExtension "file." ".bib" == "file..bib")-    ,("W.addExtension \"file.\" \".bib\" == \"file..bib\"", property $ W.addExtension "file." ".bib" == "file..bib")-    ,("P.addExtension \"file\" \".bib\" == \"file.bib\"", property $ P.addExtension "file" ".bib" == "file.bib")-    ,("W.addExtension \"file\" \".bib\" == \"file.bib\"", property $ W.addExtension "file" ".bib" == "file.bib")-    ,("P.addExtension \"/\" \"x\" == \"/.x\"", property $ P.addExtension "/" "x" == "/.x")-    ,("W.addExtension \"/\" \"x\" == \"/.x\"", property $ W.addExtension "/" "x" == "/.x")-    ,("P.addExtension x \"\" == x", property $ \(QFilePath x) -> P.addExtension x "" == x)-    ,("W.addExtension x \"\" == x", property $ \(QFilePath x) -> W.addExtension x "" == x)-    ,("P.takeFileName (P.addExtension (P.addTrailingPathSeparator x) \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeFileName (P.addExtension (P.addTrailingPathSeparator x) "ext") == ".ext")-    ,("W.takeFileName (W.addExtension (W.addTrailingPathSeparator x) \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeFileName (W.addExtension (W.addTrailingPathSeparator x) "ext") == ".ext")-    ,("W.addExtension \"\\\\\\\\share\" \".txt\" == \"\\\\\\\\share\\\\.txt\"", property $ W.addExtension "\\\\share" ".txt" == "\\\\share\\.txt")-    ,("P.hasExtension \"/directory/path.ext\" == True", property $ P.hasExtension "/directory/path.ext" == True)-    ,("W.hasExtension \"/directory/path.ext\" == True", property $ W.hasExtension "/directory/path.ext" == True)-    ,("P.hasExtension \"/directory/path\" == False", property $ P.hasExtension "/directory/path" == False)-    ,("W.hasExtension \"/directory/path\" == False", property $ W.hasExtension "/directory/path" == False)-    ,("null (P.takeExtension x) == not (P.hasExtension x)", property $ \(QFilePath x) -> null (P.takeExtension x) == not (P.hasExtension x))-    ,("null (W.takeExtension x) == not (W.hasExtension x)", property $ \(QFilePath x) -> null (W.takeExtension x) == not (W.hasExtension x))-    ,("\"png\" `P.isExtensionOf` \"/directory/file.png\" == True", property $ "png" `P.isExtensionOf` "/directory/file.png" == True)-    ,("\"png\" `W.isExtensionOf` \"/directory/file.png\" == True", property $ "png" `W.isExtensionOf` "/directory/file.png" == True)-    ,("\".png\" `P.isExtensionOf` \"/directory/file.png\" == True", property $ ".png" `P.isExtensionOf` "/directory/file.png" == True)-    ,("\".png\" `W.isExtensionOf` \"/directory/file.png\" == True", property $ ".png" `W.isExtensionOf` "/directory/file.png" == True)-    ,("\".tar.gz\" `P.isExtensionOf` \"bar/foo.tar.gz\" == True", property $ ".tar.gz" `P.isExtensionOf` "bar/foo.tar.gz" == True)-    ,("\".tar.gz\" `W.isExtensionOf` \"bar/foo.tar.gz\" == True", property $ ".tar.gz" `W.isExtensionOf` "bar/foo.tar.gz" == True)-    ,("\"ar.gz\" `P.isExtensionOf` \"bar/foo.tar.gz\" == False", property $ "ar.gz" `P.isExtensionOf` "bar/foo.tar.gz" == False)-    ,("\"ar.gz\" `W.isExtensionOf` \"bar/foo.tar.gz\" == False", property $ "ar.gz" `W.isExtensionOf` "bar/foo.tar.gz" == False)-    ,("\"png\" `P.isExtensionOf` \"/directory/file.png.jpg\" == False", property $ "png" `P.isExtensionOf` "/directory/file.png.jpg" == False)-    ,("\"png\" `W.isExtensionOf` \"/directory/file.png.jpg\" == False", property $ "png" `W.isExtensionOf` "/directory/file.png.jpg" == False)-    ,("\"csv/table.csv\" `P.isExtensionOf` \"/data/csv/table.csv\" == False", property $ "csv/table.csv" `P.isExtensionOf` "/data/csv/table.csv" == False)-    ,("\"csv/table.csv\" `W.isExtensionOf` \"/data/csv/table.csv\" == False", property $ "csv/table.csv" `W.isExtensionOf` "/data/csv/table.csv" == False)-    ,("P.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", property $ P.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")-    ,("W.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", property $ W.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")-    ,("P.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", property $ P.stripExtension "hi.o" "foo.x.hs.o" == Nothing)-    ,("W.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", property $ W.stripExtension "hi.o" "foo.x.hs.o" == Nothing)-    ,("P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x)", property $ \(QFilePath x) -> P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x))-    ,("W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x)", property $ \(QFilePath x) -> W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x))-    ,("P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x)", property $ \(QFilePath x) -> P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x))-    ,("W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x)", property $ \(QFilePath x) -> W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x))-    ,("P.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", property $ P.stripExtension ".c.d" "a.b.c.d" == Just "a.b")-    ,("W.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", property $ W.stripExtension ".c.d" "a.b.c.d" == Just "a.b")-    ,("P.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", property $ P.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")-    ,("W.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", property $ W.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")-    ,("P.stripExtension \"baz\" \"foo.bar\" == Nothing", property $ P.stripExtension "baz" "foo.bar" == Nothing)-    ,("W.stripExtension \"baz\" \"foo.bar\" == Nothing", property $ W.stripExtension "baz" "foo.bar" == Nothing)-    ,("P.stripExtension \"bar\" \"foobar\" == Nothing", property $ P.stripExtension "bar" "foobar" == Nothing)-    ,("W.stripExtension \"bar\" \"foobar\" == Nothing", property $ W.stripExtension "bar" "foobar" == Nothing)-    ,("P.stripExtension \"\" x == Just x", property $ \(QFilePath x) -> P.stripExtension "" x == Just x)-    ,("W.stripExtension \"\" x == Just x", property $ \(QFilePath x) -> W.stripExtension "" x == Just x)-    ,("P.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ P.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))-    ,("W.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ W.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))-    ,("P.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ P.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))-    ,("W.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ W.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))-    ,("uncurry (++) (P.splitExtensions x) == x", property $ \(QFilePath x) -> uncurry (++) (P.splitExtensions x) == x)-    ,("uncurry (++) (W.splitExtensions x) == x", property $ \(QFilePath x) -> uncurry (++) (W.splitExtensions x) == x)-    ,("uncurry P.addExtension (P.splitExtensions x) == x", property $ \(QFilePathValidP x) -> uncurry P.addExtension (P.splitExtensions x) == x)-    ,("uncurry W.addExtension (W.splitExtensions x) == x", property $ \(QFilePathValidW x) -> uncurry W.addExtension (W.splitExtensions x) == x)-    ,("P.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ P.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))-    ,("W.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ W.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))-    ,("P.dropExtensions \"/directory/path.ext\" == \"/directory/path\"", property $ P.dropExtensions "/directory/path.ext" == "/directory/path")-    ,("W.dropExtensions \"/directory/path.ext\" == \"/directory/path\"", property $ W.dropExtensions "/directory/path.ext" == "/directory/path")-    ,("P.dropExtensions \"file.tar.gz\" == \"file\"", property $ P.dropExtensions "file.tar.gz" == "file")-    ,("W.dropExtensions \"file.tar.gz\" == \"file\"", property $ W.dropExtensions "file.tar.gz" == "file")-    ,("not $ P.hasExtension $ P.dropExtensions x", property $ \(QFilePath x) -> not $ P.hasExtension $ P.dropExtensions x)-    ,("not $ W.hasExtension $ W.dropExtensions x", property $ \(QFilePath x) -> not $ W.hasExtension $ W.dropExtensions x)-    ,("not $ any P.isExtSeparator $ P.takeFileName $ P.dropExtensions x", property $ \(QFilePath x) -> not $ any P.isExtSeparator $ P.takeFileName $ P.dropExtensions x)-    ,("not $ any W.isExtSeparator $ W.takeFileName $ W.dropExtensions x", property $ \(QFilePath x) -> not $ any W.isExtSeparator $ W.takeFileName $ W.dropExtensions x)-    ,("P.takeExtensions \"/directory/path.ext\" == \".ext\"", property $ P.takeExtensions "/directory/path.ext" == ".ext")-    ,("W.takeExtensions \"/directory/path.ext\" == \".ext\"", property $ W.takeExtensions "/directory/path.ext" == ".ext")-    ,("P.takeExtensions \"file.tar.gz\" == \".tar.gz\"", property $ P.takeExtensions "file.tar.gz" == ".tar.gz")-    ,("W.takeExtensions \"file.tar.gz\" == \".tar.gz\"", property $ W.takeExtensions "file.tar.gz" == ".tar.gz")-    ,("P.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", property $ P.replaceExtensions "file.fred.bob" "txt" == "file.txt")-    ,("W.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", property $ W.replaceExtensions "file.fred.bob" "txt" == "file.txt")-    ,("P.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", property $ P.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")-    ,("W.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", property $ W.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")-    ,("uncurry (++) (P.splitDrive x) == x", property $ \(QFilePath x) -> uncurry (++) (P.splitDrive x) == x)-    ,("uncurry (++) (W.splitDrive x) == x", property $ \(QFilePath x) -> uncurry (++) (W.splitDrive x) == x)-    ,("W.splitDrive \"file\" == (\"\", \"file\")", property $ W.splitDrive "file" == ("", "file"))-    ,("W.splitDrive \"c:/file\" == (\"c:/\", \"file\")", property $ W.splitDrive "c:/file" == ("c:/", "file"))-    ,("W.splitDrive \"c:\\\\file\" == (\"c:\\\\\", \"file\")", property $ W.splitDrive "c:\\file" == ("c:\\", "file"))-    ,("W.splitDrive \"\\\\\\\\shared\\\\test\" == (\"\\\\\\\\shared\\\\\", \"test\")", property $ W.splitDrive "\\\\shared\\test" == ("\\\\shared\\", "test"))-    ,("W.splitDrive \"\\\\\\\\shared\" == (\"\\\\\\\\shared\", \"\")", property $ W.splitDrive "\\\\shared" == ("\\\\shared", ""))-    ,("W.splitDrive \"\\\\\\\\?\\\\UNC\\\\shared\\\\file\" == (\"\\\\\\\\?\\\\UNC\\\\shared\\\\\", \"file\")", property $ W.splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\", "file"))-    ,("W.splitDrive \"\\\\\\\\?\\\\UNCshared\\\\file\" == (\"\\\\\\\\?\\\\\", \"UNCshared\\\\file\")", property $ W.splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\", "UNCshared\\file"))-    ,("W.splitDrive \"\\\\\\\\?\\\\d:\\\\file\" == (\"\\\\\\\\?\\\\d:\\\\\", \"file\")", property $ W.splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\", "file"))-    ,("W.splitDrive \"/d\" == (\"\", \"/d\")", property $ W.splitDrive "/d" == ("", "/d"))-    ,("P.splitDrive \"/test\" == (\"/\", \"test\")", property $ P.splitDrive "/test" == ("/", "test"))-    ,("P.splitDrive \"//test\" == (\"//\", \"test\")", property $ P.splitDrive "//test" == ("//", "test"))-    ,("P.splitDrive \"test/file\" == (\"\", \"test/file\")", property $ P.splitDrive "test/file" == ("", "test/file"))-    ,("P.splitDrive \"file\" == (\"\", \"file\")", property $ P.splitDrive "file" == ("", "file"))-    ,("uncurry P.joinDrive (P.splitDrive x) == x", property $ \(QFilePathValidP x) -> uncurry P.joinDrive (P.splitDrive x) == x)-    ,("uncurry W.joinDrive (W.splitDrive x) == x", property $ \(QFilePathValidW x) -> uncurry W.joinDrive (W.splitDrive x) == x)-    ,("W.joinDrive \"C:\" \"foo\" == \"C:foo\"", property $ W.joinDrive "C:" "foo" == "C:foo")-    ,("W.joinDrive \"C:\\\\\" \"bar\" == \"C:\\\\bar\"", property $ W.joinDrive "C:\\" "bar" == "C:\\bar")-    ,("W.joinDrive \"\\\\\\\\share\" \"foo\" == \"\\\\\\\\share\\\\foo\"", property $ W.joinDrive "\\\\share" "foo" == "\\\\share\\foo")-    ,("W.joinDrive \"/:\" \"foo\" == \"/:\\\\foo\"", property $ W.joinDrive "/:" "foo" == "/:\\foo")-    ,("P.takeDrive x == fst (P.splitDrive x)", property $ \(QFilePath x) -> P.takeDrive x == fst (P.splitDrive x))-    ,("W.takeDrive x == fst (W.splitDrive x)", property $ \(QFilePath x) -> W.takeDrive x == fst (W.splitDrive x))-    ,("P.dropDrive x == snd (P.splitDrive x)", property $ \(QFilePath x) -> P.dropDrive x == snd (P.splitDrive x))-    ,("W.dropDrive x == snd (W.splitDrive x)", property $ \(QFilePath x) -> W.dropDrive x == snd (W.splitDrive x))-    ,("not (P.hasDrive x) == null (P.takeDrive x)", property $ \(QFilePath x) -> not (P.hasDrive x) == null (P.takeDrive x))-    ,("not (W.hasDrive x) == null (W.takeDrive x)", property $ \(QFilePath x) -> not (W.hasDrive x) == null (W.takeDrive x))-    ,("P.hasDrive \"/foo\" == True", property $ P.hasDrive "/foo" == True)-    ,("W.hasDrive \"C:\\\\foo\" == True", property $ W.hasDrive "C:\\foo" == True)-    ,("W.hasDrive \"C:foo\" == True", property $ W.hasDrive "C:foo" == True)-    ,("P.hasDrive \"foo\" == False", property $ P.hasDrive "foo" == False)-    ,("W.hasDrive \"foo\" == False", property $ W.hasDrive "foo" == False)-    ,("P.hasDrive \"\" == False", property $ P.hasDrive "" == False)-    ,("W.hasDrive \"\" == False", property $ W.hasDrive "" == False)-    ,("P.isDrive \"/\" == True", property $ P.isDrive "/" == True)-    ,("P.isDrive \"/foo\" == False", property $ P.isDrive "/foo" == False)-    ,("W.isDrive \"C:\\\\\" == True", property $ W.isDrive "C:\\" == True)-    ,("W.isDrive \"C:\\\\foo\" == False", property $ W.isDrive "C:\\foo" == False)-    ,("P.isDrive \"\" == False", property $ P.isDrive "" == False)-    ,("W.isDrive \"\" == False", property $ W.isDrive "" == False)-    ,("P.splitFileName \"/directory/file.ext\" == (\"/directory/\", \"file.ext\")", property $ P.splitFileName "/directory/file.ext" == ("/directory/", "file.ext"))-    ,("W.splitFileName \"/directory/file.ext\" == (\"/directory/\", \"file.ext\")", property $ W.splitFileName "/directory/file.ext" == ("/directory/", "file.ext"))-    ,("uncurry (P.</>) (P.splitFileName x) == x || fst (P.splitFileName x) == \"./\"", property $ \(QFilePathValidP x) -> uncurry (P.</>) (P.splitFileName x) == x || fst (P.splitFileName x) == "./")-    ,("uncurry (W.</>) (W.splitFileName x) == x || fst (W.splitFileName x) == \"./\"", property $ \(QFilePathValidW x) -> uncurry (W.</>) (W.splitFileName x) == x || fst (W.splitFileName x) == "./")-    ,("P.isValid (fst (P.splitFileName x))", property $ \(QFilePathValidP x) -> P.isValid (fst (P.splitFileName x)))-    ,("W.isValid (fst (W.splitFileName x))", property $ \(QFilePathValidW x) -> W.isValid (fst (W.splitFileName x)))-    ,("P.splitFileName \"file/bob.txt\" == (\"file/\", \"bob.txt\")", property $ P.splitFileName "file/bob.txt" == ("file/", "bob.txt"))-    ,("W.splitFileName \"file/bob.txt\" == (\"file/\", \"bob.txt\")", property $ W.splitFileName "file/bob.txt" == ("file/", "bob.txt"))-    ,("P.splitFileName \"file/\" == (\"file/\", \"\")", property $ P.splitFileName "file/" == ("file/", ""))-    ,("W.splitFileName \"file/\" == (\"file/\", \"\")", property $ W.splitFileName "file/" == ("file/", ""))-    ,("P.splitFileName \"bob\" == (\"./\", \"bob\")", property $ P.splitFileName "bob" == ("./", "bob"))-    ,("W.splitFileName \"bob\" == (\"./\", \"bob\")", property $ W.splitFileName "bob" == ("./", "bob"))-    ,("P.splitFileName \"/\" == (\"/\", \"\")", property $ P.splitFileName "/" == ("/", ""))-    ,("W.splitFileName \"c:\" == (\"c:\", \"\")", property $ W.splitFileName "c:" == ("c:", ""))-    ,("P.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ P.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")-    ,("W.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ W.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")-    ,("P.replaceFileName x (P.takeFileName x) == x", property $ \(QFilePathValidP x) -> P.replaceFileName x (P.takeFileName x) == x)-    ,("W.replaceFileName x (W.takeFileName x) == x", property $ \(QFilePathValidW x) -> W.replaceFileName x (W.takeFileName x) == x)-    ,("P.dropFileName \"/directory/file.ext\" == \"/directory/\"", property $ P.dropFileName "/directory/file.ext" == "/directory/")-    ,("W.dropFileName \"/directory/file.ext\" == \"/directory/\"", property $ W.dropFileName "/directory/file.ext" == "/directory/")-    ,("P.dropFileName x == fst (P.splitFileName x)", property $ \(QFilePath x) -> P.dropFileName x == fst (P.splitFileName x))-    ,("W.dropFileName x == fst (W.splitFileName x)", property $ \(QFilePath x) -> W.dropFileName x == fst (W.splitFileName x))-    ,("P.takeFileName \"/directory/file.ext\" == \"file.ext\"", property $ P.takeFileName "/directory/file.ext" == "file.ext")-    ,("W.takeFileName \"/directory/file.ext\" == \"file.ext\"", property $ W.takeFileName "/directory/file.ext" == "file.ext")-    ,("P.takeFileName \"test/\" == \"\"", property $ P.takeFileName "test/" == "")-    ,("W.takeFileName \"test/\" == \"\"", property $ W.takeFileName "test/" == "")-    ,("P.takeFileName x `isSuffixOf` x", property $ \(QFilePath x) -> P.takeFileName x `isSuffixOf` x)-    ,("W.takeFileName x `isSuffixOf` x", property $ \(QFilePath x) -> W.takeFileName x `isSuffixOf` x)-    ,("P.takeFileName x == snd (P.splitFileName x)", property $ \(QFilePath x) -> P.takeFileName x == snd (P.splitFileName x))-    ,("W.takeFileName x == snd (W.splitFileName x)", property $ \(QFilePath x) -> W.takeFileName x == snd (W.splitFileName x))-    ,("P.takeFileName (P.replaceFileName x \"fred\") == \"fred\"", property $ \(QFilePathValidP x) -> P.takeFileName (P.replaceFileName x "fred") == "fred")-    ,("W.takeFileName (W.replaceFileName x \"fred\") == \"fred\"", property $ \(QFilePathValidW x) -> W.takeFileName (W.replaceFileName x "fred") == "fred")-    ,("P.takeFileName (x P.</> \"fred\") == \"fred\"", property $ \(QFilePathValidP x) -> P.takeFileName (x P.</> "fred") == "fred")-    ,("W.takeFileName (x W.</> \"fred\") == \"fred\"", property $ \(QFilePathValidW x) -> W.takeFileName (x W.</> "fred") == "fred")-    ,("P.isRelative (P.takeFileName x)", property $ \(QFilePathValidP x) -> P.isRelative (P.takeFileName x))-    ,("W.isRelative (W.takeFileName x)", property $ \(QFilePathValidW x) -> W.isRelative (W.takeFileName x))-    ,("P.takeBaseName \"/directory/file.ext\" == \"file\"", property $ P.takeBaseName "/directory/file.ext" == "file")-    ,("W.takeBaseName \"/directory/file.ext\" == \"file\"", property $ W.takeBaseName "/directory/file.ext" == "file")-    ,("P.takeBaseName \"file/test.txt\" == \"test\"", property $ P.takeBaseName "file/test.txt" == "test")-    ,("W.takeBaseName \"file/test.txt\" == \"test\"", property $ W.takeBaseName "file/test.txt" == "test")-    ,("P.takeBaseName \"dave.ext\" == \"dave\"", property $ P.takeBaseName "dave.ext" == "dave")-    ,("W.takeBaseName \"dave.ext\" == \"dave\"", property $ W.takeBaseName "dave.ext" == "dave")-    ,("P.takeBaseName \"\" == \"\"", property $ P.takeBaseName "" == "")-    ,("W.takeBaseName \"\" == \"\"", property $ W.takeBaseName "" == "")-    ,("P.takeBaseName \"test\" == \"test\"", property $ P.takeBaseName "test" == "test")-    ,("W.takeBaseName \"test\" == \"test\"", property $ W.takeBaseName "test" == "test")-    ,("P.takeBaseName (P.addTrailingPathSeparator x) == \"\"", property $ \(QFilePath x) -> P.takeBaseName (P.addTrailingPathSeparator x) == "")-    ,("W.takeBaseName (W.addTrailingPathSeparator x) == \"\"", property $ \(QFilePath x) -> W.takeBaseName (W.addTrailingPathSeparator x) == "")-    ,("P.takeBaseName \"file/file.tar.gz\" == \"file.tar\"", property $ P.takeBaseName "file/file.tar.gz" == "file.tar")-    ,("W.takeBaseName \"file/file.tar.gz\" == \"file.tar\"", property $ W.takeBaseName "file/file.tar.gz" == "file.tar")-    ,("P.replaceBaseName \"/directory/other.ext\" \"file\" == \"/directory/file.ext\"", property $ P.replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext")-    ,("W.replaceBaseName \"/directory/other.ext\" \"file\" == \"/directory/file.ext\"", property $ W.replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext")-    ,("P.replaceBaseName \"file/test.txt\" \"bob\" == \"file/bob.txt\"", property $ P.replaceBaseName "file/test.txt" "bob" == "file/bob.txt")-    ,("W.replaceBaseName \"file/test.txt\" \"bob\" == \"file/bob.txt\"", property $ W.replaceBaseName "file/test.txt" "bob" == "file/bob.txt")-    ,("P.replaceBaseName \"fred\" \"bill\" == \"bill\"", property $ P.replaceBaseName "fred" "bill" == "bill")-    ,("W.replaceBaseName \"fred\" \"bill\" == \"bill\"", property $ W.replaceBaseName "fred" "bill" == "bill")-    ,("P.replaceBaseName \"/dave/fred/bob.gz.tar\" \"new\" == \"/dave/fred/new.tar\"", property $ P.replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar")-    ,("W.replaceBaseName \"/dave/fred/bob.gz.tar\" \"new\" == \"/dave/fred/new.tar\"", property $ W.replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar")-    ,("P.replaceBaseName x (P.takeBaseName x) == x", property $ \(QFilePathValidP x) -> P.replaceBaseName x (P.takeBaseName x) == x)-    ,("W.replaceBaseName x (W.takeBaseName x) == x", property $ \(QFilePathValidW x) -> W.replaceBaseName x (W.takeBaseName x) == x)-    ,("P.hasTrailingPathSeparator \"test\" == False", property $ P.hasTrailingPathSeparator "test" == False)-    ,("W.hasTrailingPathSeparator \"test\" == False", property $ W.hasTrailingPathSeparator "test" == False)-    ,("P.hasTrailingPathSeparator \"test/\" == True", property $ P.hasTrailingPathSeparator "test/" == True)-    ,("W.hasTrailingPathSeparator \"test/\" == True", property $ W.hasTrailingPathSeparator "test/" == True)-    ,("P.hasTrailingPathSeparator (P.addTrailingPathSeparator x)", property $ \(QFilePath x) -> P.hasTrailingPathSeparator (P.addTrailingPathSeparator x))-    ,("W.hasTrailingPathSeparator (W.addTrailingPathSeparator x)", property $ \(QFilePath x) -> W.hasTrailingPathSeparator (W.addTrailingPathSeparator x))-    ,("P.hasTrailingPathSeparator x ==> P.addTrailingPathSeparator x == x", property $ \(QFilePath x) -> P.hasTrailingPathSeparator x ==> P.addTrailingPathSeparator x == x)-    ,("W.hasTrailingPathSeparator x ==> W.addTrailingPathSeparator x == x", property $ \(QFilePath x) -> W.hasTrailingPathSeparator x ==> W.addTrailingPathSeparator x == x)-    ,("P.addTrailingPathSeparator \"test/rest\" == \"test/rest/\"", property $ P.addTrailingPathSeparator "test/rest" == "test/rest/")-    ,("P.dropTrailingPathSeparator \"file/test/\" == \"file/test\"", property $ P.dropTrailingPathSeparator "file/test/" == "file/test")-    ,("W.dropTrailingPathSeparator \"file/test/\" == \"file/test\"", property $ W.dropTrailingPathSeparator "file/test/" == "file/test")-    ,("P.dropTrailingPathSeparator \"/\" == \"/\"", property $ P.dropTrailingPathSeparator "/" == "/")-    ,("W.dropTrailingPathSeparator \"/\" == \"/\"", property $ W.dropTrailingPathSeparator "/" == "/")-    ,("W.dropTrailingPathSeparator \"\\\\\" == \"\\\\\"", property $ W.dropTrailingPathSeparator "\\" == "\\")-    ,("not (P.hasTrailingPathSeparator (P.dropTrailingPathSeparator x)) || P.isDrive x", property $ \(QFilePath x) -> not (P.hasTrailingPathSeparator (P.dropTrailingPathSeparator x)) || P.isDrive x)-    ,("P.takeDirectory \"/directory/other.ext\" == \"/directory\"", property $ P.takeDirectory "/directory/other.ext" == "/directory")-    ,("W.takeDirectory \"/directory/other.ext\" == \"/directory\"", property $ W.takeDirectory "/directory/other.ext" == "/directory")-    ,("P.takeDirectory x `isPrefixOf` x || P.takeDirectory x == \".\"", property $ \(QFilePath x) -> P.takeDirectory x `isPrefixOf` x || P.takeDirectory x == ".")-    ,("W.takeDirectory x `isPrefixOf` x || W.takeDirectory x == \".\"", property $ \(QFilePath x) -> W.takeDirectory x `isPrefixOf` x || W.takeDirectory x == ".")-    ,("P.takeDirectory \"foo\" == \".\"", property $ P.takeDirectory "foo" == ".")-    ,("W.takeDirectory \"foo\" == \".\"", property $ W.takeDirectory "foo" == ".")-    ,("P.takeDirectory \"/\" == \"/\"", property $ P.takeDirectory "/" == "/")-    ,("W.takeDirectory \"/\" == \"/\"", property $ W.takeDirectory "/" == "/")-    ,("P.takeDirectory \"/foo\" == \"/\"", property $ P.takeDirectory "/foo" == "/")-    ,("W.takeDirectory \"/foo\" == \"/\"", property $ W.takeDirectory "/foo" == "/")-    ,("P.takeDirectory \"/foo/bar/baz\" == \"/foo/bar\"", property $ P.takeDirectory "/foo/bar/baz" == "/foo/bar")-    ,("W.takeDirectory \"/foo/bar/baz\" == \"/foo/bar\"", property $ W.takeDirectory "/foo/bar/baz" == "/foo/bar")-    ,("P.takeDirectory \"/foo/bar/baz/\" == \"/foo/bar/baz\"", property $ P.takeDirectory "/foo/bar/baz/" == "/foo/bar/baz")-    ,("W.takeDirectory \"/foo/bar/baz/\" == \"/foo/bar/baz\"", property $ W.takeDirectory "/foo/bar/baz/" == "/foo/bar/baz")-    ,("P.takeDirectory \"foo/bar/baz\" == \"foo/bar\"", property $ P.takeDirectory "foo/bar/baz" == "foo/bar")-    ,("W.takeDirectory \"foo/bar/baz\" == \"foo/bar\"", property $ W.takeDirectory "foo/bar/baz" == "foo/bar")-    ,("W.takeDirectory \"foo\\\\bar\" == \"foo\"", property $ W.takeDirectory "foo\\bar" == "foo")-    ,("W.takeDirectory \"foo\\\\bar\\\\\\\\\" == \"foo\\\\bar\"", property $ W.takeDirectory "foo\\bar\\\\" == "foo\\bar")-    ,("W.takeDirectory \"C:\\\\\" == \"C:\\\\\"", property $ W.takeDirectory "C:\\" == "C:\\")-    ,("P.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ P.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")-    ,("W.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ W.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")-    ,("P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x)-    ,("W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x)-    ,("\"/directory\" P.</> \"file.ext\" == \"/directory/file.ext\"", property $ "/directory" P.</> "file.ext" == "/directory/file.ext")-    ,("\"/directory\" W.</> \"file.ext\" == \"/directory\\\\file.ext\"", property $ "/directory" W.</> "file.ext" == "/directory\\file.ext")-    ,("\"directory\" P.</> \"/file.ext\" == \"/file.ext\"", property $ "directory" P.</> "/file.ext" == "/file.ext")-    ,("\"directory\" W.</> \"/file.ext\" == \"/file.ext\"", property $ "directory" W.</> "/file.ext" == "/file.ext")-    ,("(P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> (P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x)-    ,("(W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> (W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x)-    ,("\"/\" P.</> \"test\" == \"/test\"", property $ "/" P.</> "test" == "/test")-    ,("\"home\" P.</> \"bob\" == \"home/bob\"", property $ "home" P.</> "bob" == "home/bob")-    ,("\"x:\" P.</> \"foo\" == \"x:/foo\"", property $ "x:" P.</> "foo" == "x:/foo")-    ,("\"C:\\\\foo\" W.</> \"bar\" == \"C:\\\\foo\\\\bar\"", property $ "C:\\foo" W.</> "bar" == "C:\\foo\\bar")-    ,("\"home\" W.</> \"bob\" == \"home\\\\bob\"", property $ "home" W.</> "bob" == "home\\bob")-    ,("\"home\" P.</> \"/bob\" == \"/bob\"", property $ "home" P.</> "/bob" == "/bob")-    ,("\"home\" W.</> \"C:\\\\bob\" == \"C:\\\\bob\"", property $ "home" W.</> "C:\\bob" == "C:\\bob")-    ,("\"home\" W.</> \"/bob\" == \"/bob\"", property $ "home" W.</> "/bob" == "/bob")-    ,("\"home\" W.</> \"\\\\bob\" == \"\\\\bob\"", property $ "home" W.</> "\\bob" == "\\bob")-    ,("\"C:\\\\home\" W.</> \"\\\\bob\" == \"\\\\bob\"", property $ "C:\\home" W.</> "\\bob" == "\\bob")-    ,("\"D:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", property $ "D:\\foo" W.</> "C:bar" == "C:bar")-    ,("\"C:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", property $ "C:\\foo" W.</> "C:bar" == "C:bar")-    ,("P.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", property $ P.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])-    ,("W.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", property $ W.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])-    ,("concat (P.splitPath x) == x", property $ \(QFilePath x) -> concat (P.splitPath x) == x)-    ,("concat (W.splitPath x) == x", property $ \(QFilePath x) -> concat (W.splitPath x) == x)-    ,("P.splitPath \"test//item/\" == [\"test//\", \"item/\"]", property $ P.splitPath "test//item/" == ["test//", "item/"])-    ,("W.splitPath \"test//item/\" == [\"test//\", \"item/\"]", property $ W.splitPath "test//item/" == ["test//", "item/"])-    ,("P.splitPath \"test/item/file\" == [\"test/\", \"item/\", \"file\"]", property $ P.splitPath "test/item/file" == ["test/", "item/", "file"])-    ,("W.splitPath \"test/item/file\" == [\"test/\", \"item/\", \"file\"]", property $ W.splitPath "test/item/file" == ["test/", "item/", "file"])-    ,("P.splitPath \"\" == []", property $ P.splitPath "" == [])-    ,("W.splitPath \"\" == []", property $ W.splitPath "" == [])-    ,("W.splitPath \"c:\\\\test\\\\path\" == [\"c:\\\\\", \"test\\\\\", \"path\"]", property $ W.splitPath "c:\\test\\path" == ["c:\\", "test\\", "path"])-    ,("P.splitPath \"/file/test\" == [\"/\", \"file/\", \"test\"]", property $ P.splitPath "/file/test" == ["/", "file/", "test"])-    ,("P.splitDirectories \"/directory/file.ext\" == [\"/\", \"directory\", \"file.ext\"]", property $ P.splitDirectories "/directory/file.ext" == ["/", "directory", "file.ext"])-    ,("W.splitDirectories \"/directory/file.ext\" == [\"/\", \"directory\", \"file.ext\"]", property $ W.splitDirectories "/directory/file.ext" == ["/", "directory", "file.ext"])-    ,("P.splitDirectories \"test/file\" == [\"test\", \"file\"]", property $ P.splitDirectories "test/file" == ["test", "file"])-    ,("W.splitDirectories \"test/file\" == [\"test\", \"file\"]", property $ W.splitDirectories "test/file" == ["test", "file"])-    ,("P.splitDirectories \"/test/file\" == [\"/\", \"test\", \"file\"]", property $ P.splitDirectories "/test/file" == ["/", "test", "file"])-    ,("W.splitDirectories \"/test/file\" == [\"/\", \"test\", \"file\"]", property $ W.splitDirectories "/test/file" == ["/", "test", "file"])-    ,("W.splitDirectories \"C:\\\\test\\\\file\" == [\"C:\\\\\", \"test\", \"file\"]", property $ W.splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"])-    ,("P.joinPath (P.splitDirectories x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> P.joinPath (P.splitDirectories x) `P.equalFilePath` x)-    ,("W.joinPath (W.splitDirectories x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> W.joinPath (W.splitDirectories x) `W.equalFilePath` x)-    ,("P.splitDirectories \"\" == []", property $ P.splitDirectories "" == [])-    ,("W.splitDirectories \"\" == []", property $ W.splitDirectories "" == [])-    ,("W.splitDirectories \"C:\\\\test\\\\\\\\\\\\file\" == [\"C:\\\\\", \"test\", \"file\"]", property $ W.splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"])-    ,("P.splitDirectories \"/test///file\" == [\"/\", \"test\", \"file\"]", property $ P.splitDirectories "/test///file" == ["/", "test", "file"])-    ,("W.splitDirectories \"/test///file\" == [\"/\", \"test\", \"file\"]", property $ W.splitDirectories "/test///file" == ["/", "test", "file"])-    ,("P.joinPath a == foldr (P.</>) \"\" a", property $ \a -> P.joinPath a == foldr (P.</>) "" a)-    ,("W.joinPath a == foldr (W.</>) \"\" a", property $ \a -> W.joinPath a == foldr (W.</>) "" a)-    ,("P.joinPath [\"/\", \"directory/\", \"file.ext\"] == \"/directory/file.ext\"", property $ P.joinPath ["/", "directory/", "file.ext"] == "/directory/file.ext")-    ,("W.joinPath [\"/\", \"directory/\", \"file.ext\"] == \"/directory/file.ext\"", property $ W.joinPath ["/", "directory/", "file.ext"] == "/directory/file.ext")-    ,("P.joinPath (P.splitPath x) == x", property $ \(QFilePathValidP x) -> P.joinPath (P.splitPath x) == x)-    ,("W.joinPath (W.splitPath x) == x", property $ \(QFilePathValidW x) -> W.joinPath (W.splitPath x) == x)-    ,("P.joinPath [] == \"\"", property $ P.joinPath [] == "")-    ,("W.joinPath [] == \"\"", property $ W.joinPath [] == "")-    ,("P.joinPath [\"test\", \"file\", \"path\"] == \"test/file/path\"", property $ P.joinPath ["test", "file", "path"] == "test/file/path")-    ,("x == y ==> P.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> x == y ==> P.equalFilePath x y)-    ,("x == y ==> W.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> x == y ==> W.equalFilePath x y)-    ,("P.normalise x == P.normalise y ==> P.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> P.normalise x == P.normalise y ==> P.equalFilePath x y)-    ,("W.normalise x == W.normalise y ==> W.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> W.normalise x == W.normalise y ==> W.equalFilePath x y)-    ,("P.equalFilePath \"foo\" \"foo/\"", property $ P.equalFilePath "foo" "foo/")-    ,("W.equalFilePath \"foo\" \"foo/\"", property $ W.equalFilePath "foo" "foo/")-    ,("not (P.equalFilePath \"/a/../c\" \"/c\")", property $ not (P.equalFilePath "/a/../c" "/c"))-    ,("not (W.equalFilePath \"/a/../c\" \"/c\")", property $ not (W.equalFilePath "/a/../c" "/c"))-    ,("not (P.equalFilePath \"foo\" \"/foo\")", property $ not (P.equalFilePath "foo" "/foo"))-    ,("not (W.equalFilePath \"foo\" \"/foo\")", property $ not (W.equalFilePath "foo" "/foo"))-    ,("not (P.equalFilePath \"foo\" \"FOO\")", property $ not (P.equalFilePath "foo" "FOO"))-    ,("W.equalFilePath \"foo\" \"FOO\"", property $ W.equalFilePath "foo" "FOO")-    ,("not (W.equalFilePath \"C:\" \"C:/\")", property $ not (W.equalFilePath "C:" "C:/"))-    ,("P.makeRelative \"/directory\" \"/directory/file.ext\" == \"file.ext\"", property $ P.makeRelative "/directory" "/directory/file.ext" == "file.ext")-    ,("W.makeRelative \"/directory\" \"/directory/file.ext\" == \"file.ext\"", property $ W.makeRelative "/directory" "/directory/file.ext" == "file.ext")-    ,("P.makeRelative (P.takeDirectory x) x `P.equalFilePath` P.takeFileName x", property $ \(QFilePathValidP x) -> P.makeRelative (P.takeDirectory x) x `P.equalFilePath` P.takeFileName x)-    ,("W.makeRelative (W.takeDirectory x) x `W.equalFilePath` W.takeFileName x", property $ \(QFilePathValidW x) -> W.makeRelative (W.takeDirectory x) x `W.equalFilePath` W.takeFileName x)-    ,("P.makeRelative x x == \".\"", property $ \(QFilePath x) -> P.makeRelative x x == ".")-    ,("W.makeRelative x x == \".\"", property $ \(QFilePath x) -> W.makeRelative x x == ".")-    ,("P.equalFilePath x y || (P.isRelative x && P.makeRelative y x == x) || P.equalFilePath (y P.</> P.makeRelative y x) x", property $ \(QFilePathValidP x) (QFilePathValidP y) -> P.equalFilePath x y || (P.isRelative x && P.makeRelative y x == x) || P.equalFilePath (y P.</> P.makeRelative y x) x)-    ,("W.equalFilePath x y || (W.isRelative x && W.makeRelative y x == x) || W.equalFilePath (y W.</> W.makeRelative y x) x", property $ \(QFilePathValidW x) (QFilePathValidW y) -> W.equalFilePath x y || (W.isRelative x && W.makeRelative y x == x) || W.equalFilePath (y W.</> W.makeRelative y x) x)-    ,("W.makeRelative \"C:\\\\Home\" \"c:\\\\home\\\\bob\" == \"bob\"", property $ W.makeRelative "C:\\Home" "c:\\home\\bob" == "bob")-    ,("W.makeRelative \"C:\\\\Home\" \"c:/home/bob\" == \"bob\"", property $ W.makeRelative "C:\\Home" "c:/home/bob" == "bob")-    ,("W.makeRelative \"C:\\\\Home\" \"D:\\\\Home\\\\Bob\" == \"D:\\\\Home\\\\Bob\"", property $ W.makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob")-    ,("W.makeRelative \"C:\\\\Home\" \"C:Home\\\\Bob\" == \"C:Home\\\\Bob\"", property $ W.makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob")-    ,("W.makeRelative \"/Home\" \"/home/bob\" == \"bob\"", property $ W.makeRelative "/Home" "/home/bob" == "bob")-    ,("W.makeRelative \"/\" \"//\" == \"//\"", property $ W.makeRelative "/" "//" == "//")-    ,("P.makeRelative \"/Home\" \"/home/bob\" == \"/home/bob\"", property $ P.makeRelative "/Home" "/home/bob" == "/home/bob")-    ,("P.makeRelative \"/home/\" \"/home/bob/foo/bar\" == \"bob/foo/bar\"", property $ P.makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar")-    ,("P.makeRelative \"/fred\" \"bob\" == \"bob\"", property $ P.makeRelative "/fred" "bob" == "bob")-    ,("P.makeRelative \"/file/test\" \"/file/test/fred\" == \"fred\"", property $ P.makeRelative "/file/test" "/file/test/fred" == "fred")-    ,("P.makeRelative \"/file/test\" \"/file/test/fred/\" == \"fred/\"", property $ P.makeRelative "/file/test" "/file/test/fred/" == "fred/")-    ,("P.makeRelative \"some/path\" \"some/path/a/b/c\" == \"a/b/c\"", property $ P.makeRelative "some/path" "some/path/a/b/c" == "a/b/c")-    ,("P.normalise \"/file/\\\\test////\" == \"/file/\\\\test/\"", property $ P.normalise "/file/\\test////" == "/file/\\test/")-    ,("P.normalise \"/file/./test\" == \"/file/test\"", property $ P.normalise "/file/./test" == "/file/test")-    ,("P.normalise \"/test/file/../bob/fred/\" == \"/test/file/../bob/fred/\"", property $ P.normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/")-    ,("P.normalise \"../bob/fred/\" == \"../bob/fred/\"", property $ P.normalise "../bob/fred/" == "../bob/fred/")-    ,("P.normalise \"/a/../c\" == \"/a/../c\"", property $ P.normalise "/a/../c" == "/a/../c")-    ,("P.normalise \"./bob/fred/\" == \"bob/fred/\"", property $ P.normalise "./bob/fred/" == "bob/fred/")-    ,("W.normalise \"c:\\\\file/bob\\\\\" == \"C:\\\\file\\\\bob\\\\\"", property $ W.normalise "c:\\file/bob\\" == "C:\\file\\bob\\")-    ,("W.normalise \"c:\\\\\" == \"C:\\\\\"", property $ W.normalise "c:\\" == "C:\\")-    ,("W.normalise \"C:.\\\\\" == \"C:\"", property $ W.normalise "C:.\\" == "C:")-    ,("W.normalise \"\\\\\\\\server\\\\test\" == \"\\\\\\\\server\\\\test\"", property $ W.normalise "\\\\server\\test" == "\\\\server\\test")-    ,("W.normalise \"//server/test\" == \"\\\\\\\\server\\\\test\"", property $ W.normalise "//server/test" == "\\\\server\\test")-    ,("W.normalise \"c:/file\" == \"C:\\\\file\"", property $ W.normalise "c:/file" == "C:\\file")-    ,("W.normalise \"/file\" == \"\\\\file\"", property $ W.normalise "/file" == "\\file")-    ,("W.normalise \"\\\\\" == \"\\\\\"", property $ W.normalise "\\" == "\\")-    ,("W.normalise \"/./\" == \"\\\\\"", property $ W.normalise "/./" == "\\")-    ,("P.normalise \".\" == \".\"", property $ P.normalise "." == ".")-    ,("W.normalise \".\" == \".\"", property $ W.normalise "." == ".")-    ,("P.normalise \"./\" == \"./\"", property $ P.normalise "./" == "./")-    ,("P.normalise \"./.\" == \"./\"", property $ P.normalise "./." == "./")-    ,("P.normalise \"/./\" == \"/\"", property $ P.normalise "/./" == "/")-    ,("P.normalise \"/\" == \"/\"", property $ P.normalise "/" == "/")-    ,("P.normalise \"bob/fred/.\" == \"bob/fred/\"", property $ P.normalise "bob/fred/." == "bob/fred/")-    ,("P.normalise \"//home\" == \"/home\"", property $ P.normalise "//home" == "/home")-    ,("P.isValid \"\" == False", property $ P.isValid "" == False)-    ,("W.isValid \"\" == False", property $ W.isValid "" == False)-    ,("P.isValid \"\\0\" == False", property $ P.isValid "\0" == False)-    ,("W.isValid \"\\0\" == False", property $ W.isValid "\0" == False)-    ,("P.isValid \"/random_ path:*\" == True", property $ P.isValid "/random_ path:*" == True)-    ,("P.isValid x == not (null x)", property $ \(QFilePath x) -> P.isValid x == not (null x))-    ,("W.isValid \"c:\\\\test\" == True", property $ W.isValid "c:\\test" == True)-    ,("W.isValid \"c:\\\\test:of_test\" == False", property $ W.isValid "c:\\test:of_test" == False)-    ,("W.isValid \"test*\" == False", property $ W.isValid "test*" == False)-    ,("W.isValid \"c:\\\\test\\\\nul\" == False", property $ W.isValid "c:\\test\\nul" == False)-    ,("W.isValid \"c:\\\\test\\\\prn.txt\" == False", property $ W.isValid "c:\\test\\prn.txt" == False)-    ,("W.isValid \"c:\\\\nul\\\\file\" == False", property $ W.isValid "c:\\nul\\file" == False)-    ,("W.isValid \"\\\\\\\\\" == False", property $ W.isValid "\\\\" == False)-    ,("W.isValid \"\\\\\\\\\\\\foo\" == False", property $ W.isValid "\\\\\\foo" == False)-    ,("W.isValid \"\\\\\\\\?\\\\D:file\" == False", property $ W.isValid "\\\\?\\D:file" == False)-    ,("W.isValid \"foo\\tbar\" == False", property $ W.isValid "foo\tbar" == False)-    ,("W.isValid \"nul .txt\" == False", property $ W.isValid "nul .txt" == False)-    ,("W.isValid \" nul.txt\" == True", property $ W.isValid " nul.txt" == True)-    ,("P.isValid (P.makeValid x)", property $ \(QFilePath x) -> P.isValid (P.makeValid x))-    ,("W.isValid (W.makeValid x)", property $ \(QFilePath x) -> W.isValid (W.makeValid x))-    ,("P.isValid x ==> P.makeValid x == x", property $ \(QFilePath x) -> P.isValid x ==> P.makeValid x == x)-    ,("W.isValid x ==> W.makeValid x == x", property $ \(QFilePath x) -> W.isValid x ==> W.makeValid x == x)-    ,("P.makeValid \"\" == \"_\"", property $ P.makeValid "" == "_")-    ,("W.makeValid \"\" == \"_\"", property $ W.makeValid "" == "_")-    ,("P.makeValid \"file\\0name\" == \"file_name\"", property $ P.makeValid "file\0name" == "file_name")-    ,("W.makeValid \"file\\0name\" == \"file_name\"", property $ W.makeValid "file\0name" == "file_name")-    ,("W.makeValid \"c:\\\\already\\\\/valid\" == \"c:\\\\already\\\\/valid\"", property $ W.makeValid "c:\\already\\/valid" == "c:\\already\\/valid")-    ,("W.makeValid \"c:\\\\test:of_test\" == \"c:\\\\test_of_test\"", property $ W.makeValid "c:\\test:of_test" == "c:\\test_of_test")-    ,("W.makeValid \"test*\" == \"test_\"", property $ W.makeValid "test*" == "test_")-    ,("W.makeValid \"c:\\\\test\\\\nul\" == \"c:\\\\test\\\\nul_\"", property $ W.makeValid "c:\\test\\nul" == "c:\\test\\nul_")-    ,("W.makeValid \"c:\\\\test\\\\prn.txt\" == \"c:\\\\test\\\\prn_.txt\"", property $ W.makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt")-    ,("W.makeValid \"c:\\\\test/prn.txt\" == \"c:\\\\test/prn_.txt\"", property $ W.makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt")-    ,("W.makeValid \"c:\\\\nul\\\\file\" == \"c:\\\\nul_\\\\file\"", property $ W.makeValid "c:\\nul\\file" == "c:\\nul_\\file")-    ,("W.makeValid \"\\\\\\\\\\\\foo\" == \"\\\\\\\\drive\"", property $ W.makeValid "\\\\\\foo" == "\\\\drive")-    ,("W.makeValid \"\\\\\\\\?\\\\D:file\" == \"\\\\\\\\?\\\\D:\\\\file\"", property $ W.makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file")-    ,("W.makeValid \"nul .txt\" == \"nul _.txt\"", property $ W.makeValid "nul .txt" == "nul _.txt")-    ,("W.isRelative \"path\\\\test\" == True", property $ W.isRelative "path\\test" == True)-    ,("W.isRelative \"c:\\\\test\" == False", property $ W.isRelative "c:\\test" == False)-    ,("W.isRelative \"c:test\" == True", property $ W.isRelative "c:test" == True)-    ,("W.isRelative \"c:\\\\\" == False", property $ W.isRelative "c:\\" == False)-    ,("W.isRelative \"c:/\" == False", property $ W.isRelative "c:/" == False)-    ,("W.isRelative \"c:\" == True", property $ W.isRelative "c:" == True)-    ,("W.isRelative \"\\\\\\\\foo\" == False", property $ W.isRelative "\\\\foo" == False)-    ,("W.isRelative \"\\\\\\\\?\\\\foo\" == False", property $ W.isRelative "\\\\?\\foo" == False)-    ,("W.isRelative \"\\\\\\\\?\\\\UNC\\\\foo\" == False", property $ W.isRelative "\\\\?\\UNC\\foo" == False)-    ,("W.isRelative \"/foo\" == True", property $ W.isRelative "/foo" == True)-    ,("W.isRelative \"\\\\foo\" == True", property $ W.isRelative "\\foo" == True)-    ,("P.isRelative \"test/path\" == True", property $ P.isRelative "test/path" == True)-    ,("P.isRelative \"/test\" == False", property $ P.isRelative "/test" == False)-    ,("P.isRelative \"/\" == False", property $ P.isRelative "/" == False)-    ,("P.isAbsolute x == not (P.isRelative x)", property $ \(QFilePath x) -> P.isAbsolute x == not (P.isRelative x))-    ,("W.isAbsolute x == not (W.isRelative x)", property $ \(QFilePath x) -> W.isAbsolute x == not (W.isRelative x))-    ]
tests/TestUtil.hs view
@@ -1,19 +1,39 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-orphans #-}  module TestUtil(-    (==>), QFilePath(..), QFilePathValidW(..), QFilePathValidP(..),+    module TestUtil,     module Test.QuickCheck,     module Data.List,     module Data.Maybe     ) where  import Test.QuickCheck hiding ((==>))+import Data.ByteString.Short (ShortByteString) import Data.List import Data.Maybe import Control.Monad import qualified System.FilePath.Windows as W import qualified System.FilePath.Posix as P+#ifdef GHC_MAKE+import qualified System.OsPath.Windows.Internal as AFP_W+import qualified System.OsPath.Posix.Internal as AFP_P+#else+import qualified System.OsPath.Windows as AFP_W+import qualified System.OsPath.Posix as AFP_P+import System.OsPath.Types+#endif+import System.OsString.Internal.Types+import System.OsPath.Encoding.Internal+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import GHC.IO.Encoding.Failure+import System.Environment + infixr 0 ==>+(==>) :: Bool -> Bool -> Bool a ==> b = not a || b  @@ -50,3 +70,119 @@     | y <- map valid $ shrinkList (\x -> ['a' | x /= 'a']) o     , length y < length o || (length y == length o && countA y > countA o)]     where countA = length . filter (== 'a')++encodeUtf16LE :: String -> ShortByteString+encodeUtf16LE = either (error . show) id . encodeWithTE (mkUTF16le TransliterateCodingFailure)++encodeUtf8 :: String -> ShortByteString+encodeUtf8 = either (error . show) id . encodeWithTE (mkUTF8 TransliterateCodingFailure)++decodeUtf16LE :: ShortByteString -> String+decodeUtf16LE = either (error . show) id . decodeWithTE (mkUTF16le TransliterateCodingFailure)++decodeUtf8 :: ShortByteString -> String+decodeUtf8 = either (error . show) id . decodeWithTE (mkUTF8 TransliterateCodingFailure)++#ifdef GHC_MAKE+newtype QFilePathValidAFP_W = QFilePathValidAFP_W ShortByteString deriving Show++instance Arbitrary QFilePathValidAFP_W where+    arbitrary = fmap (QFilePathValidAFP_W . AFP_W.makeValid . encodeUtf16LE) arbitraryFilePath+    shrink (QFilePathValidAFP_W x) = shrinkValid (QFilePathValidAFP_W . encodeUtf16LE) (decodeUtf16LE . AFP_W.makeValid . encodeUtf16LE) (decodeUtf16LE x)++newtype QFilePathValidAFP_P = QFilePathValidAFP_P ShortByteString deriving Show++instance Arbitrary QFilePathValidAFP_P where+    arbitrary = fmap (QFilePathValidAFP_P . AFP_P.makeValid . encodeUtf8) arbitraryFilePath+    shrink (QFilePathValidAFP_P x) = shrinkValid (QFilePathValidAFP_P . encodeUtf8) (decodeUtf8 . AFP_P.makeValid . encodeUtf8) (decodeUtf8 x)++newtype QFilePathAFP_W = QFilePathAFP_W ShortByteString deriving Show+newtype QFilePathAFP_P = QFilePathAFP_P ShortByteString deriving Show++instance Arbitrary QFilePathAFP_W where+    arbitrary = fmap (QFilePathAFP_W . encodeUtf16LE) arbitraryFilePath+    shrink (QFilePathAFP_W x) = shrinkValid (QFilePathAFP_W . encodeUtf16LE) id (decodeUtf16LE x)++instance Arbitrary QFilePathAFP_P where+    arbitrary = fmap (QFilePathAFP_P . encodeUtf8) arbitraryFilePath+    shrink (QFilePathAFP_P x) = shrinkValid (QFilePathAFP_P . encodeUtf8) id (decodeUtf8 x)++newtype QFilePathsAFP_W = QFilePathsAFP_W [ShortByteString] deriving Show+newtype QFilePathsAFP_P = QFilePathsAFP_P [ShortByteString] deriving Show++instance Arbitrary QFilePathsAFP_W where+    arbitrary = fmap (QFilePathsAFP_W . fmap encodeUtf16LE) (listOf arbitraryFilePath)++instance Arbitrary QFilePathsAFP_P where+    arbitrary = fmap (QFilePathsAFP_P . fmap encodeUtf8) (listOf arbitraryFilePath)++#else+++newtype QFilePathValidAFP_W = QFilePathValidAFP_W WindowsPath deriving Show++instance Arbitrary QFilePathValidAFP_W where+    arbitrary = fmap (QFilePathValidAFP_W . AFP_W.makeValid . WS . encodeUtf16LE) arbitraryFilePath+    shrink (QFilePathValidAFP_W x) = shrinkValid (QFilePathValidAFP_W . WS . encodeUtf16LE) (decodeUtf16LE . getWindowsString . AFP_W.makeValid . WS . encodeUtf16LE) (decodeUtf16LE . getWindowsString $ x)++newtype QFilePathValidAFP_P = QFilePathValidAFP_P PosixPath deriving Show++instance Arbitrary QFilePathValidAFP_P where+    arbitrary = fmap (QFilePathValidAFP_P . AFP_P.makeValid . PS . encodeUtf8) arbitraryFilePath+    shrink (QFilePathValidAFP_P x) = shrinkValid (QFilePathValidAFP_P . PS . encodeUtf8) (decodeUtf8 . getPosixString . AFP_P.makeValid . PS . encodeUtf8) (decodeUtf8 . getPosixString $ x)++newtype QFilePathAFP_W = QFilePathAFP_W WindowsPath deriving Show+newtype QFilePathAFP_P = QFilePathAFP_P PosixPath deriving Show++instance Arbitrary QFilePathAFP_W where+    arbitrary = fmap (QFilePathAFP_W . WS . encodeUtf16LE) arbitraryFilePath+    shrink (QFilePathAFP_W x) = shrinkValid (QFilePathAFP_W . WS . encodeUtf16LE) id (decodeUtf16LE . getWindowsString $ x)++instance Arbitrary QFilePathAFP_P where+    arbitrary = fmap (QFilePathAFP_P . PS . encodeUtf8) arbitraryFilePath+    shrink (QFilePathAFP_P x) = shrinkValid (QFilePathAFP_P . PS . encodeUtf8) id (decodeUtf8 . getPosixString $ x)++newtype QFilePathsAFP_W = QFilePathsAFP_W [WindowsPath] deriving Show+newtype QFilePathsAFP_P = QFilePathsAFP_P [PosixPath] deriving Show++instance Arbitrary QFilePathsAFP_W where+    arbitrary = fmap (QFilePathsAFP_W . fmap (WS . encodeUtf16LE)) (listOf arbitraryFilePath)++instance Arbitrary QFilePathsAFP_P where+    arbitrary = fmap (QFilePathsAFP_P . fmap (PS . encodeUtf8)) (listOf arbitraryFilePath)++instance Arbitrary WindowsChar where+  arbitrary = WW <$> arbitrary++instance Arbitrary PosixChar where+  arbitrary = PW <$> arbitrary+#endif++runTests :: [(String, Property)] -> IO ()+runTests tests = do+    args <- getArgs+    let count   = case args of i:_   -> read i; _ -> 10000+    let testNum = case args of+                    _:i:_+                      | let num = read i+                      , num < 0    -> drop (negate num) tests+                      | let num = read i+                      , num > 0    -> take num          tests+                      | otherwise  -> []+                    _ -> tests+    putStrLn $ "Testing with " ++ show count ++ " repetitions"+    let total' = length testNum+    let showOutput x = show x{output=""} ++ "\n" ++ output x+    bad <- fmap catMaybes $ forM (zip @Integer [1..] testNum) $ \(i,(msg,prop)) -> do+        putStrLn $ "Test " ++ show i ++ " of " ++ show total' ++ ": " ++ msg+        res <- quickCheckWithResult stdArgs{chatty=False, maxSuccess=count} prop+        case res of+            Success{} -> pure Nothing+            bad -> do putStrLn $ showOutput bad; putStrLn "TEST FAILURE!"; pure $ Just (msg,bad)+    if null bad then+        putStrLn $ "Success, " ++ show total' ++ " tests passed"+     else do+        putStrLn $ show (length bad) ++ " FAILURES\n"+        forM_ (zip @Integer [1..] bad) $ \(i,(a,b)) ->+            putStrLn $ "FAILURE " ++ show i ++ ": " ++ a ++ "\n" ++ showOutput b ++ "\n"+        fail $ "FAILURE, failed " ++ show (length bad) ++ " of " ++ show total' ++ " tests"
+ tests/abstract-filepath/Arbitrary.hs view
@@ -0,0 +1,81 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Arbitrary where++import Data.Char+import Data.Maybe+import System.OsString+import System.OsString.Internal.Types+import qualified System.OsString.Posix as Posix+import qualified System.OsString.Windows as Windows+import Data.ByteString ( ByteString )+import qualified Data.ByteString as ByteString+import Test.QuickCheck++import Test.QuickCheck.Checkers++++instance Arbitrary OsString where+  arbitrary = fmap fromJust $ encodeUtf <$> listOf filepathChar++instance EqProp OsString where+  (=-=) = eq++instance Arbitrary PosixString where+  arbitrary = fmap fromJust $ Posix.encodeUtf <$> listOf filepathChar++instance EqProp PosixString where+  (=-=) = eq++instance Arbitrary WindowsString where+  arbitrary = fmap fromJust $ Windows.encodeUtf <$> listOf filepathChar++instance EqProp WindowsString where+  (=-=) = eq+++newtype NonNullString = NonNullString { nonNullString :: String }+  deriving Show++instance Arbitrary NonNullString where+  arbitrary = NonNullString <$> listOf filepathChar++filepathChar :: Gen Char+filepathChar = arbitraryUnicodeChar `suchThat` (\c -> not (isNull c) && isValidUnicode c)+ where+  isNull = (== '\NUL')+  isValidUnicode c = case generalCategory c of+      Surrogate -> False+      NotAssigned -> False+      _ -> True+++newtype NonNullAsciiString = NonNullAsciiString { nonNullAsciiString :: String }+  deriving Show++instance Arbitrary NonNullAsciiString where+  arbitrary = NonNullAsciiString <$> listOf filepathAsciiChar++filepathAsciiChar :: Gen Char+filepathAsciiChar = arbitraryASCIIChar `suchThat` (\c -> not (isNull c))+ where+  isNull = (== '\NUL')++newtype NonNullSurrogateString = NonNullSurrogateString { nonNullSurrogateString :: String }+  deriving Show++instance Arbitrary NonNullSurrogateString where+  arbitrary = NonNullSurrogateString <$> listOf filepathWithSurrogates++filepathWithSurrogates :: Gen Char+filepathWithSurrogates =+  frequency+    [(3, arbitraryASCIIChar),+     (1, arbitraryUnicodeChar),+     (1, arbitraryBoundedEnum)+    ]+++instance Arbitrary ByteString where arbitrary = ByteString.pack <$> arbitrary+instance CoArbitrary ByteString where coarbitrary = coarbitrary . ByteString.unpack
+ tests/abstract-filepath/EncodingSpec.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE TypeApplications #-}++module EncodingSpec where++import Data.ByteString ( ByteString )+import qualified Data.ByteString as BS++import Arbitrary+import Test.QuickCheck++import Data.Either ( isRight )+import qualified System.OsPath.Data.ByteString.Short as BS8+import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16+import System.OsPath.Encoding.Internal+import GHC.IO (unsafePerformIO)+import GHC.IO.Encoding ( setFileSystemEncoding )+import System.IO+    ( utf16le )+import Control.Exception+import Control.DeepSeq+import Data.Bifunctor ( first )+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+++tests :: [(String, Property)]+tests =+  [ ("ucs2le_decode . ucs2le_encode == id",+    property $ \(padEven -> ba) ->+      let decoded = decodeWithTE ucs2le (BS8.toShort ba)+          encoded = encodeWithTE ucs2le =<< decoded+      in (BS8.fromShort <$> encoded) === Right ba)+  , ("utf16 doesn't handle invalid surrogate pairs",+     property $+      let str = [toEnum 55296, toEnum 55297]+          encoded = encodeWithTE utf16le str+          decoded = decodeWithTE utf16le =<< encoded+      in decoded === Left (EncodingError "recoverEncode: invalid argument (invalid character)" Nothing))+  , ("ucs2 handles invalid surrogate pairs",+     property $+      let str = [toEnum 55296, toEnum 55297]+          encoded = encodeWithTE ucs2le str+          decoded = decodeWithTE ucs2le =<< encoded+      in decoded === Right str)+  , ("can roundtrip arbitrary bytes through utf-8 (with RoundtripFailure)",+     property $+      \bs ->+        let decoded = decodeWithTE (mkUTF8 RoundtripFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF8 RoundtripFailure) =<< decoded+        in (either (const 0) BS8.length encoded, encoded) === (BS8.length (BS8.toShort bs), Right (BS8.toShort bs)))++  , ("can decode arbitrary strings through utf-8 (with RoundtripFailure)",+     property $+      \(NonNullSurrogateString str) ->+        let encoded = encodeWithTE (mkUTF8 RoundtripFailure) str+            decoded = decodeWithTE (mkUTF8 RoundtripFailure) =<< encoded+        in expectFailure $ (either (const 0) length decoded, decoded) === (length str, Right str))++  , ("utf-8 roundtrip encode cannot deal with some surrogates",+     property $+      let str = [toEnum 0xDFF0, toEnum 0xDFF2]+          encoded = encodeWithTE (mkUTF8 RoundtripFailure) str+          decoded = decodeWithTE (mkUTF8 RoundtripFailure) =<< encoded+      in decoded === Left (EncodingError "recoverEncode: invalid argument (invalid character)" Nothing))++  , ("cannot roundtrip arbitrary bytes through utf-16 (with RoundtripFailure)",+     property $+      \(padEven -> bs) ->+        let decoded = decodeWithTE (mkUTF16le RoundtripFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF16le RoundtripFailure) =<< decoded+        in expectFailure $ (either (const 0) BS8.length encoded, encoded) === (BS8.length (BS8.toShort bs), Right (BS8.toShort bs)))+  , ("encodeWithTE/decodeWithTE ErrorOnCodingFailure fails (utf16le)",+     property $+      \(padEven -> bs) ->+        let decoded = decodeWithTE (mkUTF16le ErrorOnCodingFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF16le ErrorOnCodingFailure) =<< decoded+        in expectFailure $ (isRight encoded, isRight decoded) === (True, True))+  , ("encodeWithTE/decodeWithTE ErrorOnCodingFailure fails (utf8)",+     property $+      \bs ->+        let decoded = decodeWithTE (mkUTF8 ErrorOnCodingFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF8 ErrorOnCodingFailure) =<< decoded+        in expectFailure $ (isRight encoded, isRight decoded) === (True, True))+  , ("encodeWithTE/decodeWithTE TransliterateCodingFailure never fails (utf16le)",+     property $+      \(padEven -> bs) ->+        let decoded = decodeWithTE (mkUTF16le TransliterateCodingFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF16le TransliterateCodingFailure) =<< decoded+        in (isRight encoded, isRight decoded) === (True, True))+  , ("encodeWithTE/decodeWithTE TransliterateCodingFailure never fails (utf8)",+     property $+      \bs ->+        let decoded = decodeWithTE (mkUTF8 TransliterateCodingFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF8 TransliterateCodingFailure) =<< decoded+        in (isRight encoded, isRight decoded) === (True, True))+  , ("encodeWithBaseWindows/decodeWithBaseWindows never fails (utf16le)",+     property $+      \(padEven -> bs) ->+        let decoded = decodeW' (BS8.toShort bs)+            encoded = encodeW' =<< decoded+        in (isRight encoded, isRight decoded) === (True, True))+  , ("encodeWithBasePosix/decodeWithBasePosix never fails (utf8b)",+     property $+      \bs -> ioProperty $ do+        setFileSystemEncoding (mkUTF8 TransliterateCodingFailure)+        let decoded = decodeP' (BS8.toShort bs)+            encoded = encodeP' =<< decoded+        pure $ (isRight encoded, isRight decoded) === (True, True))++  , ("decodeWithBaseWindows == utf16le_b",+     property $+      \(BS8.toShort . padEven -> bs) ->+        let decoded  = decodeW' bs+            decoded' = first displayException $ decodeWithTE (mkUTF16le_b ErrorOnCodingFailure) bs+        in decoded === decoded')++  , ("encodeWithBaseWindows == utf16le_b",+     property $+      \(NonNullSurrogateString str) ->+        let decoded  = encodeW' str+            decoded' = first displayException $ encodeWithTE (mkUTF16le_b ErrorOnCodingFailure) str+        in decoded === decoded')++  , ("encodeWithTE/decodeWithTE never fails (utf16le_b)",+     property $+      \(padEven -> bs) ->+        let decoded = decodeWithTE (mkUTF16le_b ErrorOnCodingFailure) (BS8.toShort bs)+            encoded = encodeWithTE (mkUTF16le_b ErrorOnCodingFailure) =<< decoded+        in (isRight encoded, isRight decoded) === (True, True))+  ]+++padEven :: ByteString -> ByteString+padEven bs+  | even (BS.length bs) = bs+  | otherwise = bs `BS.append` BS.pack [70]+++decodeP' :: BS8.ShortByteString -> Either String String+decodeP' ba = unsafePerformIO $ do+  r <- try @SomeException $ decodeWithBasePosix ba+  evaluate $ force $ first displayException r++encodeP' :: String -> Either String BS8.ShortByteString+encodeP' str = unsafePerformIO $ do+  r <- try @SomeException $ encodeWithBasePosix str+  evaluate $ force $ first displayException r++decodeW' :: BS16.ShortByteString -> Either String String+decodeW' ba = unsafePerformIO $ do+  r <- try @SomeException $ decodeWithBaseWindows ba+  evaluate $ force $ first displayException r++encodeW' :: String -> Either String BS8.ShortByteString+encodeW' str = unsafePerformIO $ do+  r <- try @SomeException $ encodeWithBaseWindows str+  evaluate $ force $ first displayException r+
+ tests/abstract-filepath/OsPathSpec.hs view
@@ -0,0 +1,259 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE QuasiQuotes #-}++module OsPathSpec where++import Data.Maybe++import System.OsPath as OSP+import System.OsString.Internal.Types+import System.OsPath.Posix as Posix+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 Control.Exception+import Data.ByteString ( ByteString )+import qualified Data.ByteString as BS+import Test.QuickCheck+import Test.QuickCheck.Checkers+import qualified Test.QuickCheck.Classes as QC+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import GHC.IO.Encoding ( setFileSystemEncoding )+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import Control.DeepSeq+import Data.Bifunctor ( first )+import qualified Data.ByteString.Char8 as C+import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16+import qualified System.OsPath.Data.ByteString.Short as SBS+import Data.Char ( ord )++import Arbitrary+++fromRight :: b -> Either a b -> b+fromRight _ (Right b) = b+fromRight b _         = b+++tests :: [(String, Property)]+tests =+  [ ("OSP.encodeUtf . OSP.decodeUtf == id",+    property $ \(NonNullString str) -> (OSP.decodeUtf . fromJust . OSP.encodeUtf) str == Just str)++  , ("decodeUtf . encodeUtf == id (Posix)",+    property $ \(NonNullString str) -> (Posix.decodeUtf . fromJust . Posix.encodeUtf) str == Just str)+  , ("decodeUtf . encodeUtf == id (Windows)",+    property $ \(NonNullString str) -> (Windows.decodeUtf . fromJust . Windows.encodeUtf) str == Just str)++  , ("encodeWith ucs2le . decodeWith ucs2le == id (Posix)",+    property $ \(padEven -> bs) -> (Posix.encodeWith ucs2le . (\(Right r) -> r) . Posix.decodeWith ucs2le . OS.PS . toShort) bs === Right (OS.PS . toShort $ bs))+  , ("encodeWith ucs2le . decodeWith ucs2le == id (Windows)",+    property $ \(padEven -> bs) -> (Windows.encodeWith ucs2le . (\(Right r) -> r) . Windows.decodeWith ucs2le . OS.WS . toShort) bs+           === Right (OS.WS . toShort $ bs))++  , ("decodeFS . encodeFS == id (Posix)",+    property $ \(NonNullString str) -> ioProperty $ do+      setFileSystemEncoding (mkUTF8 TransliterateCodingFailure)+      r1 <- Posix.encodeFS str+      r2 <- try @SomeException $ Posix.decodeFS r1+      r3 <- evaluate $ force $ first displayException r2+      pure (r3 === Right str)+      )+  , ("decodeFS . encodeFS == id (Windows)",+    property $ \(NonNullString str) -> ioProperty $ do+      r1 <- Windows.encodeFS str+      r2 <- try @SomeException $ Windows.decodeFS r1+      r3 <- evaluate $ force $ first displayException r2+      pure (r3 === Right str)+      )++  , ("fromPlatformString* functions are equivalent under ASCII (Windows)",+    property $ \(WindowsString . BS16.pack . map (fromIntegral . ord) . nonNullAsciiString -> str) -> ioProperty $ do+      r1         <- Windows.decodeFS str+      r2         <- Windows.decodeUtf str+      (Right r3) <- pure $ Windows.decodeWith (mkUTF16le TransliterateCodingFailure) str+      (Right r4) <- pure $ Windows.decodeWith (mkUTF16le RoundtripFailure) str+      (Right r5) <- pure $ Windows.decodeWith (mkUTF16le ErrorOnCodingFailure) str+      pure (    r1 === r2+           .&&. r1 === r3+           .&&. r1 === r4+           .&&. r1 === r5+           )+    )++  , ("fromPlatformString* functions are equivalent under ASCII (Posix)",+    property $ \(PosixString . SBS.toShort . C.pack . nonNullAsciiString -> str) -> ioProperty $ do+      r1         <-        Posix.decodeFS str+      r2         <-        Posix.decodeUtf str+      (Right r3) <- pure $ Posix.decodeWith (mkUTF8 TransliterateCodingFailure) str+      (Right r4) <- pure $ Posix.decodeWith (mkUTF8 RoundtripFailure) str+      (Right r5) <- pure $ Posix.decodeWith (mkUTF8 ErrorOnCodingFailure) str+      pure (    r1 === r2+           .&&. r1 === r3+           .&&. r1 === r4+           .&&. r1 === r5+           )+    )++  , ("toPlatformString* functions are equivalent under ASCII (Windows)",+    property $ \(NonNullAsciiString str) -> ioProperty $ do+      r1         <- Windows.encodeFS str+      r2         <- Windows.encodeUtf str+      (Right r3) <- pure $ Windows.encodeWith (mkUTF16le TransliterateCodingFailure) str+      (Right r4) <- pure $ Windows.encodeWith (mkUTF16le RoundtripFailure) str+      (Right r5) <- pure $ Windows.encodeWith (mkUTF16le ErrorOnCodingFailure) str+      pure (    r1 === r2+           .&&. r1 === r3+           .&&. r1 === r4+           .&&. r1 === r5+           )+    )++  , ("toPlatformString* functions are equivalent under ASCII (Posix)",+    property $ \(NonNullAsciiString str) -> ioProperty $ do+      r1         <-        Posix.encodeFS str+      r2         <-        Posix.encodeUtf str+      (Right r3) <- pure $ Posix.encodeWith (mkUTF8 TransliterateCodingFailure) str+      (Right r4) <- pure $ Posix.encodeWith (mkUTF8 RoundtripFailure) str+      (Right r5) <- pure $ Posix.encodeWith (mkUTF8 ErrorOnCodingFailure) str+      pure (    r1 === r2+           .&&. r1 === r3+           .&&. r1 === r4+           .&&. r1 === r5+           )+    )+  , ("Unit test toPlatformString* (Posix)",+    property $ ioProperty $ do+      let str = "ABcK_(ツ123_&**"+      let expected = PosixString $ SBS.pack [0x41,0x42,0x63,0x4b,0x5f,0x28,0xe3,0x83,0x84,0x31,0x32,0x33,0x5f,0x26,0x2a,0x2a]+      r1         <-        Posix.encodeFS str+      r2         <-        Posix.encodeUtf str+      (Right r3) <- pure $ Posix.encodeWith (mkUTF8 TransliterateCodingFailure) str+      (Right r4) <- pure $ Posix.encodeWith (mkUTF8 RoundtripFailure) str+      (Right r5) <- pure $ Posix.encodeWith (mkUTF8 ErrorOnCodingFailure) str+      pure (    r1 === expected+           .&&. r2 === expected+           .&&. r3 === expected+           .&&. r4 === expected+           .&&. r5 === expected+           )+    )+  , ("Unit test toPlatformString* (WindowsString)",+    property $ ioProperty $ do+      let str = "ABcK_(ツ123_&**"+      let expected = WindowsString $ BS16.pack [0x0041,0x0042,0x0063,0x004b,0x005f,0x0028,0x30c4,0x0031,0x0032,0x0033,0x005f,0x0026,0x002a,0x002a]+      r1         <-        Windows.encodeFS str+      r2         <-        Windows.encodeUtf str+      (Right r3) <- pure $ Windows.encodeWith (mkUTF16le TransliterateCodingFailure) str+      (Right r4) <- pure $ Windows.encodeWith (mkUTF16le RoundtripFailure) str+      (Right r5) <- pure $ Windows.encodeWith (mkUTF16le ErrorOnCodingFailure) str+      pure (    r1 === expected+           .&&. r2 === expected+           .&&. r3 === expected+           .&&. r4 === expected+           .&&. r5 === expected+           )+    )++  , ("Unit test fromPlatformString* (Posix)",+    property $ ioProperty $ do+      let bs = PosixString $ SBS.pack [0x41,0x42,0x63,0x4b,0x5f,0x28,0xe3,0x83,0x84,0x31,0x32,0x33,0x5f,0x26,0x2a,0x2a]+      let expected = "ABcK_(ツ123_&**"+      r1         <-        Posix.decodeFS bs+      r2         <-        Posix.decodeUtf bs+      (Right r3) <- pure $ Posix.decodeWith (mkUTF8 TransliterateCodingFailure) bs+      (Right r4) <- pure $ Posix.decodeWith (mkUTF8 RoundtripFailure) bs+      (Right r5) <- pure $ Posix.decodeWith (mkUTF8 ErrorOnCodingFailure) bs+      pure (    r1 === expected+           .&&. r2 === expected+           .&&. r3 === expected+           .&&. r4 === expected+           .&&. r5 === expected+           )+    )+  , ("Unit test fromPlatformString* (WindowsString)",+    property $ ioProperty $ do+      let bs = WindowsString $ BS16.pack [0x0041,0x0042,0x0063,0x004b,0x005f,0x0028,0x30c4,0x0031,0x0032,0x0033,0x005f,0x0026,0x002a,0x002a]+      let expected = "ABcK_(ツ123_&**"+      r1         <-        Windows.decodeFS bs+      r2         <-        Windows.decodeUtf bs+      (Right r3) <- pure $ Windows.decodeWith (mkUTF16le TransliterateCodingFailure) bs+      (Right r4) <- pure $ Windows.decodeWith (mkUTF16le RoundtripFailure) bs+      (Right r5) <- pure $ Windows.decodeWith (mkUTF16le ErrorOnCodingFailure) bs+      pure (    r1 === expected+           .&&. r2 === expected+           .&&. r3 === expected+           .&&. r4 === expected+           .&&. r5 === expected+           )+    )+  , ("QuasiQuoter (WindowsString)",+    property $ do+      let bs = WindowsString $ BS16.pack [0x0041,0x0042,0x0063,0x004b,0x005f,0x0028,0x30c4,0x0031,0x0032,0x0033,0x005f,0x0026,0x002a,0x002a]+      let expected = [WindowsS.pstr|ABcK_(ツ123_&**|]+      bs === expected+    )+  , ("QuasiQuoter (PosixString)",+    property $ do+      let bs = PosixString $ SBS.pack [0x41,0x42,0x63,0x4b,0x5f,0x28,0xe3,0x83,0x84,0x31,0x32,0x33,0x5f,0x26,0x2a,0x2a]+      let expected = [PosixS.pstr|ABcK_(ツ123_&**|]+      bs === expected+    )+  , ("QuasiQuoter (WindowsPath)",+    property $ do+      let bs = WindowsString $ BS16.pack [0x0041,0x0042,0x0063,0x004b,0x005f]+      let expected = [Windows.pstr|ABcK_|]+      bs === expected+    )+  , ("QuasiQuoter (PosixPath)",+    property $ do+      let bs = PosixString $ SBS.pack [0x41,0x42,0x63,0x4b,0x5f]+      let expected = [Posix.pstr|ABcK_|]+      bs === expected+    )++  , ("pack . unpack == id (Windows)",+    property $ \ws@(WindowsString _) ->+      Windows.pack (Windows.unpack ws) === ws+    )+  , ("pack . unpack == id (Posix)",+    property $ \ws@(PosixString _) ->+      Posix.pack (Posix.unpack ws) === ws+    )+  , ("pack . unpack == id (OsPath)",+    property $ \ws@(OsString _) ->+      OSP.pack (OSP.unpack ws) === ws+    )+++  ] ++ testBatch (QC.ord (\(a :: OsPath) -> pure a))+    ++ testBatch (QC.monoid (undefined :: OsPath))++    ++ testBatch (QC.ord (\(a :: OsString) -> pure a))+    ++ testBatch (QC.monoid (undefined :: OsString))++    ++ testBatch (QC.ord (\(a :: WindowsString) -> pure a))+    ++ testBatch (QC.monoid (undefined :: WindowsString))++    ++ testBatch (QC.ord (\(a :: PosixString) -> pure a))+    ++ testBatch (QC.monoid (undefined :: PosixString))++    ++ testBatch (QC.ord (\(a :: PlatformString) -> pure a))+    ++ testBatch (QC.monoid (undefined :: PlatformString))++-- | Allows to insert a 'TestBatch' into a Spec.+testBatch :: TestBatch -> [(String, Property)]+testBatch (_, tests') = tests'+++padEven :: ByteString -> ByteString+padEven bs+  | even (BS.length bs) = bs+  | otherwise = bs `BS.append` BS.pack [70]
+ tests/abstract-filepath/Test.hs view
@@ -0,0 +1,8 @@+module Main (main) where++import qualified OsPathSpec+import qualified EncodingSpec+import TestUtil++main :: IO ()+main = runTests (EncodingSpec.tests ++ OsPathSpec.tests)
+ tests/bytestring-tests/Main.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE TypeApplications #-}++module Main (main) where++import qualified Properties.ShortByteString as PropSBS+import qualified Properties.ShortByteString.Word16 as PropSBSW16+import TestUtil++main :: IO ()+main = runTests (PropSBS.tests ++ PropSBSW16.tests)
+ tests/bytestring-tests/Properties/Common.hs view
@@ -0,0 +1,418 @@+-- |+-- Module      : Properties.ShortByteString+-- Copyright   : (c) Andrew Lelechenko 2021+-- License     : BSD-style++{-# LANGUAGE CPP #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- We are happy to sacrifice optimizations in exchange for faster compilation,+-- but need to test rewrite rules. As one can check using -ddump-rule-firings,+-- rewrite rules do not fire in -O0 mode, so we use -O1, but disable almost all+-- optimizations. It roughly halves compilation time.+{-# OPTIONS_GHC -O1 -fenable-rewrite-rules+  -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 #-}++#ifdef WORD16+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+#else+module Properties.ShortByteString (tests) where+import qualified System.OsPath.Data.ByteString.Short as B+import qualified Data.Char as C+#endif+import Data.ByteString.Short (ShortByteString)++import Data.Word++import Control.Arrow+import Data.Foldable+import Data.List as L+import Data.Semigroup+import Data.Tuple+import Test.QuickCheck+import Test.QuickCheck.Monadic ( monadicIO, run )+import Text.Show.Functions ()++#ifdef WORD16+numWord :: ShortByteString -> Int+numWord = B.numWord16++toElem :: Word16 -> Word16+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++instance CoArbitrary ShortByteString where+  coarbitrary s = coarbitrary (B.unpack s)++#else+_nul :: Word8+_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++toElem :: Word8 -> Word8+toElem = id++swapW :: Word8 -> Word8+swapW = id+++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+  shrink = map B.pack . shrink . B.unpack++instance CoArbitrary ShortByteString where+  coarbitrary s = coarbitrary (B.unpack s)++#endif+++tests :: [(String, Property)]+tests =+  [ ("pack . unpack",+   property $ \x -> x === B.pack (B.unpack x))+  , ("unpack . pack" ,+   property $ \(map toElem -> xs) -> xs === B.unpack (B.pack xs))+  , ("read . show" ,+   property $ \x -> (x :: ShortByteString) === read (show x))++  , ("==" ,+   property $ \x y -> (x == y) === (B.unpack x == B.unpack y))+  , ("== refl" ,+   property $ \x -> (x :: ShortByteString) == x)+  , ("== symm",+   property $ \x y -> ((x :: ShortByteString) == y) === (y == x))+  , ("== pack unpack",+   property $ \x -> x == B.pack (B.unpack x))++  , ("compare",+   property $ \x y -> compare x y === compare (swapW <$> B.unpack x) (swapW <$> B.unpack y))+  , ("compare EQ",+   property $ \x -> compare (x :: ShortByteString) x == EQ)+  , ("compare GT",+   property $ \x (toElem -> c) -> compare (B.snoc x c) x == GT)+  , ("compare LT",+   property $ \x (toElem -> c) -> compare x (B.snoc x c) == LT)+  , ("compare GT empty",+   property $ \x -> not (B.null x) ==> compare x B.empty == GT)+  , ("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)+  , ("compare char" ,+   property $ \(toElem -> c) (toElem -> d) -> compare (swapW c) (swapW d) == compare (B.singleton c) (B.singleton d))+  , ("compare unsigned",+    once $ compare (B.singleton 255) (B.singleton 127) == GT)++  , ("null" ,+   property $ \x -> B.null x === null (B.unpack x))+  , ("empty 0" ,+    once $ numWord B.empty === 0)+  , ("empty []",+    once $ B.unpack B.empty === [])+  , ("mempty 0",+    once $ numWord mempty === 0)+  , ("mempty []",+    once $ B.unpack mempty === [])++  , ("mconcat" ,+   property $ \xs -> B.unpack (mconcat xs) === mconcat (map B.unpack xs))+  , ("mconcat [x,x]" ,+   property $ \x -> B.unpack (mconcat [x, x]) === mconcat [B.unpack x, B.unpack x])+  , ("mconcat [x,[]]" ,+   property $ \x -> B.unpack (mconcat [x, B.empty]) === mconcat [B.unpack x, []])++  , ("null" ,+   property $ \x -> B.null x === null (B.unpack x))+  , ("reverse" ,+   property $ \x -> B.unpack (B.reverse x) === reverse (B.unpack x))+  , ("all" ,+   property $ \f x -> B.all f x === all f (B.unpack x))+  , ("all ==" ,+   property $ \(toElem -> c) x -> B.all (== c) x === all (== c) (B.unpack x))+  , ("any" ,+   property $ \f x -> B.any f x === any f (B.unpack x))+  , ("any ==" ,+   property $ \(toElem -> c) x -> B.any (== c) x === any (== c) (B.unpack x))+  , ("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)+  , ("stimes" ,+   property $ \(Positive n) x -> stimes (n :: Int) (x :: ShortByteString) === mtimesDefault n x)++  , ("break" ,+   property $ \f x -> (B.unpack *** B.unpack) (B.break f x) === break f (B.unpack x))+  , ("break ==" ,+   property $ \(toElem -> c) x -> (B.unpack *** B.unpack) (B.break (== c) x) === break (== c) (B.unpack x))+  , ("break /=" ,+   property $ \(toElem -> c) x -> (B.unpack *** B.unpack) (B.break (/= c) x) === break (/= c) (B.unpack x))+  , ("break span" ,+   property $ \f x -> B.break f x === B.span (not . f) x)+  , ("breakEnd" ,+   property $ \f x -> B.breakEnd f x === swap ((B.reverse *** B.reverse) (B.break f (B.reverse x))))+  , ("breakEnd" ,+   property $ \f x -> B.breakEnd f x === B.spanEnd (not . f) x)+  , ("break isSpace" ,+   property $ \x -> (B.unpack *** B.unpack) (B.break isSpace x) === break isSpace (B.unpack x))++  , ("singleton" ,+   property $ \(toElem -> c) -> B.unpack (B.singleton c) === [c])+  , ("cons" ,+   property $ \(toElem -> c) x -> B.unpack (B.cons c x) === c : B.unpack x)+  , ("cons []" ,+   property $ \(toElem -> c) -> B.unpack (B.cons c B.empty) === [c])+  , ("uncons" ,+   property $ \x -> fmap (second B.unpack) (B.uncons x) === L.uncons (B.unpack x))+  , ("snoc" ,+   property $ \(toElem -> c) x -> B.unpack (B.snoc x c) === B.unpack x ++ [c])+  , ("snoc []" ,+   property $ \(toElem -> c) -> B.unpack (B.snoc B.empty c) === [c])+  , ("unsnoc" ,+   property $ \x -> fmap (first B.unpack) (B.unsnoc x) === unsnoc (B.unpack x))++  , ("drop" ,+   property $ \n x -> B.unpack (B.drop n x) === drop (fromIntegral n) (B.unpack x))+  , ("drop 10" ,+   property $ \x -> B.unpack (B.drop 10 x) === drop 10 (B.unpack x))+  , ("dropWhile" ,+   property $ \f x -> B.unpack (B.dropWhile f x) === dropWhile f (B.unpack x))+  , ("dropWhile ==" ,+   property $ \(toElem -> c) x -> B.unpack (B.dropWhile (== c) x) === dropWhile (== c) (B.unpack x))+  , ("dropWhile /=" ,+   property $ \(toElem -> c) x -> B.unpack (B.dropWhile (/= c) x) === dropWhile (/= c) (B.unpack x))+  , ("dropWhile isSpace" ,+   property $ \x -> B.unpack (B.dropWhile isSpace x) === dropWhile isSpace (B.unpack x))++  , ("take" ,+   property $ \n x -> B.unpack (B.take n x) === take (fromIntegral n) (B.unpack x))+  , ("take 10" ,+   property $ \x -> B.unpack (B.take 10 x) === take 10 (B.unpack x))+  , ("takeWhile" ,+   property $ \f x -> B.unpack (B.takeWhile f x) === takeWhile f (B.unpack x))+  , ("takeWhile ==" ,+   property $ \(toElem -> c) x -> B.unpack (B.takeWhile (== c) x) === takeWhile (== c) (B.unpack x))+  , ("takeWhile /=" ,+   property $ \(toElem -> c) x -> B.unpack (B.takeWhile (/= c) x) === takeWhile (/= c) (B.unpack x))++  , ("takeWhile isSpace" ,+   property $ \x -> B.unpack (B.takeWhile isSpace x) === takeWhile isSpace (B.unpack x))++  , ("dropEnd" ,+   property $ \n x -> B.dropEnd n x === B.take (numWord x - n) x)+  , ("dropWhileEnd" ,+   property $ \f x -> B.dropWhileEnd f x === B.reverse (B.dropWhile f (B.reverse x)))+  , ("takeEnd" ,+   property $ \n x -> B.takeEnd n x === B.drop (numWord x - n) x)+  , ("takeWhileEnd" ,+   property $ \f x -> B.takeWhileEnd f x === B.reverse (B.takeWhile f (B.reverse x)))++  , ("length" ,+   property $ \x -> numWord x === fromIntegral (length (B.unpack x)))+  , ("count" ,+   property $ \(toElem -> c) x -> B.count c x === fromIntegral (length (elemIndices c (B.unpack x))))+  , ("filter" ,+   property $ \f x -> B.unpack (B.filter f x) === filter f (B.unpack x))+  , ("filter compose" ,+   property $ \f g x -> B.filter f (B.filter g x) === B.filter (\c -> f c && g c) x)+  , ("filter ==" ,+   property $ \(toElem -> c) x -> B.unpack (B.filter (== c) x) === filter (== c) (B.unpack x))+  , ("filter /=" ,+   property $ \(toElem -> c) x -> B.unpack (B.filter (/= c) x) === filter (/= c) (B.unpack x))+  , ("partition" ,+   property $ \f x -> (B.unpack *** B.unpack) (B.partition f x) === partition f (B.unpack x))++  , ("find" ,+   property $ \f x -> B.find f x === find f (B.unpack x))+  , ("findIndex" ,+   property $ \f x -> B.findIndex f x === fmap fromIntegral (findIndex f (B.unpack x)))+  , ("findIndices" ,+   property $ \f x -> B.findIndices f x === fmap fromIntegral (findIndices f (B.unpack x)))+  , ("findIndices ==" ,+   property $ \(toElem -> c) x -> B.findIndices (== c) x === fmap fromIntegral (findIndices (== c) (B.unpack x)))++  , ("elem" ,+   property $ \(toElem -> c) x -> B.elem c x === elem c (B.unpack x))+  , ("not elem" ,+   property $ \(toElem -> c) x -> not (B.elem c x) === notElem c (B.unpack x))+  , ("elemIndex" ,+   property $ \(toElem -> c) x -> B.elemIndex c x === fmap fromIntegral (elemIndex c (B.unpack x)))+  , ("elemIndices" ,+   property $ \(toElem -> c) x -> B.elemIndices c x === fmap fromIntegral (elemIndices c (B.unpack x)))+++  , ("map" ,+   property $ \f x -> B.unpack (B.map (toElem . f) x) === map (toElem . f) (B.unpack x))+  , ("map compose" ,+   property $ \f g x -> B.map (toElem . f) (B.map (toElem . g) x) === B.map (toElem . f . toElem . g) x)+  , ("replicate" ,+   property $ \n (toElem -> c) -> B.unpack (B.replicate (fromIntegral n) c) === replicate n c)+  , ("replicate 0" ,+   property $ \(toElem -> c) -> B.unpack (B.replicate 0 c) === replicate 0 c)++  , ("span" ,+   property $ \f x -> (B.unpack *** B.unpack) (B.span f x) === span f (B.unpack x))+  , ("span ==" ,+   property $ \(toElem -> c) x -> (B.unpack *** B.unpack) (B.span (== c) x) === span (== c) (B.unpack x))+  , ("span /=" ,+   property $ \(toElem -> c) x -> (B.unpack *** B.unpack) (B.span (/= c) x) === span (/= c) (B.unpack x))+  , ("spanEnd" ,+   property $ \f x -> B.spanEnd f x === swap ((B.reverse *** B.reverse) (B.span f (B.reverse x))))+  , ("split" ,+   property $ \(toElem -> c) x -> map B.unpack (B.split c x) === split c (B.unpack x))+  , ("split empty" ,+   property $ \(toElem -> c) -> B.split c B.empty === [])+  , ("splitWith" ,+   property $ \f x -> map B.unpack (B.splitWith f x) === splitWith f (B.unpack x))+  , ("splitWith split" ,+   property $ \(toElem -> c) x -> B.splitWith (== c) x === B.split c x)+  , ("splitWith empty" ,+   property $ \f -> B.splitWith f B.empty === [])+  , ("splitWith length" ,+   property $ \f x -> let splits = B.splitWith f x; l1 = fromIntegral (length splits); l2 = numWord (B.filter f x) in+      (l1 == l2 || l1 == l2 + 1) && sum (map numWord splits) + l2 == numWord x)+  , ("splitAt" ,+   property $ \n x -> (B.unpack *** B.unpack) (B.splitAt n x) === splitAt (fromIntegral n) (B.unpack x))++  , ("head" ,+   property $ \x -> not (B.null x) ==> B.head x == head (B.unpack x))+  , ("last" ,+   property $ \x -> not (B.null x) ==> B.last x == last (B.unpack x))+  , ("tail" ,+   property $ \x -> not (B.null x) ==> B.unpack (B.tail x) == tail (B.unpack x))+  , ("tail length" ,+   property $ \x -> not (B.null x) ==> numWord x == 1 + numWord (B.tail x))+  , ("init" ,+   property $ \x -> not (B.null x) ==> B.unpack (B.init x) == init (B.unpack x))+  , ("init length" ,+   property $ \x -> not (B.null x) ==> numWord x == 1 + numWord (B.init x))++  , ("foldl" ,+   property $ \f (toElem -> c) x -> B.foldl ((toElem .) . f) c x === foldl ((toElem .) . f) c (B.unpack x))+  , ("foldl'" ,+   property $ \f (toElem -> c) x -> B.foldl' ((toElem .) . f) c x === foldl' ((toElem .) . f) c (B.unpack x))+  , ("foldr" ,+   property $ \f (toElem -> c) x -> B.foldr ((toElem .) . f) c x === foldr ((toElem .) . f) c (B.unpack x))+  , ("foldr'" ,+   property $ \f (toElem -> c) x -> B.foldr' ((toElem .) . f) c x === foldr' ((toElem .) . f) c (B.unpack x))++  , ("foldl cons" ,+   property $ \x -> B.foldl (flip B.cons) B.empty x === B.reverse x)+  , ("foldr cons" ,+   property $ \x -> B.foldr B.cons B.empty x === x)+  , ("foldl special" ,+   property $ \x (toElem -> c) -> B.unpack (B.foldl (\acc t -> if t == c then acc else B.cons t acc) B.empty x) ===+      foldl (\acc t -> if t == c then acc else t : acc) [] (B.unpack x))+  , ("foldr special" ,+   property $ \x (toElem -> c) -> B.unpack (B.foldr (\t acc -> if t == c then acc else B.cons t acc) B.empty x) ===+      foldr (\t acc -> if t == c then acc else t : acc) [] (B.unpack x))++  , ("foldl1" ,+   property $ \f x -> not (B.null x) ==> B.foldl1 ((toElem .) . f) x == foldl1 ((toElem .) . f) (B.unpack x))+  , ("foldl1'" ,+   property $ \f x -> not (B.null x) ==> B.foldl1' ((toElem .) . f) x == foldl1' ((toElem .) . f) (B.unpack x))+  , ("foldr1" ,+   property $ \f x -> not (B.null x) ==> B.foldr1 ((toElem .) . f) x == foldr1 ((toElem .) . f) (B.unpack x))+  , ("foldr1'", -- there is not Data.List.foldr1'+   property $ \f x -> not (B.null x) ==> B.foldr1' ((toElem .) . f) x == foldr1 ((toElem .) . f) (B.unpack x))++  , ("foldl1 const" ,+   property $ \x -> not (B.null x) ==> B.foldl1 const x == B.head x)+  , ("foldl1 flip const" ,+   property $ \x -> not (B.null x) ==> B.foldl1 (flip const) x == B.last x)+  , ("foldr1 const" ,+   property $ \x -> not (B.null x) ==> B.foldr1 const x == B.head x)+  , ("foldr1 flip const" ,+   property $ \x -> not (B.null x) ==> B.foldr1 (flip const) x == B.last x)+  , ("foldl1 max" ,+   property $ \x -> not (B.null x) ==> B.foldl1 max x == B.foldl max minBound x)+  , ("foldr1 max" ,+   property $ \x -> not (B.null x) ==> B.foldr1 max x == B.foldr max minBound x)++  , ("index" ,+   property $ \(NonNegative n) x -> fromIntegral n < numWord x ==> B.index x (fromIntegral n) == B.unpack x !! n)+  , ("indexMaybe" ,+   property $ \(NonNegative n) x -> fromIntegral n < numWord x ==> B.indexMaybe x (fromIntegral n) == Just (B.unpack x !! n))+  , ("indexMaybe Nothing" ,+   property $ \n x -> (n :: Int) < 0 || fromIntegral n >= numWord x ==> B.indexMaybe x (fromIntegral n) == Nothing)+  , ("!?" ,+   property $ \n x -> B.indexMaybe x (fromIntegral (n :: Int)) === x B.!? (fromIntegral n))++  , ("unfoldrN" ,+   property $ \n f (toElem -> c) -> B.unpack (fst (B.unfoldrN n (fmap (first toElem) . f) c)) ===+      take (fromIntegral n) (unfoldr (fmap (first toElem) . f) c))+  , ("unfoldrN replicate" ,+   property $ \n (toElem -> c) -> fst (B.unfoldrN n (\t -> Just (t, t)) c) === B.replicate n c)+  , ("unfoldr" ,+   property $ \n a (toElem -> c) -> B.unpack (B.unfoldr (\x -> if x <= 100 * n then Just (c, x + 1 :: Int) else Nothing) a) ===+      unfoldr (\x -> if x <= 100 * n then Just (c, x + 1) else Nothing) a)++  --, ("unfoldr" ,+  -- 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+  , ("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+  , ("useAsCString str packCString == str" ,+   property $ \x -> not (B.any (== _nul) x)+      ==> monadicIO $ run (B.useAsCString x B.packCString >>= \x' -> pure (x == x')))+  , ("useAsCStringLen str packCStringLen == str" ,+   property $ \x -> not (B.any (== _nul) x)+      ==> monadicIO $ run (B.useAsCStringLen x B.packCStringLen >>= \x' -> pure (x == x')))+#endif+  ]++split :: Eq a => a -> [a] -> [[a]]+split c = splitWith (== c)++splitWith :: (a -> Bool) -> [a] -> [[a]]+splitWith _ [] = []+splitWith f ys = go [] ys+  where+    go acc [] = [reverse acc]+    go acc (x : xs)+      | f x       = reverse acc : go [] xs+      | otherwise = go (x : acc) xs++unsnoc :: [a] -> Maybe ([a], a)+unsnoc [] = Nothing+unsnoc xs = Just (init xs, last xs)
+ tests/bytestring-tests/Properties/ShortByteString.hs view
@@ -0,0 +1,3 @@+{-# LANGUAGE CPP #-}+#undef WORD16+#include "Common.hs"
+ tests/bytestring-tests/Properties/ShortByteString/Word16.hs view
@@ -0,0 +1,3 @@+{-# LANGUAGE CPP #-}+#define WORD16+#include "../Common.hs"
+ tests/filepath-equivalent-tests/Legacy/System/FilePath.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 704+{-# LANGUAGE Safe #-}+#endif+{- |+Module      :  System.FilePath+Copyright   :  (c) Neil Mitchell 2005-2014+License     :  BSD3++Maintainer  :  ndmitchell@gmail.com+Stability   :  stable+Portability :  portable++A library for 'FilePath' manipulations, using Posix or Windows filepaths+depending on the platform.++Both "System.FilePath.Posix" and "System.FilePath.Windows" provide the+same interface.++Given the example 'FilePath': @\/directory\/file.ext@++We can use the following functions to extract pieces.++* 'takeFileName' gives @\"file.ext\"@++* 'takeDirectory' gives @\"\/directory\"@++* 'takeExtension' gives @\".ext\"@++* 'dropExtension' gives @\"\/directory\/file\"@++* 'takeBaseName' gives @\"file\"@++And we could have built an equivalent path with the following expressions:++* @\"\/directory\" '</>' \"file.ext\"@.++* @\"\/directory\/file" '<.>' \"ext\"@.++* @\"\/directory\/file.txt" '-<.>' \"ext\"@.++Each function in this module is documented with several examples,+which are also used as tests.++Here are a few examples of using the @filepath@ functions together:++/Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:++@['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@++/Example 2:/ Download a file from @url@ and save it to disk:++@do let file = 'makeValid' url+  System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@++/Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:++@'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@++References:+[1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)+-}+++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+module Legacy.System.FilePath(+    -- * Separator predicates+    FilePath,+    pathSeparator, pathSeparators, isPathSeparator,+    searchPathSeparator, isSearchPathSeparator,+    extSeparator, isExtSeparator,++    -- * @$PATH@ methods+    splitSearchPath, getSearchPath,++    -- * Extension functions+    splitExtension,+    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,+    stripExtension,++    -- * Filename\/directory functions+    splitFileName,+    takeFileName, replaceFileName, dropFileName,+    takeBaseName, replaceBaseName,+    takeDirectory, replaceDirectory,+    combine, (</>),+    splitPath, joinPath, splitDirectories,++    -- * Drive functions+    splitDrive, joinDrive,+    takeDrive, hasDrive, dropDrive, isDrive,++    -- * Trailing slash functions+    hasTrailingPathSeparator,+    addTrailingPathSeparator,+    dropTrailingPathSeparator,++    -- * File name manipulations+    normalise, equalFilePath,+    makeRelative,+    isRelative, isAbsolute,+    isValid, makeValid+) where+import Legacy.System.FilePath.Windows+#else+module Legacy.System.FilePath(+    -- * Separator predicates+    FilePath,+    pathSeparator, pathSeparators, isPathSeparator,+    searchPathSeparator, isSearchPathSeparator,+    extSeparator, isExtSeparator,++    -- * @$PATH@ methods+    splitSearchPath, getSearchPath,++    -- * Extension functions+    splitExtension,+    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,+    stripExtension,++    -- * Filename\/directory functions+    splitFileName,+    takeFileName, replaceFileName, dropFileName,+    takeBaseName, replaceBaseName,+    takeDirectory, replaceDirectory,+    combine, (</>),+    splitPath, joinPath, splitDirectories,++    -- * Drive functions+    splitDrive, joinDrive,+    takeDrive, hasDrive, dropDrive, isDrive,++    -- * Trailing slash functions+    hasTrailingPathSeparator,+    addTrailingPathSeparator,+    dropTrailingPathSeparator,++    -- * File name manipulations+    normalise, equalFilePath,+    makeRelative,+    isRelative, isAbsolute,+    isValid, makeValid+) where+import Legacy.System.FilePath.Posix+#endif
+ tests/filepath-equivalent-tests/Legacy/System/FilePath/Posix.hs view
@@ -0,0 +1,1048 @@++++{-# LANGUAGE PatternGuards #-}++-- This template expects CPP definitions for:+--     MODULE_NAME = Posix | Windows+--     IS_WINDOWS  = False | True++-- |+-- Module      :  System.FilePath.MODULE_NAME+-- Copyright   :  (c) Neil Mitchell 2005-2014+-- License     :  BSD3+--+-- Maintainer  :  ndmitchell@gmail.com+-- Stability   :  stable+-- Portability :  portable+--+-- A library for 'FilePath' manipulations, using MODULE_NAME style paths on+-- all platforms. Importing "System.FilePath" is usually better.+--+-- Given the example 'FilePath': @\/directory\/file.ext@+--+-- We can use the following functions to extract pieces.+--+-- * 'takeFileName' gives @\"file.ext\"@+--+-- * 'takeDirectory' gives @\"\/directory\"@+--+-- * 'takeExtension' gives @\".ext\"@+--+-- * 'dropExtension' gives @\"\/directory\/file\"@+--+-- * 'takeBaseName' gives @\"file\"@+--+-- And we could have built an equivalent path with the following expressions:+--+-- * @\"\/directory\" '</>' \"file.ext\"@.+--+-- * @\"\/directory\/file" '<.>' \"ext\"@.+--+-- * @\"\/directory\/file.txt" '-<.>' \"ext\"@.+--+-- Each function in this module is documented with several examples,+-- which are also used as tests.+--+-- Here are a few examples of using the @filepath@ functions together:+--+-- /Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:+--+-- @['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@+--+-- /Example 2:/ Download a file from @url@ and save it to disk:+--+-- @do let file = 'makeValid' url+--   System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@+--+-- /Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:+--+-- @'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@+--+-- References:+-- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)+module Legacy.System.FilePath.Posix+    (+    -- * Separator predicates+    FilePath,+    pathSeparator, pathSeparators, isPathSeparator,+    searchPathSeparator, isSearchPathSeparator,+    extSeparator, isExtSeparator,++    -- * @$PATH@ methods+    splitSearchPath, getSearchPath,++    -- * Extension functions+    splitExtension,+    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,+    stripExtension,++    -- * Filename\/directory functions+    splitFileName,+    takeFileName, replaceFileName, dropFileName,+    takeBaseName, replaceBaseName,+    takeDirectory, replaceDirectory,+    combine, (</>),+    splitPath, joinPath, splitDirectories,++    -- * Drive functions+    splitDrive, joinDrive,+    takeDrive, hasDrive, dropDrive, isDrive,++    -- * Trailing slash functions+    hasTrailingPathSeparator,+    addTrailingPathSeparator,+    dropTrailingPathSeparator,++    -- * File name manipulations+    normalise, equalFilePath,+    makeRelative,+    isRelative, isAbsolute,+    isValid, makeValid+    )+    where++import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)+import Data.Maybe(isJust)+import Data.List(stripPrefix, isSuffixOf)++import System.Environment(getEnv)+++infixr 7  <.>, -<.>+infixr 5  </>++++++---------------------------------------------------------------------+-- Platform Abstraction Methods (private)++-- | Is the operating system Unix or Linux like+isPosix :: Bool+isPosix = not isWindows++-- | Is the operating system Windows like+isWindows :: Bool+isWindows = False+++---------------------------------------------------------------------+-- The basic functions++-- | The character that separates directories. In the case where more than+--   one character is possible, 'pathSeparator' is the \'ideal\' one.+--+-- > Windows: pathSeparator == '\\'+-- > Posix:   pathSeparator ==  '/'+-- > isPathSeparator pathSeparator+pathSeparator :: Char+pathSeparator = if isWindows then '\\' else '/'++-- | The list of all possible separators.+--+-- > Windows: pathSeparators == ['\\', '/']+-- > Posix:   pathSeparators == ['/']+-- > pathSeparator `elem` pathSeparators+pathSeparators :: [Char]+pathSeparators = if isWindows then "\\/" else "/"++-- | Rather than using @(== 'pathSeparator')@, use this. Test if something+--   is a path separator.+--+-- > isPathSeparator a == (a `elem` pathSeparators)+isPathSeparator :: Char -> Bool+isPathSeparator '/' = True+isPathSeparator '\\' = isWindows+isPathSeparator _ = False+++-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- > Windows: searchPathSeparator == ';'+-- > Posix:   searchPathSeparator == ':'+searchPathSeparator :: Char+searchPathSeparator = if isWindows then ';' else ':'++-- | Is the character a file separator?+--+-- > isSearchPathSeparator a == (a == searchPathSeparator)+isSearchPathSeparator :: Char -> Bool+isSearchPathSeparator = (== searchPathSeparator)+++-- | File extension character+--+-- > extSeparator == '.'+extSeparator :: Char+extSeparator = '.'++-- | Is the character an extension character?+--+-- > isExtSeparator a == (a == extSeparator)+isExtSeparator :: Char -> Bool+isExtSeparator = (== extSeparator)+++---------------------------------------------------------------------+-- Path methods (environment $PATH)++-- | Take a string, split it on the 'searchPathSeparator' character.+--   Blank items are ignored on Windows, and converted to @.@ on Posix.+--   On Windows path elements are stripped of quotes.+--+--   Follows the recommendations in+--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>+--+-- > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]+-- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]+-- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]+splitSearchPath :: String -> [FilePath]+splitSearchPath = f+    where+    f xs = case break isSearchPathSeparator xs of+           (pre, []    ) -> g pre+           (pre, _:post) -> g pre ++ f post++    g "" = ["." | isPosix]+    g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]+    g x = [x]+++-- | Get a list of 'FilePath's in the $PATH variable.+getSearchPath :: IO [FilePath]+getSearchPath = fmap splitSearchPath (getEnv "PATH")+++---------------------------------------------------------------------+-- Extension methods++-- | Split on the extension. 'addExtension' is the inverse.+--+-- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")+-- > uncurry (++) (splitExtension x) == x+-- > Valid x => uncurry addExtension (splitExtension x) == x+-- > splitExtension "file.txt" == ("file",".txt")+-- > splitExtension "file" == ("file","")+-- > splitExtension "file/file.txt" == ("file/file",".txt")+-- > splitExtension "file.txt/boris" == ("file.txt/boris","")+-- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")+-- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")+-- > splitExtension "file/path.txt/" == ("file/path.txt/","")+splitExtension :: FilePath -> (String, String)+splitExtension x = case nameDot of+                       "" -> (x,"")+                       _ -> (dir ++ init nameDot, extSeparator : ext)+    where+        (dir,file) = splitFileName_ x+        (nameDot,ext) = breakEnd isExtSeparator file++-- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.+--+-- > takeExtension "/directory/path.ext" == ".ext"+-- > takeExtension x == snd (splitExtension x)+-- > Valid x => takeExtension (addExtension x "ext") == ".ext"+-- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"+takeExtension :: FilePath -> String+takeExtension = snd . splitExtension++-- | Remove the current extension and add another, equivalent to 'replaceExtension'.+--+-- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"+-- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"+-- > "foo.o" -<.> "c" == "foo.c"+(-<.>) :: FilePath -> String -> FilePath+(-<.>) = replaceExtension++-- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.+--+-- > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"+-- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"+-- > replaceExtension "file.txt" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "bob" == "file.bob"+-- > replaceExtension "file" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "" == "file"+-- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"+-- > replaceExtension x y == addExtension (dropExtension x) y+replaceExtension :: FilePath -> String -> FilePath+replaceExtension x y = dropExtension x <.> y++-- | Add an extension, even if there is already one there, equivalent to 'addExtension'.+--+-- > "/directory/path" <.> "ext" == "/directory/path.ext"+-- > "/directory/path" <.> ".ext" == "/directory/path.ext"+(<.>) :: FilePath -> String -> FilePath+(<.>) = addExtension++-- | Remove last extension, and the \".\" preceding it.+--+-- > dropExtension "/directory/path.ext" == "/directory/path"+-- > dropExtension x == fst (splitExtension x)+dropExtension :: FilePath -> FilePath+dropExtension = fst . splitExtension++-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"+addExtension :: FilePath -> String -> FilePath+addExtension file "" = file+addExtension file xs@(x:_) = joinDrive a res+    where+        res = if isExtSeparator x then b ++ xs+              else b ++ [extSeparator] ++ xs++        (a,b) = splitDrive file++-- | Does the given filename have an extension?+--+-- > hasExtension "/directory/path.ext" == True+-- > hasExtension "/directory/path" == False+-- > null (takeExtension x) == not (hasExtension x)+hasExtension :: FilePath -> Bool+hasExtension = any isExtSeparator . takeFileName+++-- | Does the given filename have the specified extension?+--+-- > "png" `isExtensionOf` "/directory/file.png" == True+-- > ".png" `isExtensionOf` "/directory/file.png" == True+-- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True+-- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False+-- > "png" `isExtensionOf` "/directory/file.png.jpg" == False+-- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False+isExtensionOf :: String -> FilePath -> Bool+isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions+isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions++-- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.+--   Returns 'Nothing' if the FilePath does not have the given extension, or+--   'Just' and the part before the extension if it does.+--+--   This function can be more predictable than 'dropExtensions', especially if the filename+--   might itself contain @.@ characters.+--+-- > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"+-- > stripExtension "hi.o" "foo.x.hs.o" == Nothing+-- > dropExtension x == fromJust (stripExtension (takeExtension x) x)+-- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)+-- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"+-- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."+-- > stripExtension "baz"  "foo.bar"  == Nothing+-- > stripExtension "bar"  "foobar"   == Nothing+-- > stripExtension ""     x          == Just x+stripExtension :: String -> FilePath -> Maybe FilePath+stripExtension []        path = Just path+stripExtension ext@(x:_) path = stripSuffix dotExt path+    where dotExt = if isExtSeparator x then ext else '.':ext+++-- | Split on all extensions.+--+-- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+-- > uncurry (++) (splitExtensions x) == x+-- > Valid x => uncurry addExtension (splitExtensions x) == x+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+splitExtensions :: FilePath -> (FilePath, String)+splitExtensions x = (a ++ c, d)+    where+        (a,b) = splitFileName_ x+        (c,d) = break isExtSeparator b++-- | Drop all extensions.+--+-- > dropExtensions "/directory/path.ext" == "/directory/path"+-- > dropExtensions "file.tar.gz" == "file"+-- > not $ hasExtension $ dropExtensions x+-- > not $ any isExtSeparator $ takeFileName $ dropExtensions x+dropExtensions :: FilePath -> FilePath+dropExtensions = fst . splitExtensions++-- | Get all extensions.+--+-- > takeExtensions "/directory/path.ext" == ".ext"+-- > takeExtensions "file.tar.gz" == ".tar.gz"+takeExtensions :: FilePath -> String+takeExtensions = snd . splitExtensions+++-- | Replace all extensions of a file with a new extension. Note+--   that 'replaceExtension' and 'addExtension' both work for adding+--   multiple extensions, so only required when you need to drop+--   all extensions first.+--+-- > replaceExtensions "file.fred.bob" "txt" == "file.txt"+-- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"+replaceExtensions :: FilePath -> String -> FilePath+replaceExtensions x y = dropExtensions x <.> y++++---------------------------------------------------------------------+-- Drive methods++-- | Is the given character a valid drive letter?+-- only a-z and A-Z are letters, not isAlpha which is more unicodey+isLetter :: Char -> Bool+isLetter x = isAsciiLower x || isAsciiUpper x+++-- | Split a path into a drive and a path.+--   On Posix, \/ is a Drive.+--+-- > uncurry (++) (splitDrive x) == x+-- > Windows: splitDrive "file" == ("","file")+-- > Windows: splitDrive "c:/file" == ("c:/","file")+-- > Windows: splitDrive "c:\\file" == ("c:\\","file")+-- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")+-- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")+-- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")+-- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")+-- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")+-- > Windows: splitDrive "/d" == ("","/d")+-- > Posix:   splitDrive "/test" == ("/","test")+-- > Posix:   splitDrive "//test" == ("//","test")+-- > Posix:   splitDrive "test/file" == ("","test/file")+-- > Posix:   splitDrive "file" == ("","file")+splitDrive :: FilePath -> (FilePath, FilePath)+splitDrive x | isPosix = span (== '/') x+splitDrive x | Just y <- readDriveLetter x = y+splitDrive x | Just y <- readDriveUNC x = y+splitDrive x | Just y <- readDriveShare x = y+splitDrive x = ("",x)++addSlash :: FilePath -> FilePath -> (FilePath, FilePath)+addSlash a xs = (a++c,d)+    where (c,d) = span isPathSeparator xs++-- See [1].+-- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"+readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)+readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =+    case map toUpper xs of+        ('U':'N':'C':s4:_) | isPathSeparator s4 ->+            let (a,b) = readDriveShareName (drop 4 xs)+            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)+        _ -> case readDriveLetter xs of+                 -- Extended-length path.+                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)+                 Nothing -> Nothing+readDriveUNC _ = Nothing++{- c:\ -}+readDriveLetter :: String -> Maybe (FilePath, FilePath)+readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)+readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)+readDriveLetter _ = Nothing++{- \\sharename\ -}+readDriveShare :: String -> Maybe (FilePath, FilePath)+readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =+        Just (s1:s2:a,b)+    where (a,b) = readDriveShareName xs+readDriveShare _ = Nothing++{- assume you have already seen \\ -}+{- share\bob -> "share\", "bob" -}+readDriveShareName :: String -> (FilePath, FilePath)+readDriveShareName name = addSlash a b+    where (a,b) = break isPathSeparator name++++-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+-- > Windows: joinDrive "C:" "foo" == "C:foo"+-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"+-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"+-- > Windows: joinDrive "/:" "foo" == "/:\\foo"+joinDrive :: FilePath -> FilePath -> FilePath+joinDrive = combineAlways++-- | Get the drive from a filepath.+--+-- > takeDrive x == fst (splitDrive x)+takeDrive :: FilePath -> FilePath+takeDrive = fst . splitDrive++-- | Delete the drive, if it exists.+--+-- > dropDrive x == snd (splitDrive x)+dropDrive :: FilePath -> FilePath+dropDrive = snd . splitDrive++-- | Does a path have a drive.+--+-- > not (hasDrive x) == null (takeDrive x)+-- > Posix:   hasDrive "/foo" == True+-- > Windows: hasDrive "C:\\foo" == True+-- > Windows: hasDrive "C:foo" == True+-- >          hasDrive "foo" == False+-- >          hasDrive "" == False+hasDrive :: FilePath -> Bool+hasDrive = not . null . takeDrive+++-- | Is an element a drive+--+-- > Posix:   isDrive "/" == True+-- > Posix:   isDrive "/foo" == False+-- > Windows: isDrive "C:\\" == True+-- > Windows: isDrive "C:\\foo" == False+-- >          isDrive "" == False+isDrive :: FilePath -> Bool+isDrive x = not (null x) && null (dropDrive x)+++---------------------------------------------------------------------+-- Operations on a filepath, as a list of directories++-- | Split a filename into directory and file. '</>' is the inverse.+--   The first component will often end with a trailing slash.+--+-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")+-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"+-- > Valid x => isValid (fst (splitFileName x))+-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")+-- > splitFileName "file/" == ("file/", "")+-- > splitFileName "bob" == ("./", "bob")+-- > Posix:   splitFileName "/" == ("/","")+-- > Windows: splitFileName "c:" == ("c:","")+splitFileName :: FilePath -> (String, String)+splitFileName x = (if null dir then "./" else dir, name)+    where+        (dir, name) = splitFileName_ x++-- version of splitFileName where, if the FilePath has no directory+-- component, the returned directory is "" rather than "./".  This+-- is used in cases where we are going to combine the returned+-- directory to make a valid FilePath, and having a "./" appear would+-- look strange and upset simple equality properties.  See+-- e.g. replaceFileName.+splitFileName_ :: FilePath -> (String, String)+splitFileName_ x = (drv ++ dir, file)+    where+        (drv,pth) = splitDrive x+        (dir,file) = breakEnd isPathSeparator pth++-- | Set the filename.+--+-- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"+-- > Valid x => replaceFileName x (takeFileName x) == x+replaceFileName :: FilePath -> String -> FilePath+replaceFileName x y = a </> y where (a,_) = splitFileName_ x++-- | Drop the filename. Unlike 'takeDirectory', this function will leave+--   a trailing path separator on the directory.+--+-- > dropFileName "/directory/file.ext" == "/directory/"+-- > dropFileName x == fst (splitFileName x)+dropFileName :: FilePath -> FilePath+dropFileName = fst . splitFileName+++-- | Get the file name.+--+-- > takeFileName "/directory/file.ext" == "file.ext"+-- > takeFileName "test/" == ""+-- > takeFileName x `isSuffixOf` x+-- > takeFileName x == snd (splitFileName x)+-- > Valid x => takeFileName (replaceFileName x "fred") == "fred"+-- > Valid x => takeFileName (x </> "fred") == "fred"+-- > Valid x => isRelative (takeFileName x)+takeFileName :: FilePath -> FilePath+takeFileName = snd . splitFileName++-- | Get the base name, without an extension or path.+--+-- > takeBaseName "/directory/file.ext" == "file"+-- > takeBaseName "file/test.txt" == "test"+-- > takeBaseName "dave.ext" == "dave"+-- > takeBaseName "" == ""+-- > takeBaseName "test" == "test"+-- > takeBaseName (addTrailingPathSeparator x) == ""+-- > takeBaseName "file/file.tar.gz" == "file.tar"+takeBaseName :: FilePath -> String+takeBaseName = dropExtension . takeFileName++-- | Set the base name.+--+-- > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"+-- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"+-- > replaceBaseName "fred" "bill" == "bill"+-- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"+-- > Valid x => replaceBaseName x (takeBaseName x) == x+replaceBaseName :: FilePath -> String -> FilePath+replaceBaseName pth nam = combineAlways a (nam <.> ext)+    where+        (a,b) = splitFileName_ pth+        ext = takeExtension b++-- | Is an item either a directory or the last character a path separator?+--+-- > hasTrailingPathSeparator "test" == False+-- > hasTrailingPathSeparator "test/" == True+hasTrailingPathSeparator :: FilePath -> Bool+hasTrailingPathSeparator "" = False+hasTrailingPathSeparator x = isPathSeparator (last x)+++hasLeadingPathSeparator :: FilePath -> Bool+hasLeadingPathSeparator "" = False+hasLeadingPathSeparator x = isPathSeparator (head x)+++-- | Add a trailing file path separator if one is not already present.+--+-- > hasTrailingPathSeparator (addTrailingPathSeparator x)+-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x+-- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"+addTrailingPathSeparator :: FilePath -> FilePath+addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]+++-- | Remove any trailing path separators+--+-- > dropTrailingPathSeparator "file/test/" == "file/test"+-- >           dropTrailingPathSeparator "/" == "/"+-- > Windows:  dropTrailingPathSeparator "\\" == "\\"+-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x+dropTrailingPathSeparator :: FilePath -> FilePath+dropTrailingPathSeparator x =+    if hasTrailingPathSeparator x && not (isDrive x)+    then let x' = dropWhileEnd isPathSeparator x+         in if null x' then [last x] else x'+    else x+++-- | Get the directory name, move up one level.+--+-- >           takeDirectory "/directory/other.ext" == "/directory"+-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- >           takeDirectory "foo" == "."+-- >           takeDirectory "/" == "/"+-- >           takeDirectory "/foo" == "/"+-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"+-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"+-- >           takeDirectory "foo/bar/baz" == "foo/bar"+-- > Windows:  takeDirectory "foo\\bar" == "foo"+-- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"+-- > Windows:  takeDirectory "C:\\" == "C:\\"+takeDirectory :: FilePath -> FilePath+takeDirectory = dropTrailingPathSeparator . dropFileName++-- | Set the directory, keeping the filename the same.+--+-- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"+-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x+replaceDirectory :: FilePath -> String -> FilePath+replaceDirectory x dir = combineAlways dir (takeFileName x)+++-- | An alias for '</>'.+combine :: FilePath -> FilePath -> FilePath+combine a b | hasLeadingPathSeparator b || hasDrive b = b+            | otherwise = combineAlways a b++-- | Combine two paths, assuming rhs is NOT absolute.+combineAlways :: FilePath -> FilePath -> FilePath+combineAlways a b | null a = b+                  | null b = a+                  | hasTrailingPathSeparator a = a ++ b+                  | otherwise = case a of+                      [a1,':'] | isWindows && isLetter a1 -> a ++ b+                      _ -> a ++ [pathSeparator] ++ b+++-- | Combine two paths with a path separator.+--   If the second path starts with a path separator or a drive letter, then it returns the second.+--   The intention is that @readFile (dir '</>' file)@ will access the same file as+--   @setCurrentDirectory dir; readFile file@.+--+-- > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"+-- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"+-- >          "directory" </> "/file.ext" == "/file.ext"+-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x+--+--   Combined:+--+-- > Posix:   "/" </> "test" == "/test"+-- > Posix:   "home" </> "bob" == "home/bob"+-- > Posix:   "x:" </> "foo" == "x:/foo"+-- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"+-- > Windows: "home" </> "bob" == "home\\bob"+--+--   Not combined:+--+-- > Posix:   "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "C:\\bob" == "C:\\bob"+--+--   Not combined (tricky):+--+--   On Windows, if a filepath starts with a single slash, it is relative to the+--   root of the current drive. In [1], this is (confusingly) referred to as an+--   absolute path.+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "\\bob" == "\\bob"+-- > Windows: "C:\\home" </> "\\bob" == "\\bob"+--+--   On Windows, from [1]: "If a file name begins with only a disk designator+--   but not the backslash after the colon, it is interpreted as a relative path+--   to the current directory on the drive with the specified letter."+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "D:\\foo" </> "C:bar" == "C:bar"+-- > Windows: "C:\\foo" </> "C:bar" == "C:bar"+(</>) :: FilePath -> FilePath -> FilePath+(</>) = combine+++-- | Split a path by the directory separator.+--+-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]+-- > concat (splitPath x) == x+-- > splitPath "test//item/" == ["test//","item/"]+-- > splitPath "test/item/file" == ["test/","item/","file"]+-- > splitPath "" == []+-- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]+-- > Posix:   splitPath "/file/test" == ["/","file/","test"]+splitPath :: FilePath -> [FilePath]+splitPath x = [drive | drive /= ""] ++ f path+    where+        (drive,path) = splitDrive x++        f "" = []+        f y = (a++c) : f d+            where+                (a,b) = break isPathSeparator y+                (c,d) = span  isPathSeparator b++-- | Just as 'splitPath', but don't add the trailing slashes to each element.+--+-- >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]+-- >          splitDirectories "test/file" == ["test","file"]+-- >          splitDirectories "/test/file" == ["/","test","file"]+-- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]+-- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x+-- >          splitDirectories "" == []+-- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]+-- >          splitDirectories "/test///file" == ["/","test","file"]+splitDirectories :: FilePath -> [FilePath]+splitDirectories = map dropTrailingPathSeparator . splitPath+++-- | Join path elements back together.+--+-- > joinPath a == foldr (</>) "" a+-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"+-- > Valid x => joinPath (splitPath x) == x+-- > joinPath [] == ""+-- > Posix: joinPath ["test","file","path"] == "test/file/path"+joinPath :: [FilePath] -> FilePath+-- Note that this definition on c:\\c:\\, join then split will give c:\\.+joinPath = foldr combine ""+++++++---------------------------------------------------------------------+-- File name manipulators++-- | Equality of two 'FilePath's.+--   If you call @System.Directory.canonicalizePath@+--   first this has a much better chance of working.+--   Note that this doesn't follow symlinks or DOSNAM~1s.+--+-- Similar to 'normalise', this does not expand @".."@, because of symlinks.+--+-- >          x == y ==> equalFilePath x y+-- >          normalise x == normalise y ==> equalFilePath x y+-- >          equalFilePath "foo" "foo/"+-- >          not (equalFilePath "/a/../c" "/c")+-- >          not (equalFilePath "foo" "/foo")+-- > Posix:   not (equalFilePath "foo" "FOO")+-- > Windows: equalFilePath "foo" "FOO"+-- > Windows: not (equalFilePath "C:" "C:/")+equalFilePath :: FilePath -> FilePath -> Bool+equalFilePath a b = f a == f b+    where+        f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x+            | otherwise = dropTrailingPathSeparator $ normalise x+++-- | Contract a filename, based on a relative path. Note that the resulting path+--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@+--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see+--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.+--+--   The corresponding @makeAbsolute@ function can be found in+--   @System.Directory@.+--+-- >          makeRelative "/directory" "/directory/file.ext" == "file.ext"+-- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x+-- >          makeRelative x x == "."+-- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x+-- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"+-- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"+-- > Windows: makeRelative "/Home" "/home/bob" == "bob"+-- > Windows: makeRelative "/" "//" == "//"+-- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"+-- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"+-- > Posix:   makeRelative "/fred" "bob" == "bob"+-- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"+-- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"+-- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"+makeRelative :: FilePath -> FilePath -> FilePath+makeRelative root path+ | equalFilePath root path = "."+ | takeAbs root /= takeAbs path = path+ | otherwise = f (dropAbs root) (dropAbs path)+    where+        f "" y = dropWhile isPathSeparator y+        f x y = let (x1,x2) = g x+                    (y1,y2) = g y+                in if equalFilePath x1 y1 then f x2 y2 else path++        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)+            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x++        -- on windows, need to drop '/' which is kind of absolute, but not a drive+        dropAbs x | hasLeadingPathSeparator x && not (hasDrive x) = tail x+        dropAbs x = dropDrive x++        takeAbs x | hasLeadingPathSeparator x && not (hasDrive x) = [pathSeparator]+        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x++-- | Normalise a file+--+-- * \/\/ outside of the drive can be made blank+--+-- * \/ -> 'pathSeparator'+--+-- * .\/ -> \"\"+--+-- Does not remove @".."@, because of symlinks.+--+-- > Posix:   normalise "/file/\\test////" == "/file/\\test/"+-- > Posix:   normalise "/file/./test" == "/file/test"+-- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"+-- > Posix:   normalise "../bob/fred/" == "../bob/fred/"+-- > Posix:   normalise "/a/../c" == "/a/../c"+-- > Posix:   normalise "./bob/fred/" == "bob/fred/"+-- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"+-- > Windows: normalise "c:\\" == "C:\\"+-- > Windows: normalise "C:.\\" == "C:"+-- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"+-- > Windows: normalise "//server/test" == "\\\\server\\test"+-- > Windows: normalise "c:/file" == "C:\\file"+-- > Windows: normalise "/file" == "\\file"+-- > Windows: normalise "\\" == "\\"+-- > Windows: normalise "/./" == "\\"+-- >          normalise "." == "."+-- > Posix:   normalise "./" == "./"+-- > Posix:   normalise "./." == "./"+-- > Posix:   normalise "/./" == "/"+-- > Posix:   normalise "/" == "/"+-- > Posix:   normalise "bob/fred/." == "bob/fred/"+-- > Posix:   normalise "//home" == "/home"+normalise :: FilePath -> FilePath+normalise path = result ++ [pathSeparator | addPathSeparator]+    where+        (drv,pth) = splitDrive path+        result = joinDrive' (normaliseDrive drv) (f pth)++        joinDrive' "" "" = "."+        joinDrive' d p = joinDrive d p++        addPathSeparator = isDirPath pth+            && not (hasTrailingPathSeparator result)+            && not (isRelativeDrive drv)++        isDirPath xs = hasTrailingPathSeparator xs+            || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)++        f = joinPath . dropDots . propSep . splitDirectories++        propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs+                       | otherwise = x : xs+        propSep [] = []++        dropDots = filter ("." /=)++normaliseDrive :: FilePath -> FilePath+normaliseDrive "" = ""+normaliseDrive _ | isPosix = [pathSeparator]+normaliseDrive drive = if isJust $ readDriveLetter x2+                       then map toUpper x2+                       else x2+    where+        x2 = map repSlash drive++        repSlash x = if isPathSeparator x then pathSeparator else x++-- Information for validity functions on Windows. See [1].+isBadCharacter :: Char -> Bool+isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""++badElements :: [FilePath]+badElements =+    ["CON","PRN","AUX","NUL","CLOCK$"+    ,"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"+    ,"LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"]+++-- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,+--   and invalid characters, but does not check if length limits are exceeded, as these are typically+--   filesystem dependent.+--+-- >          isValid "" == False+-- >          isValid "\0" == False+-- > Posix:   isValid "/random_ path:*" == True+-- > Posix:   isValid x == not (null x)+-- > Windows: isValid "c:\\test" == True+-- > Windows: isValid "c:\\test:of_test" == False+-- > Windows: isValid "test*" == False+-- > Windows: isValid "c:\\test\\nul" == False+-- > Windows: isValid "c:\\test\\prn.txt" == False+-- > Windows: isValid "c:\\nul\\file" == False+-- > Windows: isValid "\\\\" == False+-- > Windows: isValid "\\\\\\foo" == False+-- > Windows: isValid "\\\\?\\D:file" == False+-- > Windows: isValid "foo\tbar" == False+-- > Windows: isValid "nul .txt" == False+-- > Windows: isValid " nul.txt" == True+isValid :: FilePath -> Bool+isValid "" = False+isValid x | '\0' `elem` x = False+isValid _ | isPosix = True+isValid path =+        not (any isBadCharacter x2) &&+        not (any f $ splitDirectories x2) &&+        not (isJust (readDriveShare x1) && all isPathSeparator x1) &&+        not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))+    where+        (x1,x2) = splitDrive path+        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements+++-- | Take a FilePath and make it valid; does not change already valid FilePaths.+--+-- > isValid (makeValid x)+-- > isValid x ==> makeValid x == x+-- > makeValid "" == "_"+-- > makeValid "file\0name" == "file_name"+-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"+-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"+-- > Windows: makeValid "test*" == "test_"+-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"+-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"+-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"+-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"+-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"+-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"+-- > Windows: makeValid "nul .txt" == "nul _.txt"+makeValid :: FilePath -> FilePath+makeValid "" = "_"+makeValid path+        | isPosix = map (\x -> if x == '\0' then '_' else x) path+        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"+        | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =+            makeValid (drv ++ [pathSeparator] ++ pth)+        | otherwise = joinDrive drv $ validElements $ validChars pth+    where+        (drv,pth) = splitDrive path++        validChars = map f+        f x = if isBadCharacter x then '_' else x++        validElements x = joinPath $ map g $ splitPath x+        g x = h a ++ b+            where (a,b) = break isPathSeparator x+        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x+            where (a,b) = splitExtensions x+++-- | Is a path relative, or is it fixed to the root?+--+-- > Windows: isRelative "path\\test" == True+-- > Windows: isRelative "c:\\test" == False+-- > Windows: isRelative "c:test" == True+-- > Windows: isRelative "c:\\" == False+-- > Windows: isRelative "c:/" == False+-- > Windows: isRelative "c:" == True+-- > Windows: isRelative "\\\\foo" == False+-- > Windows: isRelative "\\\\?\\foo" == False+-- > Windows: isRelative "\\\\?\\UNC\\foo" == False+-- > Windows: isRelative "/foo" == True+-- > Windows: isRelative "\\foo" == True+-- > Posix:   isRelative "test/path" == True+-- > Posix:   isRelative "/test" == False+-- > Posix:   isRelative "/" == False+--+-- According to [1]:+--+-- * "A UNC name of any format [is never relative]."+--+-- * "You cannot use the "\\?\" prefix with a relative path."+isRelative :: FilePath -> Bool+isRelative x = null drive || isRelativeDrive drive+    where drive = takeDrive x+++{- c:foo -}+-- From [1]: "If a file name begins with only a disk designator but not the+-- backslash after the colon, it is interpreted as a relative path to the+-- current directory on the drive with the specified letter."+isRelativeDrive :: String -> Bool+isRelativeDrive x =+    maybe False (not . hasTrailingPathSeparator . fst) (readDriveLetter x)+++-- | @not . 'isRelative'@+--+-- > isAbsolute x == not (isRelative x)+isAbsolute :: FilePath -> Bool+isAbsolute = not . isRelative+++-----------------------------------------------------------------------------+-- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])+-- Note that Data.List.dropWhileEnd is only available in base >= 4.5.+dropWhileEnd :: (a -> Bool) -> [a] -> [a]+dropWhileEnd p = reverse . dropWhile p . reverse++-- takeWhileEnd (>2) [1,2,3,4,1,2,3,4] == [3,4])+takeWhileEnd :: (a -> Bool) -> [a] -> [a]+takeWhileEnd p = reverse . takeWhile p . reverse++-- spanEnd (>2) [1,2,3,4,1,2,3,4] = ([1,2,3,4,1,2], [3,4])+spanEnd :: (a -> Bool) -> [a] -> ([a], [a])+spanEnd p xs = (dropWhileEnd p xs, takeWhileEnd p xs)++-- breakEnd (< 2) [1,2,3,4,1,2,3,4] == ([1,2,3,4,1],[2,3,4])+breakEnd :: (a -> Bool) -> [a] -> ([a], [a])+breakEnd p = spanEnd (not . p)++-- | The stripSuffix function drops the given suffix from a list. It returns+-- Nothing if the list did not end with the suffix given, or Just the list+-- before the suffix, if it does.+stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+stripSuffix xs ys = fmap reverse $ stripPrefix (reverse xs) (reverse ys)
+ tests/filepath-equivalent-tests/Legacy/System/FilePath/Windows.hs view
@@ -0,0 +1,1048 @@++++{-# LANGUAGE PatternGuards #-}++-- This template expects CPP definitions for:+--     MODULE_NAME = Posix | Windows+--     IS_WINDOWS  = False | True++-- |+-- Module      :  System.FilePath.MODULE_NAME+-- Copyright   :  (c) Neil Mitchell 2005-2014+-- License     :  BSD3+--+-- Maintainer  :  ndmitchell@gmail.com+-- Stability   :  stable+-- Portability :  portable+--+-- A library for 'FilePath' manipulations, using MODULE_NAME style paths on+-- all platforms. Importing "System.FilePath" is usually better.+--+-- Given the example 'FilePath': @\/directory\/file.ext@+--+-- We can use the following functions to extract pieces.+--+-- * 'takeFileName' gives @\"file.ext\"@+--+-- * 'takeDirectory' gives @\"\/directory\"@+--+-- * 'takeExtension' gives @\".ext\"@+--+-- * 'dropExtension' gives @\"\/directory\/file\"@+--+-- * 'takeBaseName' gives @\"file\"@+--+-- And we could have built an equivalent path with the following expressions:+--+-- * @\"\/directory\" '</>' \"file.ext\"@.+--+-- * @\"\/directory\/file" '<.>' \"ext\"@.+--+-- * @\"\/directory\/file.txt" '-<.>' \"ext\"@.+--+-- Each function in this module is documented with several examples,+-- which are also used as tests.+--+-- Here are a few examples of using the @filepath@ functions together:+--+-- /Example 1:/ Find the possible locations of a Haskell module @Test@ imported from module @Main@:+--+-- @['replaceFileName' path_to_main \"Test\" '<.>' ext | ext <- [\"hs\",\"lhs\"] ]@+--+-- /Example 2:/ Download a file from @url@ and save it to disk:+--+-- @do let file = 'makeValid' url+--   System.Directory.createDirectoryIfMissing True ('takeDirectory' file)@+--+-- /Example 3:/ Compile a Haskell file, putting the @.hi@ file under @interface@:+--+-- @'takeDirectory' file '</>' \"interface\" '</>' ('takeFileName' file '-<.>' \"hi\")@+--+-- References:+-- [1] <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx Naming Files, Paths and Namespaces> (Microsoft MSDN)+module Legacy.System.FilePath.Windows+    (+    -- * Separator predicates+    FilePath,+    pathSeparator, pathSeparators, isPathSeparator,+    searchPathSeparator, isSearchPathSeparator,+    extSeparator, isExtSeparator,++    -- * @$PATH@ methods+    splitSearchPath, getSearchPath,++    -- * Extension functions+    splitExtension,+    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,+    stripExtension,++    -- * Filename\/directory functions+    splitFileName,+    takeFileName, replaceFileName, dropFileName,+    takeBaseName, replaceBaseName,+    takeDirectory, replaceDirectory,+    combine, (</>),+    splitPath, joinPath, splitDirectories,++    -- * Drive functions+    splitDrive, joinDrive,+    takeDrive, hasDrive, dropDrive, isDrive,++    -- * Trailing slash functions+    hasTrailingPathSeparator,+    addTrailingPathSeparator,+    dropTrailingPathSeparator,++    -- * File name manipulations+    normalise, equalFilePath,+    makeRelative,+    isRelative, isAbsolute,+    isValid, makeValid+    )+    where++import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)+import Data.Maybe(isJust)+import Data.List(stripPrefix, isSuffixOf)++import System.Environment(getEnv)+++infixr 7  <.>, -<.>+infixr 5  </>++++++---------------------------------------------------------------------+-- Platform Abstraction Methods (private)++-- | Is the operating system Unix or Linux like+isPosix :: Bool+isPosix = not isWindows++-- | Is the operating system Windows like+isWindows :: Bool+isWindows = True+++---------------------------------------------------------------------+-- The basic functions++-- | The character that separates directories. In the case where more than+--   one character is possible, 'pathSeparator' is the \'ideal\' one.+--+-- > Windows: pathSeparator == '\\'+-- > Posix:   pathSeparator ==  '/'+-- > isPathSeparator pathSeparator+pathSeparator :: Char+pathSeparator = if isWindows then '\\' else '/'++-- | The list of all possible separators.+--+-- > Windows: pathSeparators == ['\\', '/']+-- > Posix:   pathSeparators == ['/']+-- > pathSeparator `elem` pathSeparators+pathSeparators :: [Char]+pathSeparators = if isWindows then "\\/" else "/"++-- | Rather than using @(== 'pathSeparator')@, use this. Test if something+--   is a path separator.+--+-- > isPathSeparator a == (a `elem` pathSeparators)+isPathSeparator :: Char -> Bool+isPathSeparator '/' = True+isPathSeparator '\\' = isWindows+isPathSeparator _ = False+++-- | The character that is used to separate the entries in the $PATH environment variable.+--+-- > Windows: searchPathSeparator == ';'+-- > Posix:   searchPathSeparator == ':'+searchPathSeparator :: Char+searchPathSeparator = if isWindows then ';' else ':'++-- | Is the character a file separator?+--+-- > isSearchPathSeparator a == (a == searchPathSeparator)+isSearchPathSeparator :: Char -> Bool+isSearchPathSeparator = (== searchPathSeparator)+++-- | File extension character+--+-- > extSeparator == '.'+extSeparator :: Char+extSeparator = '.'++-- | Is the character an extension character?+--+-- > isExtSeparator a == (a == extSeparator)+isExtSeparator :: Char -> Bool+isExtSeparator = (== extSeparator)+++---------------------------------------------------------------------+-- Path methods (environment $PATH)++-- | Take a string, split it on the 'searchPathSeparator' character.+--   Blank items are ignored on Windows, and converted to @.@ on Posix.+--   On Windows path elements are stripped of quotes.+--+--   Follows the recommendations in+--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>+--+-- > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]+-- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]+-- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]+-- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]+splitSearchPath :: String -> [FilePath]+splitSearchPath = f+    where+    f xs = case break isSearchPathSeparator xs of+           (pre, []    ) -> g pre+           (pre, _:post) -> g pre ++ f post++    g "" = ["." | isPosix]+    g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]+    g x = [x]+++-- | Get a list of 'FilePath's in the $PATH variable.+getSearchPath :: IO [FilePath]+getSearchPath = fmap splitSearchPath (getEnv "PATH")+++---------------------------------------------------------------------+-- Extension methods++-- | Split on the extension. 'addExtension' is the inverse.+--+-- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")+-- > uncurry (++) (splitExtension x) == x+-- > Valid x => uncurry addExtension (splitExtension x) == x+-- > splitExtension "file.txt" == ("file",".txt")+-- > splitExtension "file" == ("file","")+-- > splitExtension "file/file.txt" == ("file/file",".txt")+-- > splitExtension "file.txt/boris" == ("file.txt/boris","")+-- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")+-- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")+-- > splitExtension "file/path.txt/" == ("file/path.txt/","")+splitExtension :: FilePath -> (String, String)+splitExtension x = case nameDot of+                       "" -> (x,"")+                       _ -> (dir ++ init nameDot, extSeparator : ext)+    where+        (dir,file) = splitFileName_ x+        (nameDot,ext) = breakEnd isExtSeparator file++-- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.+--+-- > takeExtension "/directory/path.ext" == ".ext"+-- > takeExtension x == snd (splitExtension x)+-- > Valid x => takeExtension (addExtension x "ext") == ".ext"+-- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"+takeExtension :: FilePath -> String+takeExtension = snd . splitExtension++-- | Remove the current extension and add another, equivalent to 'replaceExtension'.+--+-- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"+-- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"+-- > "foo.o" -<.> "c" == "foo.c"+(-<.>) :: FilePath -> String -> FilePath+(-<.>) = replaceExtension++-- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.+--+-- > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"+-- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"+-- > replaceExtension "file.txt" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "bob" == "file.bob"+-- > replaceExtension "file" ".bob" == "file.bob"+-- > replaceExtension "file.txt" "" == "file"+-- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"+-- > replaceExtension x y == addExtension (dropExtension x) y+replaceExtension :: FilePath -> String -> FilePath+replaceExtension x y = dropExtension x <.> y++-- | Add an extension, even if there is already one there, equivalent to 'addExtension'.+--+-- > "/directory/path" <.> "ext" == "/directory/path.ext"+-- > "/directory/path" <.> ".ext" == "/directory/path.ext"+(<.>) :: FilePath -> String -> FilePath+(<.>) = addExtension++-- | Remove last extension, and the \".\" preceding it.+--+-- > dropExtension "/directory/path.ext" == "/directory/path"+-- > dropExtension x == fst (splitExtension x)+dropExtension :: FilePath -> FilePath+dropExtension = fst . splitExtension++-- | Add an extension, even if there is already one there, equivalent to '<.>'.+--+-- > addExtension "/directory/path" "ext" == "/directory/path.ext"+-- > addExtension "file.txt" "bib" == "file.txt.bib"+-- > addExtension "file." ".bib" == "file..bib"+-- > addExtension "file" ".bib" == "file.bib"+-- > addExtension "/" "x" == "/.x"+-- > addExtension x "" == x+-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"+-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"+addExtension :: FilePath -> String -> FilePath+addExtension file "" = file+addExtension file xs@(x:_) = joinDrive a res+    where+        res = if isExtSeparator x then b ++ xs+              else b ++ [extSeparator] ++ xs++        (a,b) = splitDrive file++-- | Does the given filename have an extension?+--+-- > hasExtension "/directory/path.ext" == True+-- > hasExtension "/directory/path" == False+-- > null (takeExtension x) == not (hasExtension x)+hasExtension :: FilePath -> Bool+hasExtension = any isExtSeparator . takeFileName+++-- | Does the given filename have the specified extension?+--+-- > "png" `isExtensionOf` "/directory/file.png" == True+-- > ".png" `isExtensionOf` "/directory/file.png" == True+-- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True+-- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False+-- > "png" `isExtensionOf` "/directory/file.png.jpg" == False+-- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False+isExtensionOf :: String -> FilePath -> Bool+isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions+isExtensionOf ext         = isSuffixOf ('.':ext) . takeExtensions++-- | Drop the given extension from a FilePath, and the @\".\"@ preceding it.+--   Returns 'Nothing' if the FilePath does not have the given extension, or+--   'Just' and the part before the extension if it does.+--+--   This function can be more predictable than 'dropExtensions', especially if the filename+--   might itself contain @.@ characters.+--+-- > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"+-- > stripExtension "hi.o" "foo.x.hs.o" == Nothing+-- > dropExtension x == fromJust (stripExtension (takeExtension x) x)+-- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)+-- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"+-- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."+-- > stripExtension "baz"  "foo.bar"  == Nothing+-- > stripExtension "bar"  "foobar"   == Nothing+-- > stripExtension ""     x          == Just x+stripExtension :: String -> FilePath -> Maybe FilePath+stripExtension []        path = Just path+stripExtension ext@(x:_) path = stripSuffix dotExt path+    where dotExt = if isExtSeparator x then ext else '.':ext+++-- | Split on all extensions.+--+-- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+-- > uncurry (++) (splitExtensions x) == x+-- > Valid x => uncurry addExtension (splitExtensions x) == x+-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")+splitExtensions :: FilePath -> (FilePath, String)+splitExtensions x = (a ++ c, d)+    where+        (a,b) = splitFileName_ x+        (c,d) = break isExtSeparator b++-- | Drop all extensions.+--+-- > dropExtensions "/directory/path.ext" == "/directory/path"+-- > dropExtensions "file.tar.gz" == "file"+-- > not $ hasExtension $ dropExtensions x+-- > not $ any isExtSeparator $ takeFileName $ dropExtensions x+dropExtensions :: FilePath -> FilePath+dropExtensions = fst . splitExtensions++-- | Get all extensions.+--+-- > takeExtensions "/directory/path.ext" == ".ext"+-- > takeExtensions "file.tar.gz" == ".tar.gz"+takeExtensions :: FilePath -> String+takeExtensions = snd . splitExtensions+++-- | Replace all extensions of a file with a new extension. Note+--   that 'replaceExtension' and 'addExtension' both work for adding+--   multiple extensions, so only required when you need to drop+--   all extensions first.+--+-- > replaceExtensions "file.fred.bob" "txt" == "file.txt"+-- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"+replaceExtensions :: FilePath -> String -> FilePath+replaceExtensions x y = dropExtensions x <.> y++++---------------------------------------------------------------------+-- Drive methods++-- | Is the given character a valid drive letter?+-- only a-z and A-Z are letters, not isAlpha which is more unicodey+isLetter :: Char -> Bool+isLetter x = isAsciiLower x || isAsciiUpper x+++-- | Split a path into a drive and a path.+--   On Posix, \/ is a Drive.+--+-- > uncurry (++) (splitDrive x) == x+-- > Windows: splitDrive "file" == ("","file")+-- > Windows: splitDrive "c:/file" == ("c:/","file")+-- > Windows: splitDrive "c:\\file" == ("c:\\","file")+-- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")+-- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")+-- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")+-- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")+-- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")+-- > Windows: splitDrive "/d" == ("","/d")+-- > Posix:   splitDrive "/test" == ("/","test")+-- > Posix:   splitDrive "//test" == ("//","test")+-- > Posix:   splitDrive "test/file" == ("","test/file")+-- > Posix:   splitDrive "file" == ("","file")+splitDrive :: FilePath -> (FilePath, FilePath)+splitDrive x | isPosix = span (== '/') x+splitDrive x | Just y <- readDriveLetter x = y+splitDrive x | Just y <- readDriveUNC x = y+splitDrive x | Just y <- readDriveShare x = y+splitDrive x = ("",x)++addSlash :: FilePath -> FilePath -> (FilePath, FilePath)+addSlash a xs = (a++c,d)+    where (c,d) = span isPathSeparator xs++-- See [1].+-- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"+readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)+readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =+    case map toUpper xs of+        ('U':'N':'C':s4:_) | isPathSeparator s4 ->+            let (a,b) = readDriveShareName (drop 4 xs)+            in Just (s1:s2:'?':s3:take 4 xs ++ a, b)+        _ -> case readDriveLetter xs of+                 -- Extended-length path.+                 Just (a,b) -> Just (s1:s2:'?':s3:a,b)+                 Nothing -> Nothing+readDriveUNC _ = Nothing++{- c:\ -}+readDriveLetter :: String -> Maybe (FilePath, FilePath)+readDriveLetter (x:':':y:xs) | isLetter x && isPathSeparator y = Just $ addSlash [x,':'] (y:xs)+readDriveLetter (x:':':xs) | isLetter x = Just ([x,':'], xs)+readDriveLetter _ = Nothing++{- \\sharename\ -}+readDriveShare :: String -> Maybe (FilePath, FilePath)+readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =+        Just (s1:s2:a,b)+    where (a,b) = readDriveShareName xs+readDriveShare _ = Nothing++{- assume you have already seen \\ -}+{- share\bob -> "share\", "bob" -}+readDriveShareName :: String -> (FilePath, FilePath)+readDriveShareName name = addSlash a b+    where (a,b) = break isPathSeparator name++++-- | Join a drive and the rest of the path.+--+-- > Valid x => uncurry joinDrive (splitDrive x) == x+-- > Windows: joinDrive "C:" "foo" == "C:foo"+-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"+-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"+-- > Windows: joinDrive "/:" "foo" == "/:\\foo"+joinDrive :: FilePath -> FilePath -> FilePath+joinDrive = combineAlways++-- | Get the drive from a filepath.+--+-- > takeDrive x == fst (splitDrive x)+takeDrive :: FilePath -> FilePath+takeDrive = fst . splitDrive++-- | Delete the drive, if it exists.+--+-- > dropDrive x == snd (splitDrive x)+dropDrive :: FilePath -> FilePath+dropDrive = snd . splitDrive++-- | Does a path have a drive.+--+-- > not (hasDrive x) == null (takeDrive x)+-- > Posix:   hasDrive "/foo" == True+-- > Windows: hasDrive "C:\\foo" == True+-- > Windows: hasDrive "C:foo" == True+-- >          hasDrive "foo" == False+-- >          hasDrive "" == False+hasDrive :: FilePath -> Bool+hasDrive = not . null . takeDrive+++-- | Is an element a drive+--+-- > Posix:   isDrive "/" == True+-- > Posix:   isDrive "/foo" == False+-- > Windows: isDrive "C:\\" == True+-- > Windows: isDrive "C:\\foo" == False+-- >          isDrive "" == False+isDrive :: FilePath -> Bool+isDrive x = not (null x) && null (dropDrive x)+++---------------------------------------------------------------------+-- Operations on a filepath, as a list of directories++-- | Split a filename into directory and file. '</>' is the inverse.+--   The first component will often end with a trailing slash.+--+-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")+-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"+-- > Valid x => isValid (fst (splitFileName x))+-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")+-- > splitFileName "file/" == ("file/", "")+-- > splitFileName "bob" == ("./", "bob")+-- > Posix:   splitFileName "/" == ("/","")+-- > Windows: splitFileName "c:" == ("c:","")+splitFileName :: FilePath -> (String, String)+splitFileName x = (if null dir then "./" else dir, name)+    where+        (dir, name) = splitFileName_ x++-- version of splitFileName where, if the FilePath has no directory+-- component, the returned directory is "" rather than "./".  This+-- is used in cases where we are going to combine the returned+-- directory to make a valid FilePath, and having a "./" appear would+-- look strange and upset simple equality properties.  See+-- e.g. replaceFileName.+splitFileName_ :: FilePath -> (String, String)+splitFileName_ x = (drv ++ dir, file)+    where+        (drv,pth) = splitDrive x+        (dir,file) = breakEnd isPathSeparator pth++-- | Set the filename.+--+-- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"+-- > Valid x => replaceFileName x (takeFileName x) == x+replaceFileName :: FilePath -> String -> FilePath+replaceFileName x y = a </> y where (a,_) = splitFileName_ x++-- | Drop the filename. Unlike 'takeDirectory', this function will leave+--   a trailing path separator on the directory.+--+-- > dropFileName "/directory/file.ext" == "/directory/"+-- > dropFileName x == fst (splitFileName x)+dropFileName :: FilePath -> FilePath+dropFileName = fst . splitFileName+++-- | Get the file name.+--+-- > takeFileName "/directory/file.ext" == "file.ext"+-- > takeFileName "test/" == ""+-- > takeFileName x `isSuffixOf` x+-- > takeFileName x == snd (splitFileName x)+-- > Valid x => takeFileName (replaceFileName x "fred") == "fred"+-- > Valid x => takeFileName (x </> "fred") == "fred"+-- > Valid x => isRelative (takeFileName x)+takeFileName :: FilePath -> FilePath+takeFileName = snd . splitFileName++-- | Get the base name, without an extension or path.+--+-- > takeBaseName "/directory/file.ext" == "file"+-- > takeBaseName "file/test.txt" == "test"+-- > takeBaseName "dave.ext" == "dave"+-- > takeBaseName "" == ""+-- > takeBaseName "test" == "test"+-- > takeBaseName (addTrailingPathSeparator x) == ""+-- > takeBaseName "file/file.tar.gz" == "file.tar"+takeBaseName :: FilePath -> String+takeBaseName = dropExtension . takeFileName++-- | Set the base name.+--+-- > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"+-- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"+-- > replaceBaseName "fred" "bill" == "bill"+-- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"+-- > Valid x => replaceBaseName x (takeBaseName x) == x+replaceBaseName :: FilePath -> String -> FilePath+replaceBaseName pth nam = combineAlways a (nam <.> ext)+    where+        (a,b) = splitFileName_ pth+        ext = takeExtension b++-- | Is an item either a directory or the last character a path separator?+--+-- > hasTrailingPathSeparator "test" == False+-- > hasTrailingPathSeparator "test/" == True+hasTrailingPathSeparator :: FilePath -> Bool+hasTrailingPathSeparator "" = False+hasTrailingPathSeparator x = isPathSeparator (last x)+++hasLeadingPathSeparator :: FilePath -> Bool+hasLeadingPathSeparator "" = False+hasLeadingPathSeparator x = isPathSeparator (head x)+++-- | Add a trailing file path separator if one is not already present.+--+-- > hasTrailingPathSeparator (addTrailingPathSeparator x)+-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x+-- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"+addTrailingPathSeparator :: FilePath -> FilePath+addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pathSeparator]+++-- | Remove any trailing path separators+--+-- > dropTrailingPathSeparator "file/test/" == "file/test"+-- >           dropTrailingPathSeparator "/" == "/"+-- > Windows:  dropTrailingPathSeparator "\\" == "\\"+-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x+dropTrailingPathSeparator :: FilePath -> FilePath+dropTrailingPathSeparator x =+    if hasTrailingPathSeparator x && not (isDrive x)+    then let x' = dropWhileEnd isPathSeparator x+         in if null x' then [last x] else x'+    else x+++-- | Get the directory name, move up one level.+--+-- >           takeDirectory "/directory/other.ext" == "/directory"+-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."+-- >           takeDirectory "foo" == "."+-- >           takeDirectory "/" == "/"+-- >           takeDirectory "/foo" == "/"+-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"+-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"+-- >           takeDirectory "foo/bar/baz" == "foo/bar"+-- > Windows:  takeDirectory "foo\\bar" == "foo"+-- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"+-- > Windows:  takeDirectory "C:\\" == "C:\\"+takeDirectory :: FilePath -> FilePath+takeDirectory = dropTrailingPathSeparator . dropFileName++-- | Set the directory, keeping the filename the same.+--+-- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"+-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x+replaceDirectory :: FilePath -> String -> FilePath+replaceDirectory x dir = combineAlways dir (takeFileName x)+++-- | An alias for '</>'.+combine :: FilePath -> FilePath -> FilePath+combine a b | hasLeadingPathSeparator b || hasDrive b = b+            | otherwise = combineAlways a b++-- | Combine two paths, assuming rhs is NOT absolute.+combineAlways :: FilePath -> FilePath -> FilePath+combineAlways a b | null a = b+                  | null b = a+                  | hasTrailingPathSeparator a = a ++ b+                  | otherwise = case a of+                      [a1,':'] | isWindows && isLetter a1 -> a ++ b+                      _ -> a ++ [pathSeparator] ++ b+++-- | Combine two paths with a path separator.+--   If the second path starts with a path separator or a drive letter, then it returns the second.+--   The intention is that @readFile (dir '</>' file)@ will access the same file as+--   @setCurrentDirectory dir; readFile file@.+--+-- > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"+-- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"+-- >          "directory" </> "/file.ext" == "/file.ext"+-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x+--+--   Combined:+--+-- > Posix:   "/" </> "test" == "/test"+-- > Posix:   "home" </> "bob" == "home/bob"+-- > Posix:   "x:" </> "foo" == "x:/foo"+-- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"+-- > Windows: "home" </> "bob" == "home\\bob"+--+--   Not combined:+--+-- > Posix:   "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "C:\\bob" == "C:\\bob"+--+--   Not combined (tricky):+--+--   On Windows, if a filepath starts with a single slash, it is relative to the+--   root of the current drive. In [1], this is (confusingly) referred to as an+--   absolute path.+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "home" </> "/bob" == "/bob"+-- > Windows: "home" </> "\\bob" == "\\bob"+-- > Windows: "C:\\home" </> "\\bob" == "\\bob"+--+--   On Windows, from [1]: "If a file name begins with only a disk designator+--   but not the backslash after the colon, it is interpreted as a relative path+--   to the current directory on the drive with the specified letter."+--   The current behavior of '</>' is to never combine these forms.+--+-- > Windows: "D:\\foo" </> "C:bar" == "C:bar"+-- > Windows: "C:\\foo" </> "C:bar" == "C:bar"+(</>) :: FilePath -> FilePath -> FilePath+(</>) = combine+++-- | Split a path by the directory separator.+--+-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]+-- > concat (splitPath x) == x+-- > splitPath "test//item/" == ["test//","item/"]+-- > splitPath "test/item/file" == ["test/","item/","file"]+-- > splitPath "" == []+-- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]+-- > Posix:   splitPath "/file/test" == ["/","file/","test"]+splitPath :: FilePath -> [FilePath]+splitPath x = [drive | drive /= ""] ++ f path+    where+        (drive,path) = splitDrive x++        f "" = []+        f y = (a++c) : f d+            where+                (a,b) = break isPathSeparator y+                (c,d) = span  isPathSeparator b++-- | Just as 'splitPath', but don't add the trailing slashes to each element.+--+-- >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]+-- >          splitDirectories "test/file" == ["test","file"]+-- >          splitDirectories "/test/file" == ["/","test","file"]+-- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]+-- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x+-- >          splitDirectories "" == []+-- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]+-- >          splitDirectories "/test///file" == ["/","test","file"]+splitDirectories :: FilePath -> [FilePath]+splitDirectories = map dropTrailingPathSeparator . splitPath+++-- | Join path elements back together.+--+-- > joinPath a == foldr (</>) "" a+-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"+-- > Valid x => joinPath (splitPath x) == x+-- > joinPath [] == ""+-- > Posix: joinPath ["test","file","path"] == "test/file/path"+joinPath :: [FilePath] -> FilePath+-- Note that this definition on c:\\c:\\, join then split will give c:\\.+joinPath = foldr combine ""+++++++---------------------------------------------------------------------+-- File name manipulators++-- | Equality of two 'FilePath's.+--   If you call @System.Directory.canonicalizePath@+--   first this has a much better chance of working.+--   Note that this doesn't follow symlinks or DOSNAM~1s.+--+-- Similar to 'normalise', this does not expand @".."@, because of symlinks.+--+-- >          x == y ==> equalFilePath x y+-- >          normalise x == normalise y ==> equalFilePath x y+-- >          equalFilePath "foo" "foo/"+-- >          not (equalFilePath "/a/../c" "/c")+-- >          not (equalFilePath "foo" "/foo")+-- > Posix:   not (equalFilePath "foo" "FOO")+-- > Windows: equalFilePath "foo" "FOO"+-- > Windows: not (equalFilePath "C:" "C:/")+equalFilePath :: FilePath -> FilePath -> Bool+equalFilePath a b = f a == f b+    where+        f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x+            | otherwise = dropTrailingPathSeparator $ normalise x+++-- | Contract a filename, based on a relative path. Note that the resulting path+--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@+--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see+--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.+--+--   The corresponding @makeAbsolute@ function can be found in+--   @System.Directory@.+--+-- >          makeRelative "/directory" "/directory/file.ext" == "file.ext"+-- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x+-- >          makeRelative x x == "."+-- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x+-- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"+-- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"+-- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"+-- > Windows: makeRelative "/Home" "/home/bob" == "bob"+-- > Windows: makeRelative "/" "//" == "//"+-- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"+-- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"+-- > Posix:   makeRelative "/fred" "bob" == "bob"+-- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"+-- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"+-- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"+makeRelative :: FilePath -> FilePath -> FilePath+makeRelative root path+ | equalFilePath root path = "."+ | takeAbs root /= takeAbs path = path+ | otherwise = f (dropAbs root) (dropAbs path)+    where+        f "" y = dropWhile isPathSeparator y+        f x y = let (x1,x2) = g x+                    (y1,y2) = g y+                in if equalFilePath x1 y1 then f x2 y2 else path++        g x = (dropWhile isPathSeparator a, dropWhile isPathSeparator b)+            where (a,b) = break isPathSeparator $ dropWhile isPathSeparator x++        -- on windows, need to drop '/' which is kind of absolute, but not a drive+        dropAbs x | hasLeadingPathSeparator x && not (hasDrive x) = tail x+        dropAbs x = dropDrive x++        takeAbs x | hasLeadingPathSeparator x && not (hasDrive x) = [pathSeparator]+        takeAbs x = map (\y -> if isPathSeparator y then pathSeparator else toLower y) $ takeDrive x++-- | Normalise a file+--+-- * \/\/ outside of the drive can be made blank+--+-- * \/ -> 'pathSeparator'+--+-- * .\/ -> \"\"+--+-- Does not remove @".."@, because of symlinks.+--+-- > Posix:   normalise "/file/\\test////" == "/file/\\test/"+-- > Posix:   normalise "/file/./test" == "/file/test"+-- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"+-- > Posix:   normalise "../bob/fred/" == "../bob/fred/"+-- > Posix:   normalise "/a/../c" == "/a/../c"+-- > Posix:   normalise "./bob/fred/" == "bob/fred/"+-- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"+-- > Windows: normalise "c:\\" == "C:\\"+-- > Windows: normalise "C:.\\" == "C:"+-- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"+-- > Windows: normalise "//server/test" == "\\\\server\\test"+-- > Windows: normalise "c:/file" == "C:\\file"+-- > Windows: normalise "/file" == "\\file"+-- > Windows: normalise "\\" == "\\"+-- > Windows: normalise "/./" == "\\"+-- >          normalise "." == "."+-- > Posix:   normalise "./" == "./"+-- > Posix:   normalise "./." == "./"+-- > Posix:   normalise "/./" == "/"+-- > Posix:   normalise "/" == "/"+-- > Posix:   normalise "bob/fred/." == "bob/fred/"+-- > Posix:   normalise "//home" == "/home"+normalise :: FilePath -> FilePath+normalise path = result ++ [pathSeparator | addPathSeparator]+    where+        (drv,pth) = splitDrive path+        result = joinDrive' (normaliseDrive drv) (f pth)++        joinDrive' "" "" = "."+        joinDrive' d p = joinDrive d p++        addPathSeparator = isDirPath pth+            && not (hasTrailingPathSeparator result)+            && not (isRelativeDrive drv)++        isDirPath xs = hasTrailingPathSeparator xs+            || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)++        f = joinPath . dropDots . propSep . splitDirectories++        propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs+                       | otherwise = x : xs+        propSep [] = []++        dropDots = filter ("." /=)++normaliseDrive :: FilePath -> FilePath+normaliseDrive "" = ""+normaliseDrive _ | isPosix = [pathSeparator]+normaliseDrive drive = if isJust $ readDriveLetter x2+                       then map toUpper x2+                       else x2+    where+        x2 = map repSlash drive++        repSlash x = if isPathSeparator x then pathSeparator else x++-- Information for validity functions on Windows. See [1].+isBadCharacter :: Char -> Bool+isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""++badElements :: [FilePath]+badElements =+    ["CON","PRN","AUX","NUL","CLOCK$"+    ,"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9"+    ,"LPT1","LPT2","LPT3","LPT4","LPT5","LPT6","LPT7","LPT8","LPT9"]+++-- | Is a FilePath valid, i.e. could you create a file like it? This function checks for invalid names,+--   and invalid characters, but does not check if length limits are exceeded, as these are typically+--   filesystem dependent.+--+-- >          isValid "" == False+-- >          isValid "\0" == False+-- > Posix:   isValid "/random_ path:*" == True+-- > Posix:   isValid x == not (null x)+-- > Windows: isValid "c:\\test" == True+-- > Windows: isValid "c:\\test:of_test" == False+-- > Windows: isValid "test*" == False+-- > Windows: isValid "c:\\test\\nul" == False+-- > Windows: isValid "c:\\test\\prn.txt" == False+-- > Windows: isValid "c:\\nul\\file" == False+-- > Windows: isValid "\\\\" == False+-- > Windows: isValid "\\\\\\foo" == False+-- > Windows: isValid "\\\\?\\D:file" == False+-- > Windows: isValid "foo\tbar" == False+-- > Windows: isValid "nul .txt" == False+-- > Windows: isValid " nul.txt" == True+isValid :: FilePath -> Bool+isValid "" = False+isValid x | '\0' `elem` x = False+isValid _ | isPosix = True+isValid path =+        not (any isBadCharacter x2) &&+        not (any f $ splitDirectories x2) &&+        not (isJust (readDriveShare x1) && all isPathSeparator x1) &&+        not (isJust (readDriveUNC x1) && not (hasTrailingPathSeparator x1))+    where+        (x1,x2) = splitDrive path+        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements+++-- | Take a FilePath and make it valid; does not change already valid FilePaths.+--+-- > isValid (makeValid x)+-- > isValid x ==> makeValid x == x+-- > makeValid "" == "_"+-- > makeValid "file\0name" == "file_name"+-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"+-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"+-- > Windows: makeValid "test*" == "test_"+-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"+-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"+-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"+-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"+-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"+-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"+-- > Windows: makeValid "nul .txt" == "nul _.txt"+makeValid :: FilePath -> FilePath+makeValid "" = "_"+makeValid path+        | isPosix = map (\x -> if x == '\0' then '_' else x) path+        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"+        | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =+            makeValid (drv ++ [pathSeparator] ++ pth)+        | otherwise = joinDrive drv $ validElements $ validChars pth+    where+        (drv,pth) = splitDrive path++        validChars = map f+        f x = if isBadCharacter x then '_' else x++        validElements x = joinPath $ map g $ splitPath x+        g x = h a ++ b+            where (a,b) = break isPathSeparator x+        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x+            where (a,b) = splitExtensions x+++-- | Is a path relative, or is it fixed to the root?+--+-- > Windows: isRelative "path\\test" == True+-- > Windows: isRelative "c:\\test" == False+-- > Windows: isRelative "c:test" == True+-- > Windows: isRelative "c:\\" == False+-- > Windows: isRelative "c:/" == False+-- > Windows: isRelative "c:" == True+-- > Windows: isRelative "\\\\foo" == False+-- > Windows: isRelative "\\\\?\\foo" == False+-- > Windows: isRelative "\\\\?\\UNC\\foo" == False+-- > Windows: isRelative "/foo" == True+-- > Windows: isRelative "\\foo" == True+-- > Posix:   isRelative "test/path" == True+-- > Posix:   isRelative "/test" == False+-- > Posix:   isRelative "/" == False+--+-- According to [1]:+--+-- * "A UNC name of any format [is never relative]."+--+-- * "You cannot use the "\\?\" prefix with a relative path."+isRelative :: FilePath -> Bool+isRelative x = null drive || isRelativeDrive drive+    where drive = takeDrive x+++{- c:foo -}+-- From [1]: "If a file name begins with only a disk designator but not the+-- backslash after the colon, it is interpreted as a relative path to the+-- current directory on the drive with the specified letter."+isRelativeDrive :: String -> Bool+isRelativeDrive x =+    maybe False (not . hasTrailingPathSeparator . fst) (readDriveLetter x)+++-- | @not . 'isRelative'@+--+-- > isAbsolute x == not (isRelative x)+isAbsolute :: FilePath -> Bool+isAbsolute = not . isRelative+++-----------------------------------------------------------------------------+-- dropWhileEnd (>2) [1,2,3,4,1,2,3,4] == [1,2,3,4,1,2])+-- Note that Data.List.dropWhileEnd is only available in base >= 4.5.+dropWhileEnd :: (a -> Bool) -> [a] -> [a]+dropWhileEnd p = reverse . dropWhile p . reverse++-- takeWhileEnd (>2) [1,2,3,4,1,2,3,4] == [3,4])+takeWhileEnd :: (a -> Bool) -> [a] -> [a]+takeWhileEnd p = reverse . takeWhile p . reverse++-- spanEnd (>2) [1,2,3,4,1,2,3,4] = ([1,2,3,4,1,2], [3,4])+spanEnd :: (a -> Bool) -> [a] -> ([a], [a])+spanEnd p xs = (dropWhileEnd p xs, takeWhileEnd p xs)++-- breakEnd (< 2) [1,2,3,4,1,2,3,4] == ([1,2,3,4,1],[2,3,4])+breakEnd :: (a -> Bool) -> [a] -> ([a], [a])+breakEnd p = spanEnd (not . p)++-- | The stripSuffix function drops the given suffix from a list. It returns+-- Nothing if the list did not end with the suffix given, or Just the list+-- before the suffix, if it does.+stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+stripSuffix xs ys = fmap reverse $ stripPrefix (reverse xs) (reverse ys)
+ tests/filepath-equivalent-tests/TestEquiv.hs view
@@ -0,0 +1,427 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import Test.QuickCheck hiding ((==>))+import TestUtil+import Prelude as P++import qualified System.FilePath.Windows as W+import qualified System.FilePath.Posix as P+import qualified Legacy.System.FilePath.Windows as LW+import qualified Legacy.System.FilePath.Posix as LP+++main :: IO ()+main = runTests equivalentTests+++equivalentTests :: [(String, Property)]+equivalentTests =+  [+    ( "pathSeparator (windows)"+    , property $ W.pathSeparator == LW.pathSeparator+    )+    ,+    ( "pathSeparators (windows)"+    , property $ W.pathSeparators == LW.pathSeparators+    )+    ,+    ( "isPathSeparator (windows)"+    , property $ \p -> W.isPathSeparator p == LW.isPathSeparator p+    )+    ,+    ( "searchPathSeparator (windows)"+    , property $ W.searchPathSeparator == LW.searchPathSeparator+    )+    ,+    ( "isSearchPathSeparator (windows)"+    , property $ \p -> W.isSearchPathSeparator p == LW.isSearchPathSeparator p+    )+    ,+    ( "extSeparator (windows)"+    , property $ W.extSeparator == LW.extSeparator+    )+    ,+    ( "isExtSeparator (windows)"+    , property $ \p -> W.isExtSeparator p == LW.isExtSeparator p+    )+    ,+    ( "splitSearchPath (windows)"+    , property $ \p -> W.splitSearchPath p == LW.splitSearchPath p+    )+    ,+    ( "splitExtension (windows)"+    , property $ \p -> W.splitExtension p == LW.splitExtension p+    )+    ,+    ( "takeExtension (windows)"+    , property $ \p -> W.takeExtension p == LW.takeExtension p+    )+    ,+    ( "replaceExtension (windows)"+    , property $ \p s -> W.replaceExtension p s == LW.replaceExtension p s+    )+    ,+    ( "dropExtension (windows)"+    , property $ \p -> W.dropExtension p == LW.dropExtension p+    )+    ,+    ( "addExtension (windows)"+    , property $ \p s -> W.addExtension p s == LW.addExtension p s+    )+    ,+    ( "hasExtension (windows)"+    , property $ \p -> W.hasExtension p == LW.hasExtension p+    )+    ,+    ( "splitExtensions (windows)"+    , property $ \p -> W.splitExtensions p == LW.splitExtensions p+    )+    ,+    ( "dropExtensions (windows)"+    , property $ \p -> W.dropExtensions p == LW.dropExtensions p+    )+    ,+    ( "takeExtensions (windows)"+    , property $ \p -> W.takeExtensions p == LW.takeExtensions p+    )+    ,+    ( "replaceExtensions (windows)"+    , property $ \p s -> W.replaceExtensions p s == LW.replaceExtensions p s+    )+    ,+    ( "isExtensionOf (windows)"+    , property $ \p s -> W.isExtensionOf p s == LW.isExtensionOf p s+    )+    ,+    ( "stripExtension (windows)"+    , property $ \p s -> W.stripExtension p s == LW.stripExtension p s+    )+    ,+    ( "splitFileName (windows)"+    , property $ \p -> W.splitFileName p == LW.splitFileName p+    )+    ,+    ( "takeFileName (windows)"+    , property $ \p -> W.takeFileName p == LW.takeFileName p+    )+    ,+    ( "replaceFileName (windows)"+    , property $ \p s -> W.replaceFileName p s == LW.replaceFileName p s+    )+    ,+    ( "dropFileName (windows)"+    , property $ \p -> W.dropFileName p == LW.dropFileName p+    )+    ,+    ( "takeBaseName (windows)"+    , property $ \p -> W.takeBaseName p == LW.takeBaseName p+    )+    ,+    ( "replaceBaseName (windows)"+    , property $ \p s -> W.replaceBaseName p s == LW.replaceBaseName p s+    )+    ,+    ( "takeDirectory (windows)"+    , property $ \p -> W.takeDirectory p == LW.takeDirectory p+    )+    ,+    ( "replaceDirectory (windows)"+    , property $ \p s -> W.replaceDirectory p s == LW.replaceDirectory p s+    )+    ,+    ( "combine (windows)"+    , property $ \p s -> W.combine p s == LW.combine p s+    )+    ,+    ( "splitPath (windows)"+    , property $ \p -> W.splitPath p == LW.splitPath p+    )+    ,+    ( "joinPath (windows)"+    , property $ \p -> W.joinPath p == LW.joinPath p+    )+    ,+    ( "splitDirectories (windows)"+    , property $ \p -> W.splitDirectories p == LW.splitDirectories p+    )+    ,+    ( "splitDirectories (windows)"+    , property $ \p -> W.splitDirectories p == LW.splitDirectories p+    )+    ,+    ( "splitDrive (windows)"+    , property $ \p -> W.splitDrive p == LW.splitDrive p+    )+    ,+    ( "joinDrive (windows)"+    , property $ \p s -> W.joinDrive p s == LW.joinDrive p s+    )+    ,+    ( "takeDrive (windows)"+    , property $ \p -> W.takeDrive p == LW.takeDrive p+    )+    ,+    ( "hasDrive (windows)"+    , property $ \p -> W.hasDrive p == LW.hasDrive p+    )+    ,+    ( "dropDrive (windows)"+    , property $ \p -> W.dropDrive p == LW.dropDrive p+    )+    ,+    ( "isDrive (windows)"+    , property $ \p -> W.isDrive p == LW.isDrive p+    )+    ,+    ( "hasTrailingPathSeparator (windows)"+    , property $ \p -> W.hasTrailingPathSeparator p == LW.hasTrailingPathSeparator p+    )+    ,+    ( "addTrailingPathSeparator (windows)"+    , property $ \p -> W.addTrailingPathSeparator p == LW.addTrailingPathSeparator p+    )+    ,+    ( "dropTrailingPathSeparator (windows)"+    , property $ \p -> W.dropTrailingPathSeparator p == LW.dropTrailingPathSeparator p+    )+    ,+    ( "normalise (windows)"+    , property $ \p -> W.normalise p == LW.normalise p+    )+    ,+    ( "equalFilePath (windows)"+    , property $ \p s -> W.equalFilePath p s == LW.equalFilePath p s+    )+    ,+    ( "makeRelative (windows)"+    , property $ \p s -> W.makeRelative p s == LW.makeRelative p s+    )+    ,+    ( "isRelative (windows)"+    , property $ \p -> W.isRelative p == LW.isRelative p+    )+    ,+    ( "isAbsolute (windows)"+    , property $ \p -> W.isAbsolute p == LW.isAbsolute p+    )+    ,+    ( "isValid (windows)"+    , property $ \p -> W.isValid p == LW.isValid p+    )+    ,+    ( "makeValid (windows)"+    , property $ \p -> W.makeValid p == LW.makeValid p+    )+    ,+    ( "pathSeparator (posix)"+    , property $ P.pathSeparator == LP.pathSeparator+    )+    ,+    ( "pathSeparators (posix)"+    , property $ P.pathSeparators == LP.pathSeparators+    )+    ,+    ( "isPathSeparator (posix)"+    , property $ \p -> P.isPathSeparator p == LP.isPathSeparator p+    )+    ,+    ( "searchPathSeparator (posix)"+    , property $ P.searchPathSeparator == LP.searchPathSeparator+    )+    ,+    ( "isSearchPathSeparator (posix)"+    , property $ \p -> P.isSearchPathSeparator p == LP.isSearchPathSeparator p+    )+    ,+    ( "extSeparator (posix)"+    , property $ P.extSeparator == LP.extSeparator+    )+    ,+    ( "isExtSeparator (posix)"+    , property $ \p -> P.isExtSeparator p == LP.isExtSeparator p+    )+    ,+    ( "splitSearchPath (posix)"+    , property $ \p -> P.splitSearchPath p == LP.splitSearchPath p+    )+    ,+    ( "splitExtension (posix)"+    , property $ \p -> P.splitExtension p == LP.splitExtension p+    )+    ,+    ( "takeExtension (posix)"+    , property $ \p -> P.takeExtension p == LP.takeExtension p+    )+    ,+    ( "replaceExtension (posix)"+    , property $ \p s -> P.replaceExtension p s == LP.replaceExtension p s+    )+    ,+    ( "dropExtension (posix)"+    , property $ \p -> P.dropExtension p == LP.dropExtension p+    )+    ,+    ( "addExtension (posix)"+    , property $ \p s -> P.addExtension p s == LP.addExtension p s+    )+    ,+    ( "hasExtension (posix)"+    , property $ \p -> P.hasExtension p == LP.hasExtension p+    )+    ,+    ( "splitExtensions (posix)"+    , property $ \p -> P.splitExtensions p == LP.splitExtensions p+    )+    ,+    ( "dropExtensions (posix)"+    , property $ \p -> P.dropExtensions p == LP.dropExtensions p+    )+    ,+    ( "takeExtensions (posix)"+    , property $ \p -> P.takeExtensions p == LP.takeExtensions p+    )+    ,+    ( "replaceExtensions (posix)"+    , property $ \p s -> P.replaceExtensions p s == LP.replaceExtensions p s+    )+    ,+    ( "isExtensionOf (posix)"+    , property $ \p s -> P.isExtensionOf p s == LP.isExtensionOf p s+    )+    ,+    ( "stripExtension (posix)"+    , property $ \p s -> P.stripExtension p s == LP.stripExtension p s+    )+    ,+    ( "splitFileName (posix)"+    , property $ \p -> P.splitFileName p == LP.splitFileName p+    )+    ,+    ( "takeFileName (posix)"+    , property $ \p -> P.takeFileName p == LP.takeFileName p+    )+    ,+    ( "replaceFileName (posix)"+    , property $ \p s -> P.replaceFileName p s == LP.replaceFileName p s+    )+    ,+    ( "dropFileName (posix)"+    , property $ \p -> P.dropFileName p == LP.dropFileName p+    )+    ,+    ( "takeBaseName (posix)"+    , property $ \p -> P.takeBaseName p == LP.takeBaseName p+    )+    ,+    ( "replaceBaseName (posix)"+    , property $ \p s -> P.replaceBaseName p s == LP.replaceBaseName p s+    )+    ,+    ( "takeDirectory (posix)"+    , property $ \p -> P.takeDirectory p == LP.takeDirectory p+    )+    ,+    ( "replaceDirectory (posix)"+    , property $ \p s -> P.replaceDirectory p s == LP.replaceDirectory p s+    )+    ,+    ( "combine (posix)"+    , property $ \p s -> P.combine p s == LP.combine p s+    )+    ,+    ( "splitPath (posix)"+    , property $ \p -> P.splitPath p == LP.splitPath p+    )+    ,+    ( "joinPath (posix)"+    , property $ \p -> P.joinPath p == LP.joinPath p+    )+    ,+    ( "splitDirectories (posix)"+    , property $ \p -> P.splitDirectories p == LP.splitDirectories p+    )+    ,+    ( "splitDirectories (posix)"+    , property $ \p -> P.splitDirectories p == LP.splitDirectories p+    )+    ,+    ( "splitDrive (posix)"+    , property $ \p -> P.splitDrive p == LP.splitDrive p+    )+    ,+    ( "joinDrive (posix)"+    , property $ \p s -> P.joinDrive p s == LP.joinDrive p s+    )+    ,+    ( "takeDrive (posix)"+    , property $ \p -> P.takeDrive p == LP.takeDrive p+    )+    ,+    ( "hasDrive (posix)"+    , property $ \p -> P.hasDrive p == LP.hasDrive p+    )+    ,+    ( "dropDrive (posix)"+    , property $ \p -> P.dropDrive p == LP.dropDrive p+    )+    ,+    ( "isDrive (posix)"+    , property $ \p -> P.isDrive p == LP.isDrive p+    )+    ,+    ( "hasTrailingPathSeparator (posix)"+    , property $ \p -> P.hasTrailingPathSeparator p == LP.hasTrailingPathSeparator p+    )+    ,+    ( "addTrailingPathSeparator (posix)"+    , property $ \p -> P.addTrailingPathSeparator p == LP.addTrailingPathSeparator p+    )+    ,+    ( "dropTrailingPathSeparator (posix)"+    , property $ \p -> P.dropTrailingPathSeparator p == LP.dropTrailingPathSeparator p+    )+    ,+    ( "normalise (posix)"+    , property $ \p -> P.normalise p == LP.normalise p+    )+    ,+    ( "equalFilePath (posix)"+    , property $ \p s -> P.equalFilePath p s == LP.equalFilePath p s+    )+    ,+    ( "makeRelative (posix)"+    , property $ \p s -> P.makeRelative p s == LP.makeRelative p s+    )+    ,+    ( "isRelative (posix)"+    , property $ \p -> P.isRelative p == LP.isRelative p+    )+    ,+    ( "isAbsolute (posix)"+    , property $ \p -> P.isAbsolute p == LP.isAbsolute p+    )+    ,+    ( "isValid (posix)"+    , property $ \p -> P.isValid p == LP.isValid p+    )+    ,+    ( "makeValid (posix)"+    , property $ \p -> P.makeValid p == LP.makeValid p+    )+  ]+++++++++++++
+ tests/filepath-tests/Test.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE TypeApplications #-}++module Main where++import System.Environment+import TestGen+import Control.Monad+import Data.Maybe+import Test.QuickCheck+++main :: IO ()+main = do+    args <- getArgs+    let count   = case args of i:_   -> read i; _ -> 10000+    let testNum = case args of+                    _:i:_+                      | let num = read i+                      , num < 0    -> drop (negate num) tests+                      | let num = read i+                      , num > 0    -> take num          tests+                      | otherwise  -> []+                    _ -> tests+    putStrLn $ "Testing with " ++ show count ++ " repetitions"+    let total' = length testNum+    let showOutput x = show x{output=""} ++ "\n" ++ output x+    bad <- fmap catMaybes $ forM (zip @Integer [1..] testNum) $ \(i,(msg,prop)) -> do+        putStrLn $ "Test " ++ show i ++ " of " ++ show total' ++ ": " ++ msg+        res <- quickCheckWithResult stdArgs{chatty=False, maxSuccess=count} prop+        case res of+            Success{} -> pure Nothing+            bad -> do putStrLn $ showOutput bad; putStrLn "TEST FAILURE!"; pure $ Just (msg,bad)+    if null bad then+        putStrLn $ "Success, " ++ show total' ++ " tests passed"+     else do+        putStrLn $ show (length bad) ++ " FAILURES\n"+        forM_ (zip @Integer [1..] bad) $ \(i,(a,b)) ->+            putStrLn $ "FAILURE " ++ show i ++ ": " ++ a ++ "\n" ++ showOutput b ++ "\n"+        fail $ "FAILURE, failed " ++ show (length bad) ++ " of " ++ show total' ++ " tests"
+ tests/filepath-tests/TestGen.hs view
@@ -0,0 +1,949 @@+-- GENERATED CODE: See ../Generate.hs+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-orphans #-}+module TestGen(tests) where+import TestUtil+#if !MIN_VERSION_base(4,11,0)+import Data.Semigroup+#endif+import Prelude as P+import Data.String+import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )+import GHC.IO.Encoding.UTF16 ( mkUTF16le )+import GHC.IO.Encoding.UTF8 ( mkUTF8 )+import System.OsString.Internal.Types+import System.OsPath.Encoding.Internal+import qualified Data.Char as C+import qualified System.OsPath.Data.ByteString.Short as SBS+import qualified System.OsPath.Data.ByteString.Short.Word16 as SBS16+import qualified System.FilePath.Windows as W+import qualified System.FilePath.Posix as P+import qualified System.OsPath.Windows as AFP_W+import qualified System.OsPath.Posix as AFP_P+instance IsString WindowsString where fromString = WS . either (error . show) id . encodeWithTE (mkUTF16le TransliterateCodingFailure)+instance IsString PosixString where fromString = PS . either (error . show) id . encodeWithTE (mkUTF8 TransliterateCodingFailure)+#if defined(mingw32_HOST_OS) || defined(__MINGW32__)+instance IsString OsString where fromString = OsString . WS . either (error . show) id . encodeWithTE (mkUTF16le TransliterateCodingFailure)+#else+instance IsString OsString where fromString = OsString . PS . either (error . show) id . encodeWithTE (mkUTF8 TransliterateCodingFailure)+#endif+tests :: [(String, Property)]+tests =+    [("W.pathSeparator == '\\\\'", property $ W.pathSeparator == '\\')+    ,("AFP_W.pathSeparator == (WW . fromIntegral . C.ord $ '\\\\')", property $ AFP_W.pathSeparator == (WW . fromIntegral . C.ord $ '\\'))+    ,("P.pathSeparator == '/'", property $ P.pathSeparator == '/')+    ,("AFP_P.pathSeparator == (PW . fromIntegral . C.ord $ '/')", property $ AFP_P.pathSeparator == (PW . fromIntegral . C.ord $ '/'))+    ,("P.isPathSeparator P.pathSeparator", property $ P.isPathSeparator P.pathSeparator)+    ,("W.isPathSeparator W.pathSeparator", property $ W.isPathSeparator W.pathSeparator)+    ,("AFP_P.isPathSeparator AFP_P.pathSeparator", property $ AFP_P.isPathSeparator AFP_P.pathSeparator)+    ,("AFP_W.isPathSeparator AFP_W.pathSeparator", property $ AFP_W.isPathSeparator AFP_W.pathSeparator)+    ,("W.pathSeparators == ['\\\\', '/']", property $ W.pathSeparators == ['\\', '/'])+    ,("AFP_W.pathSeparators == [(WW . fromIntegral . C.ord $ '\\\\'), (WW . fromIntegral . C.ord $ '/')]", property $ AFP_W.pathSeparators == [(WW . fromIntegral . C.ord $ '\\'), (WW . fromIntegral . C.ord $ '/')])+    ,("P.pathSeparators == ['/']", property $ P.pathSeparators == ['/'])+    ,("AFP_P.pathSeparators == [(PW . fromIntegral . C.ord $ '/')]", property $ AFP_P.pathSeparators == [(PW . fromIntegral . C.ord $ '/')])+    ,("P.pathSeparator `elem` P.pathSeparators", property $ P.pathSeparator `elem` P.pathSeparators)+    ,("W.pathSeparator `elem` W.pathSeparators", property $ W.pathSeparator `elem` W.pathSeparators)+    ,("AFP_P.pathSeparator `elem` AFP_P.pathSeparators", property $ AFP_P.pathSeparator `elem` AFP_P.pathSeparators)+    ,("AFP_W.pathSeparator `elem` AFP_W.pathSeparators", property $ AFP_W.pathSeparator `elem` AFP_W.pathSeparators)+    ,("P.isPathSeparator a == (a `elem` P.pathSeparators)", property $ \a -> P.isPathSeparator a == (a `elem` P.pathSeparators))+    ,("W.isPathSeparator a == (a `elem` W.pathSeparators)", property $ \a -> W.isPathSeparator a == (a `elem` W.pathSeparators))+    ,("AFP_P.isPathSeparator a == (a `elem` AFP_P.pathSeparators)", property $ \a -> AFP_P.isPathSeparator a == (a `elem` AFP_P.pathSeparators))+    ,("AFP_W.isPathSeparator a == (a `elem` AFP_W.pathSeparators)", property $ \a -> AFP_W.isPathSeparator a == (a `elem` AFP_W.pathSeparators))+    ,("W.searchPathSeparator == ';'", property $ W.searchPathSeparator == ';')+    ,("AFP_W.searchPathSeparator == (WW . fromIntegral . C.ord $ ';')", property $ AFP_W.searchPathSeparator == (WW . fromIntegral . C.ord $ ';'))+    ,("P.searchPathSeparator == ':'", property $ P.searchPathSeparator == ':')+    ,("AFP_P.searchPathSeparator == (PW . fromIntegral . C.ord $ ':')", property $ AFP_P.searchPathSeparator == (PW . fromIntegral . C.ord $ ':'))+    ,("P.isSearchPathSeparator a == (a == P.searchPathSeparator)", property $ \a -> P.isSearchPathSeparator a == (a == P.searchPathSeparator))+    ,("W.isSearchPathSeparator a == (a == W.searchPathSeparator)", property $ \a -> W.isSearchPathSeparator a == (a == W.searchPathSeparator))+    ,("AFP_P.isSearchPathSeparator a == (a == AFP_P.searchPathSeparator)", property $ \a -> AFP_P.isSearchPathSeparator a == (a == AFP_P.searchPathSeparator))+    ,("AFP_W.isSearchPathSeparator a == (a == AFP_W.searchPathSeparator)", property $ \a -> AFP_W.isSearchPathSeparator a == (a == AFP_W.searchPathSeparator))+    ,("P.extSeparator == '.'", property $ P.extSeparator == '.')+    ,("W.extSeparator == '.'", property $ W.extSeparator == '.')+    ,("AFP_P.extSeparator == (PW . fromIntegral . C.ord $ '.')", property $ AFP_P.extSeparator == (PW . fromIntegral . C.ord $ '.'))+    ,("AFP_W.extSeparator == (WW . fromIntegral . C.ord $ '.')", property $ AFP_W.extSeparator == (WW . fromIntegral . C.ord $ '.'))+    ,("P.isExtSeparator a == (a == P.extSeparator)", property $ \a -> P.isExtSeparator a == (a == P.extSeparator))+    ,("W.isExtSeparator a == (a == W.extSeparator)", property $ \a -> W.isExtSeparator a == (a == W.extSeparator))+    ,("AFP_P.isExtSeparator a == (a == AFP_P.extSeparator)", property $ \a -> AFP_P.isExtSeparator a == (a == AFP_P.extSeparator))+    ,("AFP_W.isExtSeparator a == (a == AFP_W.extSeparator)", property $ \a -> AFP_W.isExtSeparator a == (a == AFP_W.extSeparator))+    ,("P.splitSearchPath \"File1:File2:File3\" == [\"File1\", \"File2\", \"File3\"]", property $ P.splitSearchPath "File1:File2:File3" == ["File1", "File2", "File3"])+    ,("AFP_P.splitSearchPath (\"File1:File2:File3\") == [(\"File1\"), (\"File2\"), (\"File3\")]", property $ AFP_P.splitSearchPath ("File1:File2:File3") == [("File1"), ("File2"), ("File3")])+    ,("P.splitSearchPath \"File1::File2:File3\" == [\"File1\", \".\", \"File2\", \"File3\"]", property $ P.splitSearchPath "File1::File2:File3" == ["File1", ".", "File2", "File3"])+    ,("AFP_P.splitSearchPath (\"File1::File2:File3\") == [(\"File1\"), (\".\"), (\"File2\"), (\"File3\")]", property $ AFP_P.splitSearchPath ("File1::File2:File3") == [("File1"), ("."), ("File2"), ("File3")])+    ,("W.splitSearchPath \"File1;File2;File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;File2;File3" == ["File1", "File2", "File3"])+    ,("AFP_W.splitSearchPath (\"File1;File2;File3\") == [(\"File1\"), (\"File2\"), (\"File3\")]", property $ AFP_W.splitSearchPath ("File1;File2;File3") == [("File1"), ("File2"), ("File3")])+    ,("W.splitSearchPath \"File1;;File2;File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;;File2;File3" == ["File1", "File2", "File3"])+    ,("AFP_W.splitSearchPath (\"File1;;File2;File3\") == [(\"File1\"), (\"File2\"), (\"File3\")]", property $ AFP_W.splitSearchPath ("File1;;File2;File3") == [("File1"), ("File2"), ("File3")])+    ,("W.splitSearchPath \"File1;\\\"File2\\\";File3\" == [\"File1\", \"File2\", \"File3\"]", property $ W.splitSearchPath "File1;\"File2\";File3" == ["File1", "File2", "File3"])+    ,("AFP_W.splitSearchPath (\"File1;\\\"File2\\\";File3\") == [(\"File1\"), (\"File2\"), (\"File3\")]", property $ AFP_W.splitSearchPath ("File1;\"File2\";File3") == [("File1"), ("File2"), ("File3")])+    ,("P.splitExtension \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ P.splitExtension "/directory/path.ext" == ("/directory/path", ".ext"))+    ,("W.splitExtension \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ W.splitExtension "/directory/path.ext" == ("/directory/path", ".ext"))+    ,("AFP_P.splitExtension (\"/directory/path.ext\") == ((\"/directory/path\"), (\".ext\"))", property $ AFP_P.splitExtension ("/directory/path.ext") == (("/directory/path"), (".ext")))+    ,("AFP_W.splitExtension (\"/directory/path.ext\") == ((\"/directory/path\"), (\".ext\"))", property $ AFP_W.splitExtension ("/directory/path.ext") == (("/directory/path"), (".ext")))+    ,("uncurry (<>) (P.splitExtension x) == x", property $ \(QFilePath x) -> uncurry (<>) (P.splitExtension x) == x)+    ,("uncurry (<>) (W.splitExtension x) == x", property $ \(QFilePath x) -> uncurry (<>) (W.splitExtension x) == x)+    ,("uncurry (<>) (AFP_P.splitExtension x) == x", property $ \(QFilePathAFP_P x) -> uncurry (<>) (AFP_P.splitExtension x) == x)+    ,("uncurry (<>) (AFP_W.splitExtension x) == x", property $ \(QFilePathAFP_W x) -> uncurry (<>) (AFP_W.splitExtension x) == x)+    ,("uncurry P.addExtension (P.splitExtension x) == x", property $ \(QFilePathValidP x) -> uncurry P.addExtension (P.splitExtension x) == x)+    ,("uncurry W.addExtension (W.splitExtension x) == x", property $ \(QFilePathValidW x) -> uncurry W.addExtension (W.splitExtension x) == x)+    ,("uncurry AFP_P.addExtension (AFP_P.splitExtension x) == x", property $ \(QFilePathValidAFP_P x) -> uncurry AFP_P.addExtension (AFP_P.splitExtension x) == x)+    ,("uncurry AFP_W.addExtension (AFP_W.splitExtension x) == x", property $ \(QFilePathValidAFP_W x) -> uncurry AFP_W.addExtension (AFP_W.splitExtension x) == x)+    ,("P.splitExtension \"file.txt\" == (\"file\", \".txt\")", property $ P.splitExtension "file.txt" == ("file", ".txt"))+    ,("W.splitExtension \"file.txt\" == (\"file\", \".txt\")", property $ W.splitExtension "file.txt" == ("file", ".txt"))+    ,("AFP_P.splitExtension (\"file.txt\") == ((\"file\"), (\".txt\"))", property $ AFP_P.splitExtension ("file.txt") == (("file"), (".txt")))+    ,("AFP_W.splitExtension (\"file.txt\") == ((\"file\"), (\".txt\"))", property $ AFP_W.splitExtension ("file.txt") == (("file"), (".txt")))+    ,("P.splitExtension \"file\" == (\"file\", \"\")", property $ P.splitExtension "file" == ("file", ""))+    ,("W.splitExtension \"file\" == (\"file\", \"\")", property $ W.splitExtension "file" == ("file", ""))+    ,("AFP_P.splitExtension (\"file\") == ((\"file\"), (\"\"))", property $ AFP_P.splitExtension ("file") == (("file"), ("")))+    ,("AFP_W.splitExtension (\"file\") == ((\"file\"), (\"\"))", property $ AFP_W.splitExtension ("file") == (("file"), ("")))+    ,("P.splitExtension \"file/file.txt\" == (\"file/file\", \".txt\")", property $ P.splitExtension "file/file.txt" == ("file/file", ".txt"))+    ,("W.splitExtension \"file/file.txt\" == (\"file/file\", \".txt\")", property $ W.splitExtension "file/file.txt" == ("file/file", ".txt"))+    ,("AFP_P.splitExtension (\"file/file.txt\") == ((\"file/file\"), (\".txt\"))", property $ AFP_P.splitExtension ("file/file.txt") == (("file/file"), (".txt")))+    ,("AFP_W.splitExtension (\"file/file.txt\") == ((\"file/file\"), (\".txt\"))", property $ AFP_W.splitExtension ("file/file.txt") == (("file/file"), (".txt")))+    ,("P.splitExtension \"file.txt/boris\" == (\"file.txt/boris\", \"\")", property $ P.splitExtension "file.txt/boris" == ("file.txt/boris", ""))+    ,("W.splitExtension \"file.txt/boris\" == (\"file.txt/boris\", \"\")", property $ W.splitExtension "file.txt/boris" == ("file.txt/boris", ""))+    ,("AFP_P.splitExtension (\"file.txt/boris\") == ((\"file.txt/boris\"), (\"\"))", property $ AFP_P.splitExtension ("file.txt/boris") == (("file.txt/boris"), ("")))+    ,("AFP_W.splitExtension (\"file.txt/boris\") == ((\"file.txt/boris\"), (\"\"))", property $ AFP_W.splitExtension ("file.txt/boris") == (("file.txt/boris"), ("")))+    ,("P.splitExtension \"file.txt/boris.ext\" == (\"file.txt/boris\", \".ext\")", property $ P.splitExtension "file.txt/boris.ext" == ("file.txt/boris", ".ext"))+    ,("W.splitExtension \"file.txt/boris.ext\" == (\"file.txt/boris\", \".ext\")", property $ W.splitExtension "file.txt/boris.ext" == ("file.txt/boris", ".ext"))+    ,("AFP_P.splitExtension (\"file.txt/boris.ext\") == ((\"file.txt/boris\"), (\".ext\"))", property $ AFP_P.splitExtension ("file.txt/boris.ext") == (("file.txt/boris"), (".ext")))+    ,("AFP_W.splitExtension (\"file.txt/boris.ext\") == ((\"file.txt/boris\"), (\".ext\"))", property $ AFP_W.splitExtension ("file.txt/boris.ext") == (("file.txt/boris"), (".ext")))+    ,("P.splitExtension \"file/path.txt.bob.fred\" == (\"file/path.txt.bob\", \".fred\")", property $ P.splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob", ".fred"))+    ,("W.splitExtension \"file/path.txt.bob.fred\" == (\"file/path.txt.bob\", \".fred\")", property $ W.splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob", ".fred"))+    ,("AFP_P.splitExtension (\"file/path.txt.bob.fred\") == ((\"file/path.txt.bob\"), (\".fred\"))", property $ AFP_P.splitExtension ("file/path.txt.bob.fred") == (("file/path.txt.bob"), (".fred")))+    ,("AFP_W.splitExtension (\"file/path.txt.bob.fred\") == ((\"file/path.txt.bob\"), (\".fred\"))", property $ AFP_W.splitExtension ("file/path.txt.bob.fred") == (("file/path.txt.bob"), (".fred")))+    ,("P.splitExtension \"file/path.txt/\" == (\"file/path.txt/\", \"\")", property $ P.splitExtension "file/path.txt/" == ("file/path.txt/", ""))+    ,("W.splitExtension \"file/path.txt/\" == (\"file/path.txt/\", \"\")", property $ W.splitExtension "file/path.txt/" == ("file/path.txt/", ""))+    ,("AFP_P.splitExtension (\"file/path.txt/\") == ((\"file/path.txt/\"), (\"\"))", property $ AFP_P.splitExtension ("file/path.txt/") == (("file/path.txt/"), ("")))+    ,("AFP_W.splitExtension (\"file/path.txt/\") == ((\"file/path.txt/\"), (\"\"))", property $ AFP_W.splitExtension ("file/path.txt/") == (("file/path.txt/"), ("")))+    ,("P.takeExtension \"/directory/path.ext\" == \".ext\"", property $ P.takeExtension "/directory/path.ext" == ".ext")+    ,("W.takeExtension \"/directory/path.ext\" == \".ext\"", property $ W.takeExtension "/directory/path.ext" == ".ext")+    ,("AFP_P.takeExtension (\"/directory/path.ext\") == (\".ext\")", property $ AFP_P.takeExtension ("/directory/path.ext") == (".ext"))+    ,("AFP_W.takeExtension (\"/directory/path.ext\") == (\".ext\")", property $ AFP_W.takeExtension ("/directory/path.ext") == (".ext"))+    ,("P.takeExtension x == snd (P.splitExtension x)", property $ \(QFilePath x) -> P.takeExtension x == snd (P.splitExtension x))+    ,("W.takeExtension x == snd (W.splitExtension x)", property $ \(QFilePath x) -> W.takeExtension x == snd (W.splitExtension x))+    ,("AFP_P.takeExtension x == snd (AFP_P.splitExtension x)", property $ \(QFilePathAFP_P x) -> AFP_P.takeExtension x == snd (AFP_P.splitExtension x))+    ,("AFP_W.takeExtension x == snd (AFP_W.splitExtension x)", property $ \(QFilePathAFP_W x) -> AFP_W.takeExtension x == snd (AFP_W.splitExtension x))+    ,("P.takeExtension (P.addExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeExtension (P.addExtension x "ext") == ".ext")+    ,("W.takeExtension (W.addExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeExtension (W.addExtension x "ext") == ".ext")+    ,("AFP_P.takeExtension (AFP_P.addExtension x (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_P x) -> AFP_P.takeExtension (AFP_P.addExtension x ("ext")) == (".ext"))+    ,("AFP_W.takeExtension (AFP_W.addExtension x (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_W x) -> AFP_W.takeExtension (AFP_W.addExtension x ("ext")) == (".ext"))+    ,("P.takeExtension (P.replaceExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeExtension (P.replaceExtension x "ext") == ".ext")+    ,("W.takeExtension (W.replaceExtension x \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeExtension (W.replaceExtension x "ext") == ".ext")+    ,("AFP_P.takeExtension (AFP_P.replaceExtension x (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_P x) -> AFP_P.takeExtension (AFP_P.replaceExtension x ("ext")) == (".ext"))+    ,("AFP_W.takeExtension (AFP_W.replaceExtension x (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_W x) -> AFP_W.takeExtension (AFP_W.replaceExtension x ("ext")) == (".ext"))+    ,("\"/directory/path.txt\" P.-<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" P.-<.> "ext" == "/directory/path.ext")+    ,("\"/directory/path.txt\" W.-<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" W.-<.> "ext" == "/directory/path.ext")+    ,("(\"/directory/path.txt\") AFP_P.-<.> (\"ext\") == (\"/directory/path.ext\")", property $ ("/directory/path.txt") AFP_P.-<.> ("ext") == ("/directory/path.ext"))+    ,("(\"/directory/path.txt\") AFP_W.-<.> (\"ext\") == (\"/directory/path.ext\")", property $ ("/directory/path.txt") AFP_W.-<.> ("ext") == ("/directory/path.ext"))+    ,("\"/directory/path.txt\" P.-<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" P.-<.> ".ext" == "/directory/path.ext")+    ,("\"/directory/path.txt\" W.-<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path.txt" W.-<.> ".ext" == "/directory/path.ext")+    ,("(\"/directory/path.txt\") AFP_P.-<.> (\".ext\") == (\"/directory/path.ext\")", property $ ("/directory/path.txt") AFP_P.-<.> (".ext") == ("/directory/path.ext"))+    ,("(\"/directory/path.txt\") AFP_W.-<.> (\".ext\") == (\"/directory/path.ext\")", property $ ("/directory/path.txt") AFP_W.-<.> (".ext") == ("/directory/path.ext"))+    ,("\"foo.o\" P.-<.> \"c\" == \"foo.c\"", property $ "foo.o" P.-<.> "c" == "foo.c")+    ,("\"foo.o\" W.-<.> \"c\" == \"foo.c\"", property $ "foo.o" W.-<.> "c" == "foo.c")+    ,("(\"foo.o\") AFP_P.-<.> (\"c\") == (\"foo.c\")", property $ ("foo.o") AFP_P.-<.> ("c") == ("foo.c"))+    ,("(\"foo.o\") AFP_W.-<.> (\"c\") == (\"foo.c\")", property $ ("foo.o") AFP_W.-<.> ("c") == ("foo.c"))+    ,("P.replaceExtension \"/directory/path.txt\" \"ext\" == \"/directory/path.ext\"", property $ P.replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext")+    ,("W.replaceExtension \"/directory/path.txt\" \"ext\" == \"/directory/path.ext\"", property $ W.replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext")+    ,("AFP_P.replaceExtension (\"/directory/path.txt\") (\"ext\") == (\"/directory/path.ext\")", property $ AFP_P.replaceExtension ("/directory/path.txt") ("ext") == ("/directory/path.ext"))+    ,("AFP_W.replaceExtension (\"/directory/path.txt\") (\"ext\") == (\"/directory/path.ext\")", property $ AFP_W.replaceExtension ("/directory/path.txt") ("ext") == ("/directory/path.ext"))+    ,("P.replaceExtension \"/directory/path.txt\" \".ext\" == \"/directory/path.ext\"", property $ P.replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext")+    ,("W.replaceExtension \"/directory/path.txt\" \".ext\" == \"/directory/path.ext\"", property $ W.replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext")+    ,("AFP_P.replaceExtension (\"/directory/path.txt\") (\".ext\") == (\"/directory/path.ext\")", property $ AFP_P.replaceExtension ("/directory/path.txt") (".ext") == ("/directory/path.ext"))+    ,("AFP_W.replaceExtension (\"/directory/path.txt\") (\".ext\") == (\"/directory/path.ext\")", property $ AFP_W.replaceExtension ("/directory/path.txt") (".ext") == ("/directory/path.ext"))+    ,("P.replaceExtension \"file.txt\" \".bob\" == \"file.bob\"", property $ P.replaceExtension "file.txt" ".bob" == "file.bob")+    ,("W.replaceExtension \"file.txt\" \".bob\" == \"file.bob\"", property $ W.replaceExtension "file.txt" ".bob" == "file.bob")+    ,("AFP_P.replaceExtension (\"file.txt\") (\".bob\") == (\"file.bob\")", property $ AFP_P.replaceExtension ("file.txt") (".bob") == ("file.bob"))+    ,("AFP_W.replaceExtension (\"file.txt\") (\".bob\") == (\"file.bob\")", property $ AFP_W.replaceExtension ("file.txt") (".bob") == ("file.bob"))+    ,("P.replaceExtension \"file.txt\" \"bob\" == \"file.bob\"", property $ P.replaceExtension "file.txt" "bob" == "file.bob")+    ,("W.replaceExtension \"file.txt\" \"bob\" == \"file.bob\"", property $ W.replaceExtension "file.txt" "bob" == "file.bob")+    ,("AFP_P.replaceExtension (\"file.txt\") (\"bob\") == (\"file.bob\")", property $ AFP_P.replaceExtension ("file.txt") ("bob") == ("file.bob"))+    ,("AFP_W.replaceExtension (\"file.txt\") (\"bob\") == (\"file.bob\")", property $ AFP_W.replaceExtension ("file.txt") ("bob") == ("file.bob"))+    ,("P.replaceExtension \"file\" \".bob\" == \"file.bob\"", property $ P.replaceExtension "file" ".bob" == "file.bob")+    ,("W.replaceExtension \"file\" \".bob\" == \"file.bob\"", property $ W.replaceExtension "file" ".bob" == "file.bob")+    ,("AFP_P.replaceExtension (\"file\") (\".bob\") == (\"file.bob\")", property $ AFP_P.replaceExtension ("file") (".bob") == ("file.bob"))+    ,("AFP_W.replaceExtension (\"file\") (\".bob\") == (\"file.bob\")", property $ AFP_W.replaceExtension ("file") (".bob") == ("file.bob"))+    ,("P.replaceExtension \"file.txt\" \"\" == \"file\"", property $ P.replaceExtension "file.txt" "" == "file")+    ,("W.replaceExtension \"file.txt\" \"\" == \"file\"", property $ W.replaceExtension "file.txt" "" == "file")+    ,("AFP_P.replaceExtension (\"file.txt\") (\"\") == (\"file\")", property $ AFP_P.replaceExtension ("file.txt") ("") == ("file"))+    ,("AFP_W.replaceExtension (\"file.txt\") (\"\") == (\"file\")", property $ AFP_W.replaceExtension ("file.txt") ("") == ("file"))+    ,("P.replaceExtension \"file.fred.bob\" \"txt\" == \"file.fred.txt\"", property $ P.replaceExtension "file.fred.bob" "txt" == "file.fred.txt")+    ,("W.replaceExtension \"file.fred.bob\" \"txt\" == \"file.fred.txt\"", property $ W.replaceExtension "file.fred.bob" "txt" == "file.fred.txt")+    ,("AFP_P.replaceExtension (\"file.fred.bob\") (\"txt\") == (\"file.fred.txt\")", property $ AFP_P.replaceExtension ("file.fred.bob") ("txt") == ("file.fred.txt"))+    ,("AFP_W.replaceExtension (\"file.fred.bob\") (\"txt\") == (\"file.fred.txt\")", property $ AFP_W.replaceExtension ("file.fred.bob") ("txt") == ("file.fred.txt"))+    ,("P.replaceExtension x y == P.addExtension (P.dropExtension x) y", property $ \(QFilePath x) (QFilePath y) -> P.replaceExtension x y == P.addExtension (P.dropExtension x) y)+    ,("W.replaceExtension x y == W.addExtension (W.dropExtension x) y", property $ \(QFilePath x) (QFilePath y) -> W.replaceExtension x y == W.addExtension (W.dropExtension x) y)+    ,("AFP_P.replaceExtension x y == AFP_P.addExtension (AFP_P.dropExtension x) y", property $ \(QFilePathAFP_P x) (QFilePathAFP_P y) -> AFP_P.replaceExtension x y == AFP_P.addExtension (AFP_P.dropExtension x) y)+    ,("AFP_W.replaceExtension x y == AFP_W.addExtension (AFP_W.dropExtension x) y", property $ \(QFilePathAFP_W x) (QFilePathAFP_W y) -> AFP_W.replaceExtension x y == AFP_W.addExtension (AFP_W.dropExtension x) y)+    ,("\"/directory/path\" P.<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path" P.<.> "ext" == "/directory/path.ext")+    ,("\"/directory/path\" W.<.> \"ext\" == \"/directory/path.ext\"", property $ "/directory/path" W.<.> "ext" == "/directory/path.ext")+    ,("(\"/directory/path\") AFP_P.<.> (\"ext\") == (\"/directory/path.ext\")", property $ ("/directory/path") AFP_P.<.> ("ext") == ("/directory/path.ext"))+    ,("(\"/directory/path\") AFP_W.<.> (\"ext\") == (\"/directory/path.ext\")", property $ ("/directory/path") AFP_W.<.> ("ext") == ("/directory/path.ext"))+    ,("\"/directory/path\" P.<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path" P.<.> ".ext" == "/directory/path.ext")+    ,("\"/directory/path\" W.<.> \".ext\" == \"/directory/path.ext\"", property $ "/directory/path" W.<.> ".ext" == "/directory/path.ext")+    ,("(\"/directory/path\") AFP_P.<.> (\".ext\") == (\"/directory/path.ext\")", property $ ("/directory/path") AFP_P.<.> (".ext") == ("/directory/path.ext"))+    ,("(\"/directory/path\") AFP_W.<.> (\".ext\") == (\"/directory/path.ext\")", property $ ("/directory/path") AFP_W.<.> (".ext") == ("/directory/path.ext"))+    ,("P.dropExtension \"/directory/path.ext\" == \"/directory/path\"", property $ P.dropExtension "/directory/path.ext" == "/directory/path")+    ,("W.dropExtension \"/directory/path.ext\" == \"/directory/path\"", property $ W.dropExtension "/directory/path.ext" == "/directory/path")+    ,("AFP_P.dropExtension (\"/directory/path.ext\") == (\"/directory/path\")", property $ AFP_P.dropExtension ("/directory/path.ext") == ("/directory/path"))+    ,("AFP_W.dropExtension (\"/directory/path.ext\") == (\"/directory/path\")", property $ AFP_W.dropExtension ("/directory/path.ext") == ("/directory/path"))+    ,("P.dropExtension x == fst (P.splitExtension x)", property $ \(QFilePath x) -> P.dropExtension x == fst (P.splitExtension x))+    ,("W.dropExtension x == fst (W.splitExtension x)", property $ \(QFilePath x) -> W.dropExtension x == fst (W.splitExtension x))+    ,("AFP_P.dropExtension x == fst (AFP_P.splitExtension x)", property $ \(QFilePathAFP_P x) -> AFP_P.dropExtension x == fst (AFP_P.splitExtension x))+    ,("AFP_W.dropExtension x == fst (AFP_W.splitExtension x)", property $ \(QFilePathAFP_W x) -> AFP_W.dropExtension x == fst (AFP_W.splitExtension x))+    ,("P.addExtension \"/directory/path\" \"ext\" == \"/directory/path.ext\"", property $ P.addExtension "/directory/path" "ext" == "/directory/path.ext")+    ,("W.addExtension \"/directory/path\" \"ext\" == \"/directory/path.ext\"", property $ W.addExtension "/directory/path" "ext" == "/directory/path.ext")+    ,("AFP_P.addExtension (\"/directory/path\") (\"ext\") == (\"/directory/path.ext\")", property $ AFP_P.addExtension ("/directory/path") ("ext") == ("/directory/path.ext"))+    ,("AFP_W.addExtension (\"/directory/path\") (\"ext\") == (\"/directory/path.ext\")", property $ AFP_W.addExtension ("/directory/path") ("ext") == ("/directory/path.ext"))+    ,("P.addExtension \"file.txt\" \"bib\" == \"file.txt.bib\"", property $ P.addExtension "file.txt" "bib" == "file.txt.bib")+    ,("W.addExtension \"file.txt\" \"bib\" == \"file.txt.bib\"", property $ W.addExtension "file.txt" "bib" == "file.txt.bib")+    ,("AFP_P.addExtension (\"file.txt\") (\"bib\") == (\"file.txt.bib\")", property $ AFP_P.addExtension ("file.txt") ("bib") == ("file.txt.bib"))+    ,("AFP_W.addExtension (\"file.txt\") (\"bib\") == (\"file.txt.bib\")", property $ AFP_W.addExtension ("file.txt") ("bib") == ("file.txt.bib"))+    ,("P.addExtension \"file.\" \".bib\" == \"file..bib\"", property $ P.addExtension "file." ".bib" == "file..bib")+    ,("W.addExtension \"file.\" \".bib\" == \"file..bib\"", property $ W.addExtension "file." ".bib" == "file..bib")+    ,("AFP_P.addExtension (\"file.\") (\".bib\") == (\"file..bib\")", property $ AFP_P.addExtension ("file.") (".bib") == ("file..bib"))+    ,("AFP_W.addExtension (\"file.\") (\".bib\") == (\"file..bib\")", property $ AFP_W.addExtension ("file.") (".bib") == ("file..bib"))+    ,("P.addExtension \"file\" \".bib\" == \"file.bib\"", property $ P.addExtension "file" ".bib" == "file.bib")+    ,("W.addExtension \"file\" \".bib\" == \"file.bib\"", property $ W.addExtension "file" ".bib" == "file.bib")+    ,("AFP_P.addExtension (\"file\") (\".bib\") == (\"file.bib\")", property $ AFP_P.addExtension ("file") (".bib") == ("file.bib"))+    ,("AFP_W.addExtension (\"file\") (\".bib\") == (\"file.bib\")", property $ AFP_W.addExtension ("file") (".bib") == ("file.bib"))+    ,("P.addExtension \"/\" \"x\" == \"/.x\"", property $ P.addExtension "/" "x" == "/.x")+    ,("W.addExtension \"/\" \"x\" == \"/.x\"", property $ W.addExtension "/" "x" == "/.x")+    ,("AFP_P.addExtension (\"/\") (\"x\") == (\"/.x\")", property $ AFP_P.addExtension ("/") ("x") == ("/.x"))+    ,("AFP_W.addExtension (\"/\") (\"x\") == (\"/.x\")", property $ AFP_W.addExtension ("/") ("x") == ("/.x"))+    ,("P.addExtension x \"\" == x", property $ \(QFilePath x) -> P.addExtension x "" == x)+    ,("W.addExtension x \"\" == x", property $ \(QFilePath x) -> W.addExtension x "" == x)+    ,("AFP_P.addExtension x (\"\") == x", property $ \(QFilePathAFP_P x) -> AFP_P.addExtension x ("") == x)+    ,("AFP_W.addExtension x (\"\") == x", property $ \(QFilePathAFP_W x) -> AFP_W.addExtension x ("") == x)+    ,("P.takeFileName (P.addExtension (P.addTrailingPathSeparator x) \"ext\") == \".ext\"", property $ \(QFilePathValidP x) -> P.takeFileName (P.addExtension (P.addTrailingPathSeparator x) "ext") == ".ext")+    ,("W.takeFileName (W.addExtension (W.addTrailingPathSeparator x) \"ext\") == \".ext\"", property $ \(QFilePathValidW x) -> W.takeFileName (W.addExtension (W.addTrailingPathSeparator x) "ext") == ".ext")+    ,("AFP_P.takeFileName (AFP_P.addExtension (AFP_P.addTrailingPathSeparator x) (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_P x) -> AFP_P.takeFileName (AFP_P.addExtension (AFP_P.addTrailingPathSeparator x) ("ext")) == (".ext"))+    ,("AFP_W.takeFileName (AFP_W.addExtension (AFP_W.addTrailingPathSeparator x) (\"ext\")) == (\".ext\")", property $ \(QFilePathValidAFP_W x) -> AFP_W.takeFileName (AFP_W.addExtension (AFP_W.addTrailingPathSeparator x) ("ext")) == (".ext"))+    ,("W.addExtension \"\\\\\\\\share\" \".txt\" == \"\\\\\\\\share\\\\.txt\"", property $ W.addExtension "\\\\share" ".txt" == "\\\\share\\.txt")+    ,("AFP_W.addExtension (\"\\\\\\\\share\") (\".txt\") == (\"\\\\\\\\share\\\\.txt\")", property $ AFP_W.addExtension ("\\\\share") (".txt") == ("\\\\share\\.txt"))+    ,("P.hasExtension \"/directory/path.ext\" == True", property $ P.hasExtension "/directory/path.ext" == True)+    ,("W.hasExtension \"/directory/path.ext\" == True", property $ W.hasExtension "/directory/path.ext" == True)+    ,("AFP_P.hasExtension (\"/directory/path.ext\") == True", property $ AFP_P.hasExtension ("/directory/path.ext") == True)+    ,("AFP_W.hasExtension (\"/directory/path.ext\") == True", property $ AFP_W.hasExtension ("/directory/path.ext") == True)+    ,("P.hasExtension \"/directory/path\" == False", property $ P.hasExtension "/directory/path" == False)+    ,("W.hasExtension \"/directory/path\" == False", property $ W.hasExtension "/directory/path" == False)+    ,("AFP_P.hasExtension (\"/directory/path\") == False", property $ AFP_P.hasExtension ("/directory/path") == False)+    ,("AFP_W.hasExtension (\"/directory/path\") == False", property $ AFP_W.hasExtension ("/directory/path") == False)+    ,("null (P.takeExtension x) == not (P.hasExtension x)", property $ \(QFilePath x) -> null (P.takeExtension x) == not (P.hasExtension x))+    ,("null (W.takeExtension x) == not (W.hasExtension x)", property $ \(QFilePath x) -> null (W.takeExtension x) == not (W.hasExtension x))+    ,("(SBS.null . getPosixString) (AFP_P.takeExtension x) == not (AFP_P.hasExtension x)", property $ \(QFilePathAFP_P x) -> (SBS.null . getPosixString) (AFP_P.takeExtension x) == not (AFP_P.hasExtension x))+    ,("(SBS16.null . getWindowsString) (AFP_W.takeExtension x) == not (AFP_W.hasExtension x)", property $ \(QFilePathAFP_W x) -> (SBS16.null . getWindowsString) (AFP_W.takeExtension x) == not (AFP_W.hasExtension x))+    ,("\"png\" `P.isExtensionOf` \"/directory/file.png\" == True", property $ "png" `P.isExtensionOf` "/directory/file.png" == True)+    ,("\"png\" `W.isExtensionOf` \"/directory/file.png\" == True", property $ "png" `W.isExtensionOf` "/directory/file.png" == True)+    ,("(\"png\") `AFP_P.isExtensionOf` (\"/directory/file.png\") == True", property $ ("png") `AFP_P.isExtensionOf` ("/directory/file.png") == True)+    ,("(\"png\") `AFP_W.isExtensionOf` (\"/directory/file.png\") == True", property $ ("png") `AFP_W.isExtensionOf` ("/directory/file.png") == True)+    ,("\".png\" `P.isExtensionOf` \"/directory/file.png\" == True", property $ ".png" `P.isExtensionOf` "/directory/file.png" == True)+    ,("\".png\" `W.isExtensionOf` \"/directory/file.png\" == True", property $ ".png" `W.isExtensionOf` "/directory/file.png" == True)+    ,("(\".png\") `AFP_P.isExtensionOf` (\"/directory/file.png\") == True", property $ (".png") `AFP_P.isExtensionOf` ("/directory/file.png") == True)+    ,("(\".png\") `AFP_W.isExtensionOf` (\"/directory/file.png\") == True", property $ (".png") `AFP_W.isExtensionOf` ("/directory/file.png") == True)+    ,("\".tar.gz\" `P.isExtensionOf` \"bar/foo.tar.gz\" == True", property $ ".tar.gz" `P.isExtensionOf` "bar/foo.tar.gz" == True)+    ,("\".tar.gz\" `W.isExtensionOf` \"bar/foo.tar.gz\" == True", property $ ".tar.gz" `W.isExtensionOf` "bar/foo.tar.gz" == True)+    ,("(\".tar.gz\") `AFP_P.isExtensionOf` (\"bar/foo.tar.gz\") == True", property $ (".tar.gz") `AFP_P.isExtensionOf` ("bar/foo.tar.gz") == True)+    ,("(\".tar.gz\") `AFP_W.isExtensionOf` (\"bar/foo.tar.gz\") == True", property $ (".tar.gz") `AFP_W.isExtensionOf` ("bar/foo.tar.gz") == True)+    ,("\"ar.gz\" `P.isExtensionOf` \"bar/foo.tar.gz\" == False", property $ "ar.gz" `P.isExtensionOf` "bar/foo.tar.gz" == False)+    ,("\"ar.gz\" `W.isExtensionOf` \"bar/foo.tar.gz\" == False", property $ "ar.gz" `W.isExtensionOf` "bar/foo.tar.gz" == False)+    ,("(\"ar.gz\") `AFP_P.isExtensionOf` (\"bar/foo.tar.gz\") == False", property $ ("ar.gz") `AFP_P.isExtensionOf` ("bar/foo.tar.gz") == False)+    ,("(\"ar.gz\") `AFP_W.isExtensionOf` (\"bar/foo.tar.gz\") == False", property $ ("ar.gz") `AFP_W.isExtensionOf` ("bar/foo.tar.gz") == False)+    ,("\"png\" `P.isExtensionOf` \"/directory/file.png.jpg\" == False", property $ "png" `P.isExtensionOf` "/directory/file.png.jpg" == False)+    ,("\"png\" `W.isExtensionOf` \"/directory/file.png.jpg\" == False", property $ "png" `W.isExtensionOf` "/directory/file.png.jpg" == False)+    ,("(\"png\") `AFP_P.isExtensionOf` (\"/directory/file.png.jpg\") == False", property $ ("png") `AFP_P.isExtensionOf` ("/directory/file.png.jpg") == False)+    ,("(\"png\") `AFP_W.isExtensionOf` (\"/directory/file.png.jpg\") == False", property $ ("png") `AFP_W.isExtensionOf` ("/directory/file.png.jpg") == False)+    ,("\"csv/table.csv\" `P.isExtensionOf` \"/data/csv/table.csv\" == False", property $ "csv/table.csv" `P.isExtensionOf` "/data/csv/table.csv" == False)+    ,("\"csv/table.csv\" `W.isExtensionOf` \"/data/csv/table.csv\" == False", property $ "csv/table.csv" `W.isExtensionOf` "/data/csv/table.csv" == False)+    ,("(\"csv/table.csv\") `AFP_P.isExtensionOf` (\"/data/csv/table.csv\") == False", property $ ("csv/table.csv") `AFP_P.isExtensionOf` ("/data/csv/table.csv") == False)+    ,("(\"csv/table.csv\") `AFP_W.isExtensionOf` (\"/data/csv/table.csv\") == False", property $ ("csv/table.csv") `AFP_W.isExtensionOf` ("/data/csv/table.csv") == False)+    ,("P.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", property $ P.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")+    ,("W.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", property $ W.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")+    ,("AFP_P.stripExtension (\"hs.o\") (\"foo.x.hs.o\") == Just (\"foo.x\")", property $ AFP_P.stripExtension ("hs.o") ("foo.x.hs.o") == Just ("foo.x"))+    ,("AFP_W.stripExtension (\"hs.o\") (\"foo.x.hs.o\") == Just (\"foo.x\")", property $ AFP_W.stripExtension ("hs.o") ("foo.x.hs.o") == Just ("foo.x"))+    ,("P.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", property $ P.stripExtension "hi.o" "foo.x.hs.o" == Nothing)+    ,("W.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", property $ W.stripExtension "hi.o" "foo.x.hs.o" == Nothing)+    ,("AFP_P.stripExtension (\"hi.o\") (\"foo.x.hs.o\") == Nothing", property $ AFP_P.stripExtension ("hi.o") ("foo.x.hs.o") == Nothing)+    ,("AFP_W.stripExtension (\"hi.o\") (\"foo.x.hs.o\") == Nothing", property $ AFP_W.stripExtension ("hi.o") ("foo.x.hs.o") == Nothing)+    ,("P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x)", property $ \(QFilePath x) -> P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x))+    ,("W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x)", property $ \(QFilePath x) -> W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x))+    ,("AFP_P.dropExtension x == fromJust (AFP_P.stripExtension (AFP_P.takeExtension x) x)", property $ \(QFilePathAFP_P x) -> AFP_P.dropExtension x == fromJust (AFP_P.stripExtension (AFP_P.takeExtension x) x))+    ,("AFP_W.dropExtension x == fromJust (AFP_W.stripExtension (AFP_W.takeExtension x) x)", property $ \(QFilePathAFP_W x) -> AFP_W.dropExtension x == fromJust (AFP_W.stripExtension (AFP_W.takeExtension x) x))+    ,("P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x)", property $ \(QFilePath x) -> P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x))+    ,("W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x)", property $ \(QFilePath x) -> W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x))+    ,("AFP_P.dropExtensions x == fromJust (AFP_P.stripExtension (AFP_P.takeExtensions x) x)", property $ \(QFilePathAFP_P x) -> AFP_P.dropExtensions x == fromJust (AFP_P.stripExtension (AFP_P.takeExtensions x) x))+    ,("AFP_W.dropExtensions x == fromJust (AFP_W.stripExtension (AFP_W.takeExtensions x) x)", property $ \(QFilePathAFP_W x) -> AFP_W.dropExtensions x == fromJust (AFP_W.stripExtension (AFP_W.takeExtensions x) x))+    ,("P.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", property $ P.stripExtension ".c.d" "a.b.c.d" == Just "a.b")+    ,("W.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", property $ W.stripExtension ".c.d" "a.b.c.d" == Just "a.b")+    ,("AFP_P.stripExtension (\".c.d\") (\"a.b.c.d\") == Just (\"a.b\")", property $ AFP_P.stripExtension (".c.d") ("a.b.c.d") == Just ("a.b"))+    ,("AFP_W.stripExtension (\".c.d\") (\"a.b.c.d\") == Just (\"a.b\")", property $ AFP_W.stripExtension (".c.d") ("a.b.c.d") == Just ("a.b"))+    ,("P.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", property $ P.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")+    ,("W.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", property $ W.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")+    ,("AFP_P.stripExtension (\".c.d\") (\"a.b..c.d\") == Just (\"a.b.\")", property $ AFP_P.stripExtension (".c.d") ("a.b..c.d") == Just ("a.b."))+    ,("AFP_W.stripExtension (\".c.d\") (\"a.b..c.d\") == Just (\"a.b.\")", property $ AFP_W.stripExtension (".c.d") ("a.b..c.d") == Just ("a.b."))+    ,("P.stripExtension \"baz\" \"foo.bar\" == Nothing", property $ P.stripExtension "baz" "foo.bar" == Nothing)+    ,("W.stripExtension \"baz\" \"foo.bar\" == Nothing", property $ W.stripExtension "baz" "foo.bar" == Nothing)+    ,("AFP_P.stripExtension (\"baz\") (\"foo.bar\") == Nothing", property $ AFP_P.stripExtension ("baz") ("foo.bar") == Nothing)+    ,("AFP_W.stripExtension (\"baz\") (\"foo.bar\") == Nothing", property $ AFP_W.stripExtension ("baz") ("foo.bar") == Nothing)+    ,("P.stripExtension \"bar\" \"foobar\" == Nothing", property $ P.stripExtension "bar" "foobar" == Nothing)+    ,("W.stripExtension \"bar\" \"foobar\" == Nothing", property $ W.stripExtension "bar" "foobar" == Nothing)+    ,("AFP_P.stripExtension (\"bar\") (\"foobar\") == Nothing", property $ AFP_P.stripExtension ("bar") ("foobar") == Nothing)+    ,("AFP_W.stripExtension (\"bar\") (\"foobar\") == Nothing", property $ AFP_W.stripExtension ("bar") ("foobar") == Nothing)+    ,("P.stripExtension \"\" x == Just x", property $ \(QFilePath x) -> P.stripExtension "" x == Just x)+    ,("W.stripExtension \"\" x == Just x", property $ \(QFilePath x) -> W.stripExtension "" x == Just x)+    ,("AFP_P.stripExtension (\"\") x == Just x", property $ \(QFilePathAFP_P x) -> AFP_P.stripExtension ("") x == Just x)+    ,("AFP_W.stripExtension (\"\") x == Just x", property $ \(QFilePathAFP_W x) -> AFP_W.stripExtension ("") x == Just x)+    ,("P.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ P.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))+    ,("W.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", property $ W.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))+    ,("AFP_P.splitExtensions (\"/directory/path.ext\") == ((\"/directory/path\"), (\".ext\"))", property $ AFP_P.splitExtensions ("/directory/path.ext") == (("/directory/path"), (".ext")))+    ,("AFP_W.splitExtensions (\"/directory/path.ext\") == ((\"/directory/path\"), (\".ext\"))", property $ AFP_W.splitExtensions ("/directory/path.ext") == (("/directory/path"), (".ext")))+    ,("P.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ P.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))+    ,("W.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ W.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))+    ,("AFP_P.splitExtensions (\"file.tar.gz\") == ((\"file\"), (\".tar.gz\"))", property $ AFP_P.splitExtensions ("file.tar.gz") == (("file"), (".tar.gz")))+    ,("AFP_W.splitExtensions (\"file.tar.gz\") == ((\"file\"), (\".tar.gz\"))", property $ AFP_W.splitExtensions ("file.tar.gz") == (("file"), (".tar.gz")))+    ,("uncurry (<>) (P.splitExtensions x) == x", property $ \(QFilePath x) -> uncurry (<>) (P.splitExtensions x) == x)+    ,("uncurry (<>) (W.splitExtensions x) == x", property $ \(QFilePath x) -> uncurry (<>) (W.splitExtensions x) == x)+    ,("uncurry (<>) (AFP_P.splitExtensions x) == x", property $ \(QFilePathAFP_P x) -> uncurry (<>) (AFP_P.splitExtensions x) == x)+    ,("uncurry (<>) (AFP_W.splitExtensions x) == x", property $ \(QFilePathAFP_W x) -> uncurry (<>) (AFP_W.splitExtensions x) == x)+    ,("uncurry P.addExtension (P.splitExtensions x) == x", property $ \(QFilePathValidP x) -> uncurry P.addExtension (P.splitExtensions x) == x)+    ,("uncurry W.addExtension (W.splitExtensions x) == x", property $ \(QFilePathValidW x) -> uncurry W.addExtension (W.splitExtensions x) == x)+    ,("uncurry AFP_P.addExtension (AFP_P.splitExtensions x) == x", property $ \(QFilePathValidAFP_P x) -> uncurry AFP_P.addExtension (AFP_P.splitExtensions x) == x)+    ,("uncurry AFP_W.addExtension (AFP_W.splitExtensions x) == x", property $ \(QFilePathValidAFP_W x) -> uncurry AFP_W.addExtension (AFP_W.splitExtensions x) == x)+    ,("P.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ P.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))+    ,("W.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", property $ W.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))+    ,("AFP_P.splitExtensions (\"file.tar.gz\") == ((\"file\"), (\".tar.gz\"))", property $ AFP_P.splitExtensions ("file.tar.gz") == (("file"), (".tar.gz")))+    ,("AFP_W.splitExtensions (\"file.tar.gz\") == ((\"file\"), (\".tar.gz\"))", property $ AFP_W.splitExtensions ("file.tar.gz") == (("file"), (".tar.gz")))+    ,("P.dropExtensions \"/directory/path.ext\" == \"/directory/path\"", property $ P.dropExtensions "/directory/path.ext" == "/directory/path")+    ,("W.dropExtensions \"/directory/path.ext\" == \"/directory/path\"", property $ W.dropExtensions "/directory/path.ext" == "/directory/path")+    ,("AFP_P.dropExtensions (\"/directory/path.ext\") == (\"/directory/path\")", property $ AFP_P.dropExtensions ("/directory/path.ext") == ("/directory/path"))+    ,("AFP_W.dropExtensions (\"/directory/path.ext\") == (\"/directory/path\")", property $ AFP_W.dropExtensions ("/directory/path.ext") == ("/directory/path"))+    ,("P.dropExtensions \"file.tar.gz\" == \"file\"", property $ P.dropExtensions "file.tar.gz" == "file")+    ,("W.dropExtensions \"file.tar.gz\" == \"file\"", property $ W.dropExtensions "file.tar.gz" == "file")+    ,("AFP_P.dropExtensions (\"file.tar.gz\") == (\"file\")", property $ AFP_P.dropExtensions ("file.tar.gz") == ("file"))+    ,("AFP_W.dropExtensions (\"file.tar.gz\") == (\"file\")", property $ AFP_W.dropExtensions ("file.tar.gz") == ("file"))+    ,("not $ P.hasExtension $ P.dropExtensions x", property $ \(QFilePath x) -> not $ P.hasExtension $ P.dropExtensions x)+    ,("not $ W.hasExtension $ W.dropExtensions x", property $ \(QFilePath x) -> not $ W.hasExtension $ W.dropExtensions x)+    ,("not $ AFP_P.hasExtension $ AFP_P.dropExtensions x", property $ \(QFilePathAFP_P x) -> not $ AFP_P.hasExtension $ AFP_P.dropExtensions x)+    ,("not $ AFP_W.hasExtension $ AFP_W.dropExtensions x", property $ \(QFilePathAFP_W x) -> not $ AFP_W.hasExtension $ AFP_W.dropExtensions x)+    ,("not $ any P.isExtSeparator $ P.takeFileName $ P.dropExtensions x", property $ \(QFilePath x) -> not $ any P.isExtSeparator $ P.takeFileName $ P.dropExtensions x)+    ,("not $ any W.isExtSeparator $ W.takeFileName $ W.dropExtensions x", property $ \(QFilePath x) -> not $ any W.isExtSeparator $ W.takeFileName $ W.dropExtensions x)+    ,("not $ (\\f (getPosixString -> x) -> SBS.any (f . PW) x) AFP_P.isExtSeparator $ AFP_P.takeFileName $ AFP_P.dropExtensions x", property $ \(QFilePathAFP_P x) -> not $ (\f (getPosixString -> x) -> SBS.any (f . PW) x) AFP_P.isExtSeparator $ AFP_P.takeFileName $ AFP_P.dropExtensions x)+    ,("not $ (\\f (getWindowsString -> x) -> SBS16.any (f . WW) x) AFP_W.isExtSeparator $ AFP_W.takeFileName $ AFP_W.dropExtensions x", property $ \(QFilePathAFP_W x) -> not $ (\f (getWindowsString -> x) -> SBS16.any (f . WW) x) AFP_W.isExtSeparator $ AFP_W.takeFileName $ AFP_W.dropExtensions x)+    ,("P.takeExtensions \"/directory/path.ext\" == \".ext\"", property $ P.takeExtensions "/directory/path.ext" == ".ext")+    ,("W.takeExtensions \"/directory/path.ext\" == \".ext\"", property $ W.takeExtensions "/directory/path.ext" == ".ext")+    ,("AFP_P.takeExtensions (\"/directory/path.ext\") == (\".ext\")", property $ AFP_P.takeExtensions ("/directory/path.ext") == (".ext"))+    ,("AFP_W.takeExtensions (\"/directory/path.ext\") == (\".ext\")", property $ AFP_W.takeExtensions ("/directory/path.ext") == (".ext"))+    ,("P.takeExtensions \"file.tar.gz\" == \".tar.gz\"", property $ P.takeExtensions "file.tar.gz" == ".tar.gz")+    ,("W.takeExtensions \"file.tar.gz\" == \".tar.gz\"", property $ W.takeExtensions "file.tar.gz" == ".tar.gz")+    ,("AFP_P.takeExtensions (\"file.tar.gz\") == (\".tar.gz\")", property $ AFP_P.takeExtensions ("file.tar.gz") == (".tar.gz"))+    ,("AFP_W.takeExtensions (\"file.tar.gz\") == (\".tar.gz\")", property $ AFP_W.takeExtensions ("file.tar.gz") == (".tar.gz"))+    ,("P.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", property $ P.replaceExtensions "file.fred.bob" "txt" == "file.txt")+    ,("W.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", property $ W.replaceExtensions "file.fred.bob" "txt" == "file.txt")+    ,("AFP_P.replaceExtensions (\"file.fred.bob\") (\"txt\") == (\"file.txt\")", property $ AFP_P.replaceExtensions ("file.fred.bob") ("txt") == ("file.txt"))+    ,("AFP_W.replaceExtensions (\"file.fred.bob\") (\"txt\") == (\"file.txt\")", property $ AFP_W.replaceExtensions ("file.fred.bob") ("txt") == ("file.txt"))+    ,("P.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", property $ P.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")+    ,("W.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", property $ W.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")+    ,("AFP_P.replaceExtensions (\"file.fred.bob\") (\"tar.gz\") == (\"file.tar.gz\")", property $ AFP_P.replaceExtensions ("file.fred.bob") ("tar.gz") == ("file.tar.gz"))+    ,("AFP_W.replaceExtensions (\"file.fred.bob\") (\"tar.gz\") == (\"file.tar.gz\")", property $ AFP_W.replaceExtensions ("file.fred.bob") ("tar.gz") == ("file.tar.gz"))+    ,("uncurry (<>) (P.splitDrive x) == x", property $ \(QFilePath x) -> uncurry (<>) (P.splitDrive x) == x)+    ,("uncurry (<>) (W.splitDrive x) == x", property $ \(QFilePath x) -> uncurry (<>) (W.splitDrive x) == x)+    ,("uncurry (<>) (AFP_P.splitDrive x) == x", property $ \(QFilePathAFP_P x) -> uncurry (<>) (AFP_P.splitDrive x) == x)+    ,("uncurry (<>) (AFP_W.splitDrive x) == x", property $ \(QFilePathAFP_W x) -> uncurry (<>) (AFP_W.splitDrive x) == x)+    ,("W.splitDrive \"file\" == (\"\", \"file\")", property $ W.splitDrive "file" == ("", "file"))+    ,("AFP_W.splitDrive (\"file\") == ((\"\"), (\"file\"))", property $ AFP_W.splitDrive ("file") == ((""), ("file")))+    ,("W.splitDrive \"c:/file\" == (\"c:/\", \"file\")", property $ W.splitDrive "c:/file" == ("c:/", "file"))+    ,("AFP_W.splitDrive (\"c:/file\") == ((\"c:/\"), (\"file\"))", property $ AFP_W.splitDrive ("c:/file") == (("c:/"), ("file")))+    ,("W.splitDrive \"c:\\\\file\" == (\"c:\\\\\", \"file\")", property $ W.splitDrive "c:\\file" == ("c:\\", "file"))+    ,("AFP_W.splitDrive (\"c:\\\\file\") == ((\"c:\\\\\"), (\"file\"))", property $ AFP_W.splitDrive ("c:\\file") == (("c:\\"), ("file")))+    ,("W.splitDrive \"\\\\\\\\shared\\\\test\" == (\"\\\\\\\\shared\\\\\", \"test\")", property $ W.splitDrive "\\\\shared\\test" == ("\\\\shared\\", "test"))+    ,("AFP_W.splitDrive (\"\\\\\\\\shared\\\\test\") == ((\"\\\\\\\\shared\\\\\"), (\"test\"))", property $ AFP_W.splitDrive ("\\\\shared\\test") == (("\\\\shared\\"), ("test")))+    ,("W.splitDrive \"\\\\\\\\shared\" == (\"\\\\\\\\shared\", \"\")", property $ W.splitDrive "\\\\shared" == ("\\\\shared", ""))+    ,("AFP_W.splitDrive (\"\\\\\\\\shared\") == ((\"\\\\\\\\shared\"), (\"\"))", property $ AFP_W.splitDrive ("\\\\shared") == (("\\\\shared"), ("")))+    ,("W.splitDrive \"\\\\\\\\?\\\\UNC\\\\shared\\\\file\" == (\"\\\\\\\\?\\\\UNC\\\\shared\\\\\", \"file\")", property $ W.splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\", "file"))+    ,("AFP_W.splitDrive (\"\\\\\\\\?\\\\UNC\\\\shared\\\\file\") == ((\"\\\\\\\\?\\\\UNC\\\\shared\\\\\"), (\"file\"))", property $ AFP_W.splitDrive ("\\\\?\\UNC\\shared\\file") == (("\\\\?\\UNC\\shared\\"), ("file")))+    ,("W.splitDrive \"\\\\\\\\?\\\\UNCshared\\\\file\" == (\"\\\\\\\\?\\\\\", \"UNCshared\\\\file\")", property $ W.splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\", "UNCshared\\file"))+    ,("AFP_W.splitDrive (\"\\\\\\\\?\\\\UNCshared\\\\file\") == ((\"\\\\\\\\?\\\\\"), (\"UNCshared\\\\file\"))", property $ AFP_W.splitDrive ("\\\\?\\UNCshared\\file") == (("\\\\?\\"), ("UNCshared\\file")))+    ,("W.splitDrive \"\\\\\\\\?\\\\d:\\\\file\" == (\"\\\\\\\\?\\\\d:\\\\\", \"file\")", property $ W.splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\", "file"))+    ,("AFP_W.splitDrive (\"\\\\\\\\?\\\\d:\\\\file\") == ((\"\\\\\\\\?\\\\d:\\\\\"), (\"file\"))", property $ AFP_W.splitDrive ("\\\\?\\d:\\file") == (("\\\\?\\d:\\"), ("file")))+    ,("W.splitDrive \"/d\" == (\"\", \"/d\")", property $ W.splitDrive "/d" == ("", "/d"))+    ,("AFP_W.splitDrive (\"/d\") == ((\"\"), (\"/d\"))", property $ AFP_W.splitDrive ("/d") == ((""), ("/d")))+    ,("P.splitDrive \"/test\" == (\"/\", \"test\")", property $ P.splitDrive "/test" == ("/", "test"))+    ,("AFP_P.splitDrive (\"/test\") == ((\"/\"), (\"test\"))", property $ AFP_P.splitDrive ("/test") == (("/"), ("test")))+    ,("P.splitDrive \"//test\" == (\"//\", \"test\")", property $ P.splitDrive "//test" == ("//", "test"))+    ,("AFP_P.splitDrive (\"//test\") == ((\"//\"), (\"test\"))", property $ AFP_P.splitDrive ("//test") == (("//"), ("test")))+    ,("P.splitDrive \"test/file\" == (\"\", \"test/file\")", property $ P.splitDrive "test/file" == ("", "test/file"))+    ,("AFP_P.splitDrive (\"test/file\") == ((\"\"), (\"test/file\"))", property $ AFP_P.splitDrive ("test/file") == ((""), ("test/file")))+    ,("P.splitDrive \"file\" == (\"\", \"file\")", property $ P.splitDrive "file" == ("", "file"))+    ,("AFP_P.splitDrive (\"file\") == ((\"\"), (\"file\"))", property $ AFP_P.splitDrive ("file") == ((""), ("file")))+    ,("uncurry P.joinDrive (P.splitDrive x) == x", property $ \(QFilePathValidP x) -> uncurry P.joinDrive (P.splitDrive x) == x)+    ,("uncurry W.joinDrive (W.splitDrive x) == x", property $ \(QFilePathValidW x) -> uncurry W.joinDrive (W.splitDrive x) == x)+    ,("uncurry AFP_P.joinDrive (AFP_P.splitDrive x) == x", property $ \(QFilePathValidAFP_P x) -> uncurry AFP_P.joinDrive (AFP_P.splitDrive x) == x)+    ,("uncurry AFP_W.joinDrive (AFP_W.splitDrive x) == x", property $ \(QFilePathValidAFP_W x) -> uncurry AFP_W.joinDrive (AFP_W.splitDrive x) == x)+    ,("W.joinDrive \"C:\" \"foo\" == \"C:foo\"", property $ W.joinDrive "C:" "foo" == "C:foo")+    ,("AFP_W.joinDrive (\"C:\") (\"foo\") == (\"C:foo\")", property $ AFP_W.joinDrive ("C:") ("foo") == ("C:foo"))+    ,("W.joinDrive \"C:\\\\\" \"bar\" == \"C:\\\\bar\"", property $ W.joinDrive "C:\\" "bar" == "C:\\bar")+    ,("AFP_W.joinDrive (\"C:\\\\\") (\"bar\") == (\"C:\\\\bar\")", property $ AFP_W.joinDrive ("C:\\") ("bar") == ("C:\\bar"))+    ,("W.joinDrive \"\\\\\\\\share\" \"foo\" == \"\\\\\\\\share\\\\foo\"", property $ W.joinDrive "\\\\share" "foo" == "\\\\share\\foo")+    ,("AFP_W.joinDrive (\"\\\\\\\\share\") (\"foo\") == (\"\\\\\\\\share\\\\foo\")", property $ AFP_W.joinDrive ("\\\\share") ("foo") == ("\\\\share\\foo"))+    ,("W.joinDrive \"/:\" \"foo\" == \"/:\\\\foo\"", property $ W.joinDrive "/:" "foo" == "/:\\foo")+    ,("AFP_W.joinDrive (\"/:\") (\"foo\") == (\"/:\\\\foo\")", property $ AFP_W.joinDrive ("/:") ("foo") == ("/:\\foo"))+    ,("P.takeDrive x == fst (P.splitDrive x)", property $ \(QFilePath x) -> P.takeDrive x == fst (P.splitDrive x))+    ,("W.takeDrive x == fst (W.splitDrive x)", property $ \(QFilePath x) -> W.takeDrive x == fst (W.splitDrive x))+    ,("AFP_P.takeDrive x == fst (AFP_P.splitDrive x)", property $ \(QFilePathAFP_P x) -> AFP_P.takeDrive x == fst (AFP_P.splitDrive x))+    ,("AFP_W.takeDrive x == fst (AFP_W.splitDrive x)", property $ \(QFilePathAFP_W x) -> AFP_W.takeDrive x == fst (AFP_W.splitDrive x))+    ,("P.dropDrive x == snd (P.splitDrive x)", property $ \(QFilePath x) -> P.dropDrive x == snd (P.splitDrive x))+    ,("W.dropDrive x == snd (W.splitDrive x)", property $ \(QFilePath x) -> W.dropDrive x == snd (W.splitDrive x))+    ,("AFP_P.dropDrive x == snd (AFP_P.splitDrive x)", property $ \(QFilePathAFP_P x) -> AFP_P.dropDrive x == snd (AFP_P.splitDrive x))+    ,("AFP_W.dropDrive x == snd (AFP_W.splitDrive x)", property $ \(QFilePathAFP_W x) -> AFP_W.dropDrive x == snd (AFP_W.splitDrive x))+    ,("not (P.hasDrive x) == null (P.takeDrive x)", property $ \(QFilePath x) -> not (P.hasDrive x) == null (P.takeDrive x))+    ,("not (W.hasDrive x) == null (W.takeDrive x)", property $ \(QFilePath x) -> not (W.hasDrive x) == null (W.takeDrive x))+    ,("not (AFP_P.hasDrive x) == (SBS.null . getPosixString) (AFP_P.takeDrive x)", property $ \(QFilePathAFP_P x) -> not (AFP_P.hasDrive x) == (SBS.null . getPosixString) (AFP_P.takeDrive x))+    ,("not (AFP_W.hasDrive x) == (SBS16.null . getWindowsString) (AFP_W.takeDrive x)", property $ \(QFilePathAFP_W x) -> not (AFP_W.hasDrive x) == (SBS16.null . getWindowsString) (AFP_W.takeDrive x))+    ,("P.hasDrive \"/foo\" == True", property $ P.hasDrive "/foo" == True)+    ,("AFP_P.hasDrive (\"/foo\") == True", property $ AFP_P.hasDrive ("/foo") == True)+    ,("W.hasDrive \"C:\\\\foo\" == True", property $ W.hasDrive "C:\\foo" == True)+    ,("AFP_W.hasDrive (\"C:\\\\foo\") == True", property $ AFP_W.hasDrive ("C:\\foo") == True)+    ,("W.hasDrive \"C:foo\" == True", property $ W.hasDrive "C:foo" == True)+    ,("AFP_W.hasDrive (\"C:foo\") == True", property $ AFP_W.hasDrive ("C:foo") == True)+    ,("P.hasDrive \"foo\" == False", property $ P.hasDrive "foo" == False)+    ,("W.hasDrive \"foo\" == False", property $ W.hasDrive "foo" == False)+    ,("AFP_P.hasDrive (\"foo\") == False", property $ AFP_P.hasDrive ("foo") == False)+    ,("AFP_W.hasDrive (\"foo\") == False", property $ AFP_W.hasDrive ("foo") == False)+    ,("P.hasDrive \"\" == False", property $ P.hasDrive "" == False)+    ,("W.hasDrive \"\" == False", property $ W.hasDrive "" == False)+    ,("AFP_P.hasDrive (\"\") == False", property $ AFP_P.hasDrive ("") == False)+    ,("AFP_W.hasDrive (\"\") == False", property $ AFP_W.hasDrive ("") == False)+    ,("P.isDrive \"/\" == True", property $ P.isDrive "/" == True)+    ,("AFP_P.isDrive (\"/\") == True", property $ AFP_P.isDrive ("/") == True)+    ,("P.isDrive \"/foo\" == False", property $ P.isDrive "/foo" == False)+    ,("AFP_P.isDrive (\"/foo\") == False", property $ AFP_P.isDrive ("/foo") == False)+    ,("W.isDrive \"C:\\\\\" == True", property $ W.isDrive "C:\\" == True)+    ,("AFP_W.isDrive (\"C:\\\\\") == True", property $ AFP_W.isDrive ("C:\\") == True)+    ,("W.isDrive \"C:\\\\foo\" == False", property $ W.isDrive "C:\\foo" == False)+    ,("AFP_W.isDrive (\"C:\\\\foo\") == False", property $ AFP_W.isDrive ("C:\\foo") == False)+    ,("P.isDrive \"\" == False", property $ P.isDrive "" == False)+    ,("W.isDrive \"\" == False", property $ W.isDrive "" == False)+    ,("AFP_P.isDrive (\"\") == False", property $ AFP_P.isDrive ("") == False)+    ,("AFP_W.isDrive (\"\") == False", property $ AFP_W.isDrive ("") == False)+    ,("P.splitFileName \"/directory/file.ext\" == (\"/directory/\", \"file.ext\")", property $ P.splitFileName "/directory/file.ext" == ("/directory/", "file.ext"))+    ,("W.splitFileName \"/directory/file.ext\" == (\"/directory/\", \"file.ext\")", property $ W.splitFileName "/directory/file.ext" == ("/directory/", "file.ext"))+    ,("AFP_P.splitFileName (\"/directory/file.ext\") == ((\"/directory/\"), (\"file.ext\"))", property $ AFP_P.splitFileName ("/directory/file.ext") == (("/directory/"), ("file.ext")))+    ,("AFP_W.splitFileName (\"/directory/file.ext\") == ((\"/directory/\"), (\"file.ext\"))", property $ AFP_W.splitFileName ("/directory/file.ext") == (("/directory/"), ("file.ext")))+    ,("uncurry (P.</>) (P.splitFileName x) == x || fst (P.splitFileName x) == \"./\"", property $ \(QFilePathValidP x) -> uncurry (P.</>) (P.splitFileName x) == x || fst (P.splitFileName x) == "./")+    ,("uncurry (W.</>) (W.splitFileName x) == x || fst (W.splitFileName x) == \"./\"", property $ \(QFilePathValidW x) -> uncurry (W.</>) (W.splitFileName x) == x || fst (W.splitFileName x) == "./")+    ,("uncurry (AFP_P.</>) (AFP_P.splitFileName x) == x || fst (AFP_P.splitFileName x) == (\"./\")", property $ \(QFilePathValidAFP_P x) -> uncurry (AFP_P.</>) (AFP_P.splitFileName x) == x || fst (AFP_P.splitFileName x) == ("./"))+    ,("uncurry (AFP_W.</>) (AFP_W.splitFileName x) == x || fst (AFP_W.splitFileName x) == (\"./\")", property $ \(QFilePathValidAFP_W x) -> uncurry (AFP_W.</>) (AFP_W.splitFileName x) == x || fst (AFP_W.splitFileName x) == ("./"))+    ,("P.isValid (fst (P.splitFileName x))", property $ \(QFilePathValidP x) -> P.isValid (fst (P.splitFileName x)))+    ,("W.isValid (fst (W.splitFileName x))", property $ \(QFilePathValidW x) -> W.isValid (fst (W.splitFileName x)))+    ,("AFP_P.isValid (fst (AFP_P.splitFileName x))", property $ \(QFilePathValidAFP_P x) -> AFP_P.isValid (fst (AFP_P.splitFileName x)))+    ,("AFP_W.isValid (fst (AFP_W.splitFileName x))", property $ \(QFilePathValidAFP_W x) -> AFP_W.isValid (fst (AFP_W.splitFileName x)))+    ,("P.splitFileName \"file/bob.txt\" == (\"file/\", \"bob.txt\")", property $ P.splitFileName "file/bob.txt" == ("file/", "bob.txt"))+    ,("W.splitFileName \"file/bob.txt\" == (\"file/\", \"bob.txt\")", property $ W.splitFileName "file/bob.txt" == ("file/", "bob.txt"))+    ,("AFP_P.splitFileName (\"file/bob.txt\") == ((\"file/\"), (\"bob.txt\"))", property $ AFP_P.splitFileName ("file/bob.txt") == (("file/"), ("bob.txt")))+    ,("AFP_W.splitFileName (\"file/bob.txt\") == ((\"file/\"), (\"bob.txt\"))", property $ AFP_W.splitFileName ("file/bob.txt") == (("file/"), ("bob.txt")))+    ,("P.splitFileName \"file/\" == (\"file/\", \"\")", property $ P.splitFileName "file/" == ("file/", ""))+    ,("W.splitFileName \"file/\" == (\"file/\", \"\")", property $ W.splitFileName "file/" == ("file/", ""))+    ,("AFP_P.splitFileName (\"file/\") == ((\"file/\"), (\"\"))", property $ AFP_P.splitFileName ("file/") == (("file/"), ("")))+    ,("AFP_W.splitFileName (\"file/\") == ((\"file/\"), (\"\"))", property $ AFP_W.splitFileName ("file/") == (("file/"), ("")))+    ,("P.splitFileName \"bob\" == (\"./\", \"bob\")", property $ P.splitFileName "bob" == ("./", "bob"))+    ,("W.splitFileName \"bob\" == (\"./\", \"bob\")", property $ W.splitFileName "bob" == ("./", "bob"))+    ,("AFP_P.splitFileName (\"bob\") == ((\"./\"), (\"bob\"))", property $ AFP_P.splitFileName ("bob") == (("./"), ("bob")))+    ,("AFP_W.splitFileName (\"bob\") == ((\"./\"), (\"bob\"))", property $ AFP_W.splitFileName ("bob") == (("./"), ("bob")))+    ,("P.splitFileName \"/\" == (\"/\", \"\")", property $ P.splitFileName "/" == ("/", ""))+    ,("AFP_P.splitFileName (\"/\") == ((\"/\"), (\"\"))", property $ AFP_P.splitFileName ("/") == (("/"), ("")))+    ,("W.splitFileName \"c:\" == (\"c:\", \"\")", property $ W.splitFileName "c:" == ("c:", ""))+    ,("AFP_W.splitFileName (\"c:\") == ((\"c:\"), (\"\"))", property $ AFP_W.splitFileName ("c:") == (("c:"), ("")))+    ,("P.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ P.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")+    ,("W.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ W.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")+    ,("AFP_P.replaceFileName (\"/directory/other.txt\") (\"file.ext\") == (\"/directory/file.ext\")", property $ AFP_P.replaceFileName ("/directory/other.txt") ("file.ext") == ("/directory/file.ext"))+    ,("AFP_W.replaceFileName (\"/directory/other.txt\") (\"file.ext\") == (\"/directory/file.ext\")", property $ AFP_W.replaceFileName ("/directory/other.txt") ("file.ext") == ("/directory/file.ext"))+    ,("P.replaceFileName x (P.takeFileName x) == x", property $ \(QFilePathValidP x) -> P.replaceFileName x (P.takeFileName x) == x)+    ,("W.replaceFileName x (W.takeFileName x) == x", property $ \(QFilePathValidW x) -> W.replaceFileName x (W.takeFileName x) == x)+    ,("AFP_P.replaceFileName x (AFP_P.takeFileName x) == x", property $ \(QFilePathValidAFP_P x) -> AFP_P.replaceFileName x (AFP_P.takeFileName x) == x)+    ,("AFP_W.replaceFileName x (AFP_W.takeFileName x) == x", property $ \(QFilePathValidAFP_W x) -> AFP_W.replaceFileName x (AFP_W.takeFileName x) == x)+    ,("P.dropFileName \"/directory/file.ext\" == \"/directory/\"", property $ P.dropFileName "/directory/file.ext" == "/directory/")+    ,("W.dropFileName \"/directory/file.ext\" == \"/directory/\"", property $ W.dropFileName "/directory/file.ext" == "/directory/")+    ,("AFP_P.dropFileName (\"/directory/file.ext\") == (\"/directory/\")", property $ AFP_P.dropFileName ("/directory/file.ext") == ("/directory/"))+    ,("AFP_W.dropFileName (\"/directory/file.ext\") == (\"/directory/\")", property $ AFP_W.dropFileName ("/directory/file.ext") == ("/directory/"))+    ,("P.dropFileName x == fst (P.splitFileName x)", property $ \(QFilePath x) -> P.dropFileName x == fst (P.splitFileName x))+    ,("W.dropFileName x == fst (W.splitFileName x)", property $ \(QFilePath x) -> W.dropFileName x == fst (W.splitFileName x))+    ,("AFP_P.dropFileName x == fst (AFP_P.splitFileName x)", property $ \(QFilePathAFP_P x) -> AFP_P.dropFileName x == fst (AFP_P.splitFileName x))+    ,("AFP_W.dropFileName x == fst (AFP_W.splitFileName x)", property $ \(QFilePathAFP_W x) -> AFP_W.dropFileName x == fst (AFP_W.splitFileName x))+    ,("P.takeFileName \"/directory/file.ext\" == \"file.ext\"", property $ P.takeFileName "/directory/file.ext" == "file.ext")+    ,("W.takeFileName \"/directory/file.ext\" == \"file.ext\"", property $ W.takeFileName "/directory/file.ext" == "file.ext")+    ,("AFP_P.takeFileName (\"/directory/file.ext\") == (\"file.ext\")", property $ AFP_P.takeFileName ("/directory/file.ext") == ("file.ext"))+    ,("AFP_W.takeFileName (\"/directory/file.ext\") == (\"file.ext\")", property $ AFP_W.takeFileName ("/directory/file.ext") == ("file.ext"))+    ,("P.takeFileName \"test/\" == \"\"", property $ P.takeFileName "test/" == "")+    ,("W.takeFileName \"test/\" == \"\"", property $ W.takeFileName "test/" == "")+    ,("AFP_P.takeFileName (\"test/\") == (\"\")", property $ AFP_P.takeFileName ("test/") == (""))+    ,("AFP_W.takeFileName (\"test/\") == (\"\")", property $ AFP_W.takeFileName ("test/") == (""))+    ,("isSuffixOf (P.takeFileName x) x", property $ \(QFilePath x) -> isSuffixOf (P.takeFileName x) x)+    ,("isSuffixOf (W.takeFileName x) x", property $ \(QFilePath x) -> isSuffixOf (W.takeFileName x) x)+    ,("(\\(getPosixString -> x) (getPosixString -> y) -> SBS.isSuffixOf x y) (AFP_P.takeFileName x) x", property $ \(QFilePathAFP_P x) -> (\(getPosixString -> x) (getPosixString -> y) -> SBS.isSuffixOf x y) (AFP_P.takeFileName x) x)+    ,("(\\(getWindowsString -> x) (getWindowsString -> y) -> SBS16.isSuffixOf x y) (AFP_W.takeFileName x) x", property $ \(QFilePathAFP_W x) -> (\(getWindowsString -> x) (getWindowsString -> y) -> SBS16.isSuffixOf x y) (AFP_W.takeFileName x) x)+    ,("P.takeFileName x == snd (P.splitFileName x)", property $ \(QFilePath x) -> P.takeFileName x == snd (P.splitFileName x))+    ,("W.takeFileName x == snd (W.splitFileName x)", property $ \(QFilePath x) -> W.takeFileName x == snd (W.splitFileName x))+    ,("AFP_P.takeFileName x == snd (AFP_P.splitFileName x)", property $ \(QFilePathAFP_P x) -> AFP_P.takeFileName x == snd (AFP_P.splitFileName x))+    ,("AFP_W.takeFileName x == snd (AFP_W.splitFileName x)", property $ \(QFilePathAFP_W x) -> AFP_W.takeFileName x == snd (AFP_W.splitFileName x))+    ,("P.takeFileName (P.replaceFileName x \"fred\") == \"fred\"", property $ \(QFilePathValidP x) -> P.takeFileName (P.replaceFileName x "fred") == "fred")+    ,("W.takeFileName (W.replaceFileName x \"fred\") == \"fred\"", property $ \(QFilePathValidW x) -> W.takeFileName (W.replaceFileName x "fred") == "fred")+    ,("AFP_P.takeFileName (AFP_P.replaceFileName x (\"fred\")) == (\"fred\")", property $ \(QFilePathValidAFP_P x) -> AFP_P.takeFileName (AFP_P.replaceFileName x ("fred")) == ("fred"))+    ,("AFP_W.takeFileName (AFP_W.replaceFileName x (\"fred\")) == (\"fred\")", property $ \(QFilePathValidAFP_W x) -> AFP_W.takeFileName (AFP_W.replaceFileName x ("fred")) == ("fred"))+    ,("P.takeFileName (x P.</> \"fred\") == \"fred\"", property $ \(QFilePathValidP x) -> P.takeFileName (x P.</> "fred") == "fred")+    ,("W.takeFileName (x W.</> \"fred\") == \"fred\"", property $ \(QFilePathValidW x) -> W.takeFileName (x W.</> "fred") == "fred")+    ,("AFP_P.takeFileName (x AFP_P.</> (\"fred\")) == (\"fred\")", property $ \(QFilePathValidAFP_P x) -> AFP_P.takeFileName (x AFP_P.</> ("fred")) == ("fred"))+    ,("AFP_W.takeFileName (x AFP_W.</> (\"fred\")) == (\"fred\")", property $ \(QFilePathValidAFP_W x) -> AFP_W.takeFileName (x AFP_W.</> ("fred")) == ("fred"))+    ,("P.isRelative (P.takeFileName x)", property $ \(QFilePathValidP x) -> P.isRelative (P.takeFileName x))+    ,("W.isRelative (W.takeFileName x)", property $ \(QFilePathValidW x) -> W.isRelative (W.takeFileName x))+    ,("AFP_P.isRelative (AFP_P.takeFileName x)", property $ \(QFilePathValidAFP_P x) -> AFP_P.isRelative (AFP_P.takeFileName x))+    ,("AFP_W.isRelative (AFP_W.takeFileName x)", property $ \(QFilePathValidAFP_W x) -> AFP_W.isRelative (AFP_W.takeFileName x))+    ,("P.takeBaseName \"/directory/file.ext\" == \"file\"", property $ P.takeBaseName "/directory/file.ext" == "file")+    ,("W.takeBaseName \"/directory/file.ext\" == \"file\"", property $ W.takeBaseName "/directory/file.ext" == "file")+    ,("AFP_P.takeBaseName (\"/directory/file.ext\") == (\"file\")", property $ AFP_P.takeBaseName ("/directory/file.ext") == ("file"))+    ,("AFP_W.takeBaseName (\"/directory/file.ext\") == (\"file\")", property $ AFP_W.takeBaseName ("/directory/file.ext") == ("file"))+    ,("P.takeBaseName \"file/test.txt\" == \"test\"", property $ P.takeBaseName "file/test.txt" == "test")+    ,("W.takeBaseName \"file/test.txt\" == \"test\"", property $ W.takeBaseName "file/test.txt" == "test")+    ,("AFP_P.takeBaseName (\"file/test.txt\") == (\"test\")", property $ AFP_P.takeBaseName ("file/test.txt") == ("test"))+    ,("AFP_W.takeBaseName (\"file/test.txt\") == (\"test\")", property $ AFP_W.takeBaseName ("file/test.txt") == ("test"))+    ,("P.takeBaseName \"dave.ext\" == \"dave\"", property $ P.takeBaseName "dave.ext" == "dave")+    ,("W.takeBaseName \"dave.ext\" == \"dave\"", property $ W.takeBaseName "dave.ext" == "dave")+    ,("AFP_P.takeBaseName (\"dave.ext\") == (\"dave\")", property $ AFP_P.takeBaseName ("dave.ext") == ("dave"))+    ,("AFP_W.takeBaseName (\"dave.ext\") == (\"dave\")", property $ AFP_W.takeBaseName ("dave.ext") == ("dave"))+    ,("P.takeBaseName \"\" == \"\"", property $ P.takeBaseName "" == "")+    ,("W.takeBaseName \"\" == \"\"", property $ W.takeBaseName "" == "")+    ,("AFP_P.takeBaseName (\"\") == (\"\")", property $ AFP_P.takeBaseName ("") == (""))+    ,("AFP_W.takeBaseName (\"\") == (\"\")", property $ AFP_W.takeBaseName ("") == (""))+    ,("P.takeBaseName \"test\" == \"test\"", property $ P.takeBaseName "test" == "test")+    ,("W.takeBaseName \"test\" == \"test\"", property $ W.takeBaseName "test" == "test")+    ,("AFP_P.takeBaseName (\"test\") == (\"test\")", property $ AFP_P.takeBaseName ("test") == ("test"))+    ,("AFP_W.takeBaseName (\"test\") == (\"test\")", property $ AFP_W.takeBaseName ("test") == ("test"))+    ,("P.takeBaseName (P.addTrailingPathSeparator x) == \"\"", property $ \(QFilePath x) -> P.takeBaseName (P.addTrailingPathSeparator x) == "")+    ,("W.takeBaseName (W.addTrailingPathSeparator x) == \"\"", property $ \(QFilePath x) -> W.takeBaseName (W.addTrailingPathSeparator x) == "")+    ,("AFP_P.takeBaseName (AFP_P.addTrailingPathSeparator x) == (\"\")", property $ \(QFilePathAFP_P x) -> AFP_P.takeBaseName (AFP_P.addTrailingPathSeparator x) == (""))+    ,("AFP_W.takeBaseName (AFP_W.addTrailingPathSeparator x) == (\"\")", property $ \(QFilePathAFP_W x) -> AFP_W.takeBaseName (AFP_W.addTrailingPathSeparator x) == (""))+    ,("P.takeBaseName \"file/file.tar.gz\" == \"file.tar\"", property $ P.takeBaseName "file/file.tar.gz" == "file.tar")+    ,("W.takeBaseName \"file/file.tar.gz\" == \"file.tar\"", property $ W.takeBaseName "file/file.tar.gz" == "file.tar")+    ,("AFP_P.takeBaseName (\"file/file.tar.gz\") == (\"file.tar\")", property $ AFP_P.takeBaseName ("file/file.tar.gz") == ("file.tar"))+    ,("AFP_W.takeBaseName (\"file/file.tar.gz\") == (\"file.tar\")", property $ AFP_W.takeBaseName ("file/file.tar.gz") == ("file.tar"))+    ,("P.replaceBaseName \"/directory/other.ext\" \"file\" == \"/directory/file.ext\"", property $ P.replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext")+    ,("W.replaceBaseName \"/directory/other.ext\" \"file\" == \"/directory/file.ext\"", property $ W.replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext")+    ,("AFP_P.replaceBaseName (\"/directory/other.ext\") (\"file\") == (\"/directory/file.ext\")", property $ AFP_P.replaceBaseName ("/directory/other.ext") ("file") == ("/directory/file.ext"))+    ,("AFP_W.replaceBaseName (\"/directory/other.ext\") (\"file\") == (\"/directory/file.ext\")", property $ AFP_W.replaceBaseName ("/directory/other.ext") ("file") == ("/directory/file.ext"))+    ,("P.replaceBaseName \"file/test.txt\" \"bob\" == \"file/bob.txt\"", property $ P.replaceBaseName "file/test.txt" "bob" == "file/bob.txt")+    ,("W.replaceBaseName \"file/test.txt\" \"bob\" == \"file/bob.txt\"", property $ W.replaceBaseName "file/test.txt" "bob" == "file/bob.txt")+    ,("AFP_P.replaceBaseName (\"file/test.txt\") (\"bob\") == (\"file/bob.txt\")", property $ AFP_P.replaceBaseName ("file/test.txt") ("bob") == ("file/bob.txt"))+    ,("AFP_W.replaceBaseName (\"file/test.txt\") (\"bob\") == (\"file/bob.txt\")", property $ AFP_W.replaceBaseName ("file/test.txt") ("bob") == ("file/bob.txt"))+    ,("P.replaceBaseName \"fred\" \"bill\" == \"bill\"", property $ P.replaceBaseName "fred" "bill" == "bill")+    ,("W.replaceBaseName \"fred\" \"bill\" == \"bill\"", property $ W.replaceBaseName "fred" "bill" == "bill")+    ,("AFP_P.replaceBaseName (\"fred\") (\"bill\") == (\"bill\")", property $ AFP_P.replaceBaseName ("fred") ("bill") == ("bill"))+    ,("AFP_W.replaceBaseName (\"fred\") (\"bill\") == (\"bill\")", property $ AFP_W.replaceBaseName ("fred") ("bill") == ("bill"))+    ,("P.replaceBaseName \"/dave/fred/bob.gz.tar\" \"new\" == \"/dave/fred/new.tar\"", property $ P.replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar")+    ,("W.replaceBaseName \"/dave/fred/bob.gz.tar\" \"new\" == \"/dave/fred/new.tar\"", property $ W.replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar")+    ,("AFP_P.replaceBaseName (\"/dave/fred/bob.gz.tar\") (\"new\") == (\"/dave/fred/new.tar\")", property $ AFP_P.replaceBaseName ("/dave/fred/bob.gz.tar") ("new") == ("/dave/fred/new.tar"))+    ,("AFP_W.replaceBaseName (\"/dave/fred/bob.gz.tar\") (\"new\") == (\"/dave/fred/new.tar\")", property $ AFP_W.replaceBaseName ("/dave/fred/bob.gz.tar") ("new") == ("/dave/fred/new.tar"))+    ,("P.replaceBaseName x (P.takeBaseName x) == x", property $ \(QFilePathValidP x) -> P.replaceBaseName x (P.takeBaseName x) == x)+    ,("W.replaceBaseName x (W.takeBaseName x) == x", property $ \(QFilePathValidW x) -> W.replaceBaseName x (W.takeBaseName x) == x)+    ,("AFP_P.replaceBaseName x (AFP_P.takeBaseName x) == x", property $ \(QFilePathValidAFP_P x) -> AFP_P.replaceBaseName x (AFP_P.takeBaseName x) == x)+    ,("AFP_W.replaceBaseName x (AFP_W.takeBaseName x) == x", property $ \(QFilePathValidAFP_W x) -> AFP_W.replaceBaseName x (AFP_W.takeBaseName x) == x)+    ,("P.hasTrailingPathSeparator \"test\" == False", property $ P.hasTrailingPathSeparator "test" == False)+    ,("W.hasTrailingPathSeparator \"test\" == False", property $ W.hasTrailingPathSeparator "test" == False)+    ,("AFP_P.hasTrailingPathSeparator (\"test\") == False", property $ AFP_P.hasTrailingPathSeparator ("test") == False)+    ,("AFP_W.hasTrailingPathSeparator (\"test\") == False", property $ AFP_W.hasTrailingPathSeparator ("test") == False)+    ,("P.hasTrailingPathSeparator \"test/\" == True", property $ P.hasTrailingPathSeparator "test/" == True)+    ,("W.hasTrailingPathSeparator \"test/\" == True", property $ W.hasTrailingPathSeparator "test/" == True)+    ,("AFP_P.hasTrailingPathSeparator (\"test/\") == True", property $ AFP_P.hasTrailingPathSeparator ("test/") == True)+    ,("AFP_W.hasTrailingPathSeparator (\"test/\") == True", property $ AFP_W.hasTrailingPathSeparator ("test/") == True)+    ,("P.hasTrailingPathSeparator (P.addTrailingPathSeparator x)", property $ \(QFilePath x) -> P.hasTrailingPathSeparator (P.addTrailingPathSeparator x))+    ,("W.hasTrailingPathSeparator (W.addTrailingPathSeparator x)", property $ \(QFilePath x) -> W.hasTrailingPathSeparator (W.addTrailingPathSeparator x))+    ,("AFP_P.hasTrailingPathSeparator (AFP_P.addTrailingPathSeparator x)", property $ \(QFilePathAFP_P x) -> AFP_P.hasTrailingPathSeparator (AFP_P.addTrailingPathSeparator x))+    ,("AFP_W.hasTrailingPathSeparator (AFP_W.addTrailingPathSeparator x)", property $ \(QFilePathAFP_W x) -> AFP_W.hasTrailingPathSeparator (AFP_W.addTrailingPathSeparator x))+    ,("P.hasTrailingPathSeparator x ==> P.addTrailingPathSeparator x == x", property $ \(QFilePath x) -> P.hasTrailingPathSeparator x ==> P.addTrailingPathSeparator x == x)+    ,("W.hasTrailingPathSeparator x ==> W.addTrailingPathSeparator x == x", property $ \(QFilePath x) -> W.hasTrailingPathSeparator x ==> W.addTrailingPathSeparator x == x)+    ,("AFP_P.hasTrailingPathSeparator x ==> AFP_P.addTrailingPathSeparator x == x", property $ \(QFilePathAFP_P x) -> AFP_P.hasTrailingPathSeparator x ==> AFP_P.addTrailingPathSeparator x == x)+    ,("AFP_W.hasTrailingPathSeparator x ==> AFP_W.addTrailingPathSeparator x == x", property $ \(QFilePathAFP_W x) -> AFP_W.hasTrailingPathSeparator x ==> AFP_W.addTrailingPathSeparator x == x)+    ,("P.addTrailingPathSeparator \"test/rest\" == \"test/rest/\"", property $ P.addTrailingPathSeparator "test/rest" == "test/rest/")+    ,("AFP_P.addTrailingPathSeparator (\"test/rest\") == (\"test/rest/\")", property $ AFP_P.addTrailingPathSeparator ("test/rest") == ("test/rest/"))+    ,("P.dropTrailingPathSeparator \"file/test/\" == \"file/test\"", property $ P.dropTrailingPathSeparator "file/test/" == "file/test")+    ,("W.dropTrailingPathSeparator \"file/test/\" == \"file/test\"", property $ W.dropTrailingPathSeparator "file/test/" == "file/test")+    ,("AFP_P.dropTrailingPathSeparator (\"file/test/\") == (\"file/test\")", property $ AFP_P.dropTrailingPathSeparator ("file/test/") == ("file/test"))+    ,("AFP_W.dropTrailingPathSeparator (\"file/test/\") == (\"file/test\")", property $ AFP_W.dropTrailingPathSeparator ("file/test/") == ("file/test"))+    ,("P.dropTrailingPathSeparator \"/\" == \"/\"", property $ P.dropTrailingPathSeparator "/" == "/")+    ,("W.dropTrailingPathSeparator \"/\" == \"/\"", property $ W.dropTrailingPathSeparator "/" == "/")+    ,("AFP_P.dropTrailingPathSeparator (\"/\") == (\"/\")", property $ AFP_P.dropTrailingPathSeparator ("/") == ("/"))+    ,("AFP_W.dropTrailingPathSeparator (\"/\") == (\"/\")", property $ AFP_W.dropTrailingPathSeparator ("/") == ("/"))+    ,("W.dropTrailingPathSeparator \"\\\\\" == \"\\\\\"", property $ W.dropTrailingPathSeparator "\\" == "\\")+    ,("AFP_W.dropTrailingPathSeparator (\"\\\\\") == (\"\\\\\")", property $ AFP_W.dropTrailingPathSeparator ("\\") == ("\\"))+    ,("not (P.hasTrailingPathSeparator (P.dropTrailingPathSeparator x)) || P.isDrive x", property $ \(QFilePath x) -> not (P.hasTrailingPathSeparator (P.dropTrailingPathSeparator x)) || P.isDrive x)+    ,("not (AFP_P.hasTrailingPathSeparator (AFP_P.dropTrailingPathSeparator x)) || AFP_P.isDrive x", property $ \(QFilePathAFP_P x) -> not (AFP_P.hasTrailingPathSeparator (AFP_P.dropTrailingPathSeparator x)) || AFP_P.isDrive x)+    ,("P.takeDirectory \"/directory/other.ext\" == \"/directory\"", property $ P.takeDirectory "/directory/other.ext" == "/directory")+    ,("W.takeDirectory \"/directory/other.ext\" == \"/directory\"", property $ W.takeDirectory "/directory/other.ext" == "/directory")+    ,("AFP_P.takeDirectory (\"/directory/other.ext\") == (\"/directory\")", property $ AFP_P.takeDirectory ("/directory/other.ext") == ("/directory"))+    ,("AFP_W.takeDirectory (\"/directory/other.ext\") == (\"/directory\")", property $ AFP_W.takeDirectory ("/directory/other.ext") == ("/directory"))+    ,("isPrefixOf (P.takeDirectory x) x || P.takeDirectory x == \".\"", property $ \(QFilePath x) -> isPrefixOf (P.takeDirectory x) x || P.takeDirectory x == ".")+    ,("isPrefixOf (W.takeDirectory x) x || W.takeDirectory x == \".\"", property $ \(QFilePath x) -> isPrefixOf (W.takeDirectory x) x || W.takeDirectory x == ".")+    ,("(\\(getPosixString -> x) (getPosixString -> y) -> SBS.isPrefixOf x y) (AFP_P.takeDirectory x) x || AFP_P.takeDirectory x == (\".\")", property $ \(QFilePathAFP_P x) -> (\(getPosixString -> x) (getPosixString -> y) -> SBS.isPrefixOf x y) (AFP_P.takeDirectory x) x || AFP_P.takeDirectory x == ("."))+    ,("(\\(getWindowsString -> x) (getWindowsString -> y) -> SBS16.isPrefixOf x y) (AFP_W.takeDirectory x) x || AFP_W.takeDirectory x == (\".\")", property $ \(QFilePathAFP_W x) -> (\(getWindowsString -> x) (getWindowsString -> y) -> SBS16.isPrefixOf x y) (AFP_W.takeDirectory x) x || AFP_W.takeDirectory x == ("."))+    ,("P.takeDirectory \"foo\" == \".\"", property $ P.takeDirectory "foo" == ".")+    ,("W.takeDirectory \"foo\" == \".\"", property $ W.takeDirectory "foo" == ".")+    ,("AFP_P.takeDirectory (\"foo\") == (\".\")", property $ AFP_P.takeDirectory ("foo") == ("."))+    ,("AFP_W.takeDirectory (\"foo\") == (\".\")", property $ AFP_W.takeDirectory ("foo") == ("."))+    ,("P.takeDirectory \"/\" == \"/\"", property $ P.takeDirectory "/" == "/")+    ,("W.takeDirectory \"/\" == \"/\"", property $ W.takeDirectory "/" == "/")+    ,("AFP_P.takeDirectory (\"/\") == (\"/\")", property $ AFP_P.takeDirectory ("/") == ("/"))+    ,("AFP_W.takeDirectory (\"/\") == (\"/\")", property $ AFP_W.takeDirectory ("/") == ("/"))+    ,("P.takeDirectory \"/foo\" == \"/\"", property $ P.takeDirectory "/foo" == "/")+    ,("W.takeDirectory \"/foo\" == \"/\"", property $ W.takeDirectory "/foo" == "/")+    ,("AFP_P.takeDirectory (\"/foo\") == (\"/\")", property $ AFP_P.takeDirectory ("/foo") == ("/"))+    ,("AFP_W.takeDirectory (\"/foo\") == (\"/\")", property $ AFP_W.takeDirectory ("/foo") == ("/"))+    ,("P.takeDirectory \"/foo/bar/baz\" == \"/foo/bar\"", property $ P.takeDirectory "/foo/bar/baz" == "/foo/bar")+    ,("W.takeDirectory \"/foo/bar/baz\" == \"/foo/bar\"", property $ W.takeDirectory "/foo/bar/baz" == "/foo/bar")+    ,("AFP_P.takeDirectory (\"/foo/bar/baz\") == (\"/foo/bar\")", property $ AFP_P.takeDirectory ("/foo/bar/baz") == ("/foo/bar"))+    ,("AFP_W.takeDirectory (\"/foo/bar/baz\") == (\"/foo/bar\")", property $ AFP_W.takeDirectory ("/foo/bar/baz") == ("/foo/bar"))+    ,("P.takeDirectory \"/foo/bar/baz/\" == \"/foo/bar/baz\"", property $ P.takeDirectory "/foo/bar/baz/" == "/foo/bar/baz")+    ,("W.takeDirectory \"/foo/bar/baz/\" == \"/foo/bar/baz\"", property $ W.takeDirectory "/foo/bar/baz/" == "/foo/bar/baz")+    ,("AFP_P.takeDirectory (\"/foo/bar/baz/\") == (\"/foo/bar/baz\")", property $ AFP_P.takeDirectory ("/foo/bar/baz/") == ("/foo/bar/baz"))+    ,("AFP_W.takeDirectory (\"/foo/bar/baz/\") == (\"/foo/bar/baz\")", property $ AFP_W.takeDirectory ("/foo/bar/baz/") == ("/foo/bar/baz"))+    ,("P.takeDirectory \"foo/bar/baz\" == \"foo/bar\"", property $ P.takeDirectory "foo/bar/baz" == "foo/bar")+    ,("W.takeDirectory \"foo/bar/baz\" == \"foo/bar\"", property $ W.takeDirectory "foo/bar/baz" == "foo/bar")+    ,("AFP_P.takeDirectory (\"foo/bar/baz\") == (\"foo/bar\")", property $ AFP_P.takeDirectory ("foo/bar/baz") == ("foo/bar"))+    ,("AFP_W.takeDirectory (\"foo/bar/baz\") == (\"foo/bar\")", property $ AFP_W.takeDirectory ("foo/bar/baz") == ("foo/bar"))+    ,("W.takeDirectory \"foo\\\\bar\" == \"foo\"", property $ W.takeDirectory "foo\\bar" == "foo")+    ,("AFP_W.takeDirectory (\"foo\\\\bar\") == (\"foo\")", property $ AFP_W.takeDirectory ("foo\\bar") == ("foo"))+    ,("W.takeDirectory \"foo\\\\bar\\\\\\\\\" == \"foo\\\\bar\"", property $ W.takeDirectory "foo\\bar\\\\" == "foo\\bar")+    ,("AFP_W.takeDirectory (\"foo\\\\bar\\\\\\\\\") == (\"foo\\\\bar\")", property $ AFP_W.takeDirectory ("foo\\bar\\\\") == ("foo\\bar"))+    ,("W.takeDirectory \"C:\\\\\" == \"C:\\\\\"", property $ W.takeDirectory "C:\\" == "C:\\")+    ,("AFP_W.takeDirectory (\"C:\\\\\") == (\"C:\\\\\")", property $ AFP_W.takeDirectory ("C:\\") == ("C:\\"))+    ,("P.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ P.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")+    ,("W.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ W.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")+    ,("AFP_P.replaceDirectory (\"root/file.ext\") (\"/directory/\") == (\"/directory/file.ext\")", property $ AFP_P.replaceDirectory ("root/file.ext") ("/directory/") == ("/directory/file.ext"))+    ,("AFP_W.replaceDirectory (\"root/file.ext\") (\"/directory/\") == (\"/directory/file.ext\")", property $ AFP_W.replaceDirectory ("root/file.ext") ("/directory/") == ("/directory/file.ext"))+    ,("P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x)+    ,("W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x)+    ,("AFP_P.replaceDirectory x (AFP_P.takeDirectory x) `AFP_P.equalFilePath` x", property $ \(QFilePathValidAFP_P x) -> AFP_P.replaceDirectory x (AFP_P.takeDirectory x) `AFP_P.equalFilePath` x)+    ,("AFP_W.replaceDirectory x (AFP_W.takeDirectory x) `AFP_W.equalFilePath` x", property $ \(QFilePathValidAFP_W x) -> AFP_W.replaceDirectory x (AFP_W.takeDirectory x) `AFP_W.equalFilePath` x)+    ,("\"/directory\" P.</> \"file.ext\" == \"/directory/file.ext\"", property $ "/directory" P.</> "file.ext" == "/directory/file.ext")+    ,("(\"/directory\") AFP_P.</> (\"file.ext\") == (\"/directory/file.ext\")", property $ ("/directory") AFP_P.</> ("file.ext") == ("/directory/file.ext"))+    ,("\"/directory\" W.</> \"file.ext\" == \"/directory\\\\file.ext\"", property $ "/directory" W.</> "file.ext" == "/directory\\file.ext")+    ,("(\"/directory\") AFP_W.</> (\"file.ext\") == (\"/directory\\\\file.ext\")", property $ ("/directory") AFP_W.</> ("file.ext") == ("/directory\\file.ext"))+    ,("\"directory\" P.</> \"/file.ext\" == \"/file.ext\"", property $ "directory" P.</> "/file.ext" == "/file.ext")+    ,("\"directory\" W.</> \"/file.ext\" == \"/file.ext\"", property $ "directory" W.</> "/file.ext" == "/file.ext")+    ,("(\"directory\") AFP_P.</> (\"/file.ext\") == (\"/file.ext\")", property $ ("directory") AFP_P.</> ("/file.ext") == ("/file.ext"))+    ,("(\"directory\") AFP_W.</> (\"/file.ext\") == (\"/file.ext\")", property $ ("directory") AFP_W.</> ("/file.ext") == ("/file.ext"))+    ,("(P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> (P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x)+    ,("(W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> (W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x)+    ,("(AFP_P.takeDirectory x AFP_P.</> AFP_P.takeFileName x) `AFP_P.equalFilePath` x", property $ \(QFilePathValidAFP_P x) -> (AFP_P.takeDirectory x AFP_P.</> AFP_P.takeFileName x) `AFP_P.equalFilePath` x)+    ,("(AFP_W.takeDirectory x AFP_W.</> AFP_W.takeFileName x) `AFP_W.equalFilePath` x", property $ \(QFilePathValidAFP_W x) -> (AFP_W.takeDirectory x AFP_W.</> AFP_W.takeFileName x) `AFP_W.equalFilePath` x)+    ,("\"/\" P.</> \"test\" == \"/test\"", property $ "/" P.</> "test" == "/test")+    ,("(\"/\") AFP_P.</> (\"test\") == (\"/test\")", property $ ("/") AFP_P.</> ("test") == ("/test"))+    ,("\"home\" P.</> \"bob\" == \"home/bob\"", property $ "home" P.</> "bob" == "home/bob")+    ,("(\"home\") AFP_P.</> (\"bob\") == (\"home/bob\")", property $ ("home") AFP_P.</> ("bob") == ("home/bob"))+    ,("\"x:\" P.</> \"foo\" == \"x:/foo\"", property $ "x:" P.</> "foo" == "x:/foo")+    ,("(\"x:\") AFP_P.</> (\"foo\") == (\"x:/foo\")", property $ ("x:") AFP_P.</> ("foo") == ("x:/foo"))+    ,("\"C:\\\\foo\" W.</> \"bar\" == \"C:\\\\foo\\\\bar\"", property $ "C:\\foo" W.</> "bar" == "C:\\foo\\bar")+    ,("(\"C:\\\\foo\") AFP_W.</> (\"bar\") == (\"C:\\\\foo\\\\bar\")", property $ ("C:\\foo") AFP_W.</> ("bar") == ("C:\\foo\\bar"))+    ,("\"home\" W.</> \"bob\" == \"home\\\\bob\"", property $ "home" W.</> "bob" == "home\\bob")+    ,("(\"home\") AFP_W.</> (\"bob\") == (\"home\\\\bob\")", property $ ("home") AFP_W.</> ("bob") == ("home\\bob"))+    ,("\"home\" P.</> \"/bob\" == \"/bob\"", property $ "home" P.</> "/bob" == "/bob")+    ,("(\"home\") AFP_P.</> (\"/bob\") == (\"/bob\")", property $ ("home") AFP_P.</> ("/bob") == ("/bob"))+    ,("\"home\" W.</> \"C:\\\\bob\" == \"C:\\\\bob\"", property $ "home" W.</> "C:\\bob" == "C:\\bob")+    ,("(\"home\") AFP_W.</> (\"C:\\\\bob\") == (\"C:\\\\bob\")", property $ ("home") AFP_W.</> ("C:\\bob") == ("C:\\bob"))+    ,("\"home\" W.</> \"/bob\" == \"/bob\"", property $ "home" W.</> "/bob" == "/bob")+    ,("(\"home\") AFP_W.</> (\"/bob\") == (\"/bob\")", property $ ("home") AFP_W.</> ("/bob") == ("/bob"))+    ,("\"home\" W.</> \"\\\\bob\" == \"\\\\bob\"", property $ "home" W.</> "\\bob" == "\\bob")+    ,("(\"home\") AFP_W.</> (\"\\\\bob\") == (\"\\\\bob\")", property $ ("home") AFP_W.</> ("\\bob") == ("\\bob"))+    ,("\"C:\\\\home\" W.</> \"\\\\bob\" == \"\\\\bob\"", property $ "C:\\home" W.</> "\\bob" == "\\bob")+    ,("(\"C:\\\\home\") AFP_W.</> (\"\\\\bob\") == (\"\\\\bob\")", property $ ("C:\\home") AFP_W.</> ("\\bob") == ("\\bob"))+    ,("\"D:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", property $ "D:\\foo" W.</> "C:bar" == "C:bar")+    ,("(\"D:\\\\foo\") AFP_W.</> (\"C:bar\") == (\"C:bar\")", property $ ("D:\\foo") AFP_W.</> ("C:bar") == ("C:bar"))+    ,("\"C:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", property $ "C:\\foo" W.</> "C:bar" == "C:bar")+    ,("(\"C:\\\\foo\") AFP_W.</> (\"C:bar\") == (\"C:bar\")", property $ ("C:\\foo") AFP_W.</> ("C:bar") == ("C:bar"))+    ,("P.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", property $ P.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])+    ,("W.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", property $ W.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])+    ,("AFP_P.splitPath (\"/directory/file.ext\") == [(\"/\"), (\"directory/\"), (\"file.ext\")]", property $ AFP_P.splitPath ("/directory/file.ext") == [("/"), ("directory/"), ("file.ext")])+    ,("AFP_W.splitPath (\"/directory/file.ext\") == [(\"/\"), (\"directory/\"), (\"file.ext\")]", property $ AFP_W.splitPath ("/directory/file.ext") == [("/"), ("directory/"), ("file.ext")])+    ,("concat (P.splitPath x) == x", property $ \(QFilePath x) -> concat (P.splitPath x) == x)+    ,("concat (W.splitPath x) == x", property $ \(QFilePath x) -> concat (W.splitPath x) == x)+    ,("(PS . SBS.concat . fmap getPosixString) (AFP_P.splitPath x) == x", property $ \(QFilePathAFP_P x) -> (PS . SBS.concat . fmap getPosixString) (AFP_P.splitPath x) == x)+    ,("(WS . SBS16.concat . fmap getWindowsString) (AFP_W.splitPath x) == x", property $ \(QFilePathAFP_W x) -> (WS . SBS16.concat . fmap getWindowsString) (AFP_W.splitPath x) == x)+    ,("P.splitPath \"test//item/\" == [\"test//\", \"item/\"]", property $ P.splitPath "test//item/" == ["test//", "item/"])+    ,("W.splitPath \"test//item/\" == [\"test//\", \"item/\"]", property $ W.splitPath "test//item/" == ["test//", "item/"])+    ,("AFP_P.splitPath (\"test//item/\") == [(\"test//\"), (\"item/\")]", property $ AFP_P.splitPath ("test//item/") == [("test//"), ("item/")])+    ,("AFP_W.splitPath (\"test//item/\") == [(\"test//\"), (\"item/\")]", property $ AFP_W.splitPath ("test//item/") == [("test//"), ("item/")])+    ,("P.splitPath \"test/item/file\" == [\"test/\", \"item/\", \"file\"]", property $ P.splitPath "test/item/file" == ["test/", "item/", "file"])+    ,("W.splitPath \"test/item/file\" == [\"test/\", \"item/\", \"file\"]", property $ W.splitPath "test/item/file" == ["test/", "item/", "file"])+    ,("AFP_P.splitPath (\"test/item/file\") == [(\"test/\"), (\"item/\"), (\"file\")]", property $ AFP_P.splitPath ("test/item/file") == [("test/"), ("item/"), ("file")])+    ,("AFP_W.splitPath (\"test/item/file\") == [(\"test/\"), (\"item/\"), (\"file\")]", property $ AFP_W.splitPath ("test/item/file") == [("test/"), ("item/"), ("file")])+    ,("P.splitPath \"\" == []", property $ P.splitPath "" == [])+    ,("W.splitPath \"\" == []", property $ W.splitPath "" == [])+    ,("AFP_P.splitPath (\"\") == []", property $ AFP_P.splitPath ("") == [])+    ,("AFP_W.splitPath (\"\") == []", property $ AFP_W.splitPath ("") == [])+    ,("W.splitPath \"c:\\\\test\\\\path\" == [\"c:\\\\\", \"test\\\\\", \"path\"]", property $ W.splitPath "c:\\test\\path" == ["c:\\", "test\\", "path"])+    ,("AFP_W.splitPath (\"c:\\\\test\\\\path\") == [(\"c:\\\\\"), (\"test\\\\\"), (\"path\")]", property $ AFP_W.splitPath ("c:\\test\\path") == [("c:\\"), ("test\\"), ("path")])+    ,("P.splitPath \"/file/test\" == [\"/\", \"file/\", \"test\"]", property $ P.splitPath "/file/test" == ["/", "file/", "test"])+    ,("AFP_P.splitPath (\"/file/test\") == [(\"/\"), (\"file/\"), (\"test\")]", property $ AFP_P.splitPath ("/file/test") == [("/"), ("file/"), ("test")])+    ,("P.splitDirectories \"/directory/file.ext\" == [\"/\", \"directory\", \"file.ext\"]", property $ P.splitDirectories "/directory/file.ext" == ["/", "directory", "file.ext"])+    ,("W.splitDirectories \"/directory/file.ext\" == [\"/\", \"directory\", \"file.ext\"]", property $ W.splitDirectories "/directory/file.ext" == ["/", "directory", "file.ext"])+    ,("AFP_P.splitDirectories (\"/directory/file.ext\") == [(\"/\"), (\"directory\"), (\"file.ext\")]", property $ AFP_P.splitDirectories ("/directory/file.ext") == [("/"), ("directory"), ("file.ext")])+    ,("AFP_W.splitDirectories (\"/directory/file.ext\") == [(\"/\"), (\"directory\"), (\"file.ext\")]", property $ AFP_W.splitDirectories ("/directory/file.ext") == [("/"), ("directory"), ("file.ext")])+    ,("P.splitDirectories \"test/file\" == [\"test\", \"file\"]", property $ P.splitDirectories "test/file" == ["test", "file"])+    ,("W.splitDirectories \"test/file\" == [\"test\", \"file\"]", property $ W.splitDirectories "test/file" == ["test", "file"])+    ,("AFP_P.splitDirectories (\"test/file\") == [(\"test\"), (\"file\")]", property $ AFP_P.splitDirectories ("test/file") == [("test"), ("file")])+    ,("AFP_W.splitDirectories (\"test/file\") == [(\"test\"), (\"file\")]", property $ AFP_W.splitDirectories ("test/file") == [("test"), ("file")])+    ,("P.splitDirectories \"/test/file\" == [\"/\", \"test\", \"file\"]", property $ P.splitDirectories "/test/file" == ["/", "test", "file"])+    ,("W.splitDirectories \"/test/file\" == [\"/\", \"test\", \"file\"]", property $ W.splitDirectories "/test/file" == ["/", "test", "file"])+    ,("AFP_P.splitDirectories (\"/test/file\") == [(\"/\"), (\"test\"), (\"file\")]", property $ AFP_P.splitDirectories ("/test/file") == [("/"), ("test"), ("file")])+    ,("AFP_W.splitDirectories (\"/test/file\") == [(\"/\"), (\"test\"), (\"file\")]", property $ AFP_W.splitDirectories ("/test/file") == [("/"), ("test"), ("file")])+    ,("W.splitDirectories \"C:\\\\test\\\\file\" == [\"C:\\\\\", \"test\", \"file\"]", property $ W.splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"])+    ,("AFP_W.splitDirectories (\"C:\\\\test\\\\file\") == [(\"C:\\\\\"), (\"test\"), (\"file\")]", property $ AFP_W.splitDirectories ("C:\\test\\file") == [("C:\\"), ("test"), ("file")])+    ,("P.joinPath (P.splitDirectories x) `P.equalFilePath` x", property $ \(QFilePathValidP x) -> P.joinPath (P.splitDirectories x) `P.equalFilePath` x)+    ,("W.joinPath (W.splitDirectories x) `W.equalFilePath` x", property $ \(QFilePathValidW x) -> W.joinPath (W.splitDirectories x) `W.equalFilePath` x)+    ,("AFP_P.joinPath (AFP_P.splitDirectories x) `AFP_P.equalFilePath` x", property $ \(QFilePathValidAFP_P x) -> AFP_P.joinPath (AFP_P.splitDirectories x) `AFP_P.equalFilePath` x)+    ,("AFP_W.joinPath (AFP_W.splitDirectories x) `AFP_W.equalFilePath` x", property $ \(QFilePathValidAFP_W x) -> AFP_W.joinPath (AFP_W.splitDirectories x) `AFP_W.equalFilePath` x)+    ,("P.splitDirectories \"\" == []", property $ P.splitDirectories "" == [])+    ,("W.splitDirectories \"\" == []", property $ W.splitDirectories "" == [])+    ,("AFP_P.splitDirectories (\"\") == []", property $ AFP_P.splitDirectories ("") == [])+    ,("AFP_W.splitDirectories (\"\") == []", property $ AFP_W.splitDirectories ("") == [])+    ,("W.splitDirectories \"C:\\\\test\\\\\\\\\\\\file\" == [\"C:\\\\\", \"test\", \"file\"]", property $ W.splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"])+    ,("AFP_W.splitDirectories (\"C:\\\\test\\\\\\\\\\\\file\") == [(\"C:\\\\\"), (\"test\"), (\"file\")]", property $ AFP_W.splitDirectories ("C:\\test\\\\\\file") == [("C:\\"), ("test"), ("file")])+    ,("P.splitDirectories \"/test///file\" == [\"/\", \"test\", \"file\"]", property $ P.splitDirectories "/test///file" == ["/", "test", "file"])+    ,("W.splitDirectories \"/test///file\" == [\"/\", \"test\", \"file\"]", property $ W.splitDirectories "/test///file" == ["/", "test", "file"])+    ,("AFP_P.splitDirectories (\"/test///file\") == [(\"/\"), (\"test\"), (\"file\")]", property $ AFP_P.splitDirectories ("/test///file") == [("/"), ("test"), ("file")])+    ,("AFP_W.splitDirectories (\"/test///file\") == [(\"/\"), (\"test\"), (\"file\")]", property $ AFP_W.splitDirectories ("/test///file") == [("/"), ("test"), ("file")])+    ,("P.joinPath z == foldr (P.</>) \"\" z", property $ \z -> P.joinPath z == foldr (P.</>) "" z)+    ,("W.joinPath z == foldr (W.</>) \"\" z", property $ \z -> W.joinPath z == foldr (W.</>) "" z)+    ,("AFP_P.joinPath z == foldr (AFP_P.</>) (\"\") z", property $ \(QFilePathsAFP_P z) -> AFP_P.joinPath z == foldr (AFP_P.</>) ("") z)+    ,("AFP_W.joinPath z == foldr (AFP_W.</>) (\"\") z", property $ \(QFilePathsAFP_W z) -> AFP_W.joinPath z == foldr (AFP_W.</>) ("") z)+    ,("P.joinPath [\"/\", \"directory/\", \"file.ext\"] == \"/directory/file.ext\"", property $ P.joinPath ["/", "directory/", "file.ext"] == "/directory/file.ext")+    ,("W.joinPath [\"/\", \"directory/\", \"file.ext\"] == \"/directory/file.ext\"", property $ W.joinPath ["/", "directory/", "file.ext"] == "/directory/file.ext")+    ,("AFP_P.joinPath [(\"/\"), (\"directory/\"), (\"file.ext\")] == (\"/directory/file.ext\")", property $ AFP_P.joinPath [("/"), ("directory/"), ("file.ext")] == ("/directory/file.ext"))+    ,("AFP_W.joinPath [(\"/\"), (\"directory/\"), (\"file.ext\")] == (\"/directory/file.ext\")", property $ AFP_W.joinPath [("/"), ("directory/"), ("file.ext")] == ("/directory/file.ext"))+    ,("P.joinPath (P.splitPath x) == x", property $ \(QFilePathValidP x) -> P.joinPath (P.splitPath x) == x)+    ,("W.joinPath (W.splitPath x) == x", property $ \(QFilePathValidW x) -> W.joinPath (W.splitPath x) == x)+    ,("AFP_P.joinPath (AFP_P.splitPath x) == x", property $ \(QFilePathValidAFP_P x) -> AFP_P.joinPath (AFP_P.splitPath x) == x)+    ,("AFP_W.joinPath (AFP_W.splitPath x) == x", property $ \(QFilePathValidAFP_W x) -> AFP_W.joinPath (AFP_W.splitPath x) == x)+    ,("P.joinPath [] == \"\"", property $ P.joinPath [] == "")+    ,("W.joinPath [] == \"\"", property $ W.joinPath [] == "")+    ,("AFP_P.joinPath [] == (\"\")", property $ AFP_P.joinPath [] == (""))+    ,("AFP_W.joinPath [] == (\"\")", property $ AFP_W.joinPath [] == (""))+    ,("P.joinPath [\"test\", \"file\", \"path\"] == \"test/file/path\"", property $ P.joinPath ["test", "file", "path"] == "test/file/path")+    ,("AFP_P.joinPath [(\"test\"), (\"file\"), (\"path\")] == (\"test/file/path\")", property $ AFP_P.joinPath [("test"), ("file"), ("path")] == ("test/file/path"))+    ,("x == y ==> P.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> x == y ==> P.equalFilePath x y)+    ,("x == y ==> W.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> x == y ==> W.equalFilePath x y)+    ,("x == y ==> AFP_P.equalFilePath x y", property $ \(QFilePathAFP_P x) (QFilePathAFP_P y) -> x == y ==> AFP_P.equalFilePath x y)+    ,("x == y ==> AFP_W.equalFilePath x y", property $ \(QFilePathAFP_W x) (QFilePathAFP_W y) -> x == y ==> AFP_W.equalFilePath x y)+    ,("P.normalise x == P.normalise y ==> P.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> P.normalise x == P.normalise y ==> P.equalFilePath x y)+    ,("W.normalise x == W.normalise y ==> W.equalFilePath x y", property $ \(QFilePath x) (QFilePath y) -> W.normalise x == W.normalise y ==> W.equalFilePath x y)+    ,("AFP_P.normalise x == AFP_P.normalise y ==> AFP_P.equalFilePath x y", property $ \(QFilePathAFP_P x) (QFilePathAFP_P y) -> AFP_P.normalise x == AFP_P.normalise y ==> AFP_P.equalFilePath x y)+    ,("AFP_W.normalise x == AFP_W.normalise y ==> AFP_W.equalFilePath x y", property $ \(QFilePathAFP_W x) (QFilePathAFP_W y) -> AFP_W.normalise x == AFP_W.normalise y ==> AFP_W.equalFilePath x y)+    ,("P.equalFilePath \"foo\" \"foo/\"", property $ P.equalFilePath "foo" "foo/")+    ,("W.equalFilePath \"foo\" \"foo/\"", property $ W.equalFilePath "foo" "foo/")+    ,("AFP_P.equalFilePath (\"foo\") (\"foo/\")", property $ AFP_P.equalFilePath ("foo") ("foo/"))+    ,("AFP_W.equalFilePath (\"foo\") (\"foo/\")", property $ AFP_W.equalFilePath ("foo") ("foo/"))+    ,("not (P.equalFilePath \"/a/../c\" \"/c\")", property $ not (P.equalFilePath "/a/../c" "/c"))+    ,("not (W.equalFilePath \"/a/../c\" \"/c\")", property $ not (W.equalFilePath "/a/../c" "/c"))+    ,("not (AFP_P.equalFilePath (\"/a/../c\") (\"/c\"))", property $ not (AFP_P.equalFilePath ("/a/../c") ("/c")))+    ,("not (AFP_W.equalFilePath (\"/a/../c\") (\"/c\"))", property $ not (AFP_W.equalFilePath ("/a/../c") ("/c")))+    ,("not (P.equalFilePath \"foo\" \"/foo\")", property $ not (P.equalFilePath "foo" "/foo"))+    ,("not (W.equalFilePath \"foo\" \"/foo\")", property $ not (W.equalFilePath "foo" "/foo"))+    ,("not (AFP_P.equalFilePath (\"foo\") (\"/foo\"))", property $ not (AFP_P.equalFilePath ("foo") ("/foo")))+    ,("not (AFP_W.equalFilePath (\"foo\") (\"/foo\"))", property $ not (AFP_W.equalFilePath ("foo") ("/foo")))+    ,("not (P.equalFilePath \"foo\" \"FOO\")", property $ not (P.equalFilePath "foo" "FOO"))+    ,("not (AFP_P.equalFilePath (\"foo\") (\"FOO\"))", property $ not (AFP_P.equalFilePath ("foo") ("FOO")))+    ,("W.equalFilePath \"foo\" \"FOO\"", property $ W.equalFilePath "foo" "FOO")+    ,("AFP_W.equalFilePath (\"foo\") (\"FOO\")", property $ AFP_W.equalFilePath ("foo") ("FOO"))+    ,("not (W.equalFilePath \"C:\" \"C:/\")", property $ not (W.equalFilePath "C:" "C:/"))+    ,("not (AFP_W.equalFilePath (\"C:\") (\"C:/\"))", property $ not (AFP_W.equalFilePath ("C:") ("C:/")))+    ,("P.makeRelative \"/directory\" \"/directory/file.ext\" == \"file.ext\"", property $ P.makeRelative "/directory" "/directory/file.ext" == "file.ext")+    ,("W.makeRelative \"/directory\" \"/directory/file.ext\" == \"file.ext\"", property $ W.makeRelative "/directory" "/directory/file.ext" == "file.ext")+    ,("AFP_P.makeRelative (\"/directory\") (\"/directory/file.ext\") == (\"file.ext\")", property $ AFP_P.makeRelative ("/directory") ("/directory/file.ext") == ("file.ext"))+    ,("AFP_W.makeRelative (\"/directory\") (\"/directory/file.ext\") == (\"file.ext\")", property $ AFP_W.makeRelative ("/directory") ("/directory/file.ext") == ("file.ext"))+    ,("P.makeRelative (P.takeDirectory x) x `P.equalFilePath` P.takeFileName x", property $ \(QFilePathValidP x) -> P.makeRelative (P.takeDirectory x) x `P.equalFilePath` P.takeFileName x)+    ,("W.makeRelative (W.takeDirectory x) x `W.equalFilePath` W.takeFileName x", property $ \(QFilePathValidW x) -> W.makeRelative (W.takeDirectory x) x `W.equalFilePath` W.takeFileName x)+    ,("AFP_P.makeRelative (AFP_P.takeDirectory x) x `AFP_P.equalFilePath` AFP_P.takeFileName x", property $ \(QFilePathValidAFP_P x) -> AFP_P.makeRelative (AFP_P.takeDirectory x) x `AFP_P.equalFilePath` AFP_P.takeFileName x)+    ,("AFP_W.makeRelative (AFP_W.takeDirectory x) x `AFP_W.equalFilePath` AFP_W.takeFileName x", property $ \(QFilePathValidAFP_W x) -> AFP_W.makeRelative (AFP_W.takeDirectory x) x `AFP_W.equalFilePath` AFP_W.takeFileName x)+    ,("P.makeRelative x x == \".\"", property $ \(QFilePath x) -> P.makeRelative x x == ".")+    ,("W.makeRelative x x == \".\"", property $ \(QFilePath x) -> W.makeRelative x x == ".")+    ,("AFP_P.makeRelative x x == (\".\")", property $ \(QFilePathAFP_P x) -> AFP_P.makeRelative x x == ("."))+    ,("AFP_W.makeRelative x x == (\".\")", property $ \(QFilePathAFP_W x) -> AFP_W.makeRelative x x == ("."))+    ,("P.equalFilePath x y || (P.isRelative x && P.makeRelative y x == x) || P.equalFilePath (y P.</> P.makeRelative y x) x", property $ \(QFilePathValidP x) (QFilePathValidP y) -> P.equalFilePath x y || (P.isRelative x && P.makeRelative y x == x) || P.equalFilePath (y P.</> P.makeRelative y x) x)+    ,("W.equalFilePath x y || (W.isRelative x && W.makeRelative y x == x) || W.equalFilePath (y W.</> W.makeRelative y x) x", property $ \(QFilePathValidW x) (QFilePathValidW y) -> W.equalFilePath x y || (W.isRelative x && W.makeRelative y x == x) || W.equalFilePath (y W.</> W.makeRelative y x) x)+    ,("AFP_P.equalFilePath x y || (AFP_P.isRelative x && AFP_P.makeRelative y x == x) || AFP_P.equalFilePath (y AFP_P.</> AFP_P.makeRelative y x) x", property $ \(QFilePathValidAFP_P x) (QFilePathValidAFP_P y) -> AFP_P.equalFilePath x y || (AFP_P.isRelative x && AFP_P.makeRelative y x == x) || AFP_P.equalFilePath (y AFP_P.</> AFP_P.makeRelative y x) x)+    ,("AFP_W.equalFilePath x y || (AFP_W.isRelative x && AFP_W.makeRelative y x == x) || AFP_W.equalFilePath (y AFP_W.</> AFP_W.makeRelative y x) x", property $ \(QFilePathValidAFP_W x) (QFilePathValidAFP_W y) -> AFP_W.equalFilePath x y || (AFP_W.isRelative x && AFP_W.makeRelative y x == x) || AFP_W.equalFilePath (y AFP_W.</> AFP_W.makeRelative y x) x)+    ,("W.makeRelative \"C:\\\\Home\" \"c:\\\\home\\\\bob\" == \"bob\"", property $ W.makeRelative "C:\\Home" "c:\\home\\bob" == "bob")+    ,("AFP_W.makeRelative (\"C:\\\\Home\") (\"c:\\\\home\\\\bob\") == (\"bob\")", property $ AFP_W.makeRelative ("C:\\Home") ("c:\\home\\bob") == ("bob"))+    ,("W.makeRelative \"C:\\\\Home\" \"c:/home/bob\" == \"bob\"", property $ W.makeRelative "C:\\Home" "c:/home/bob" == "bob")+    ,("AFP_W.makeRelative (\"C:\\\\Home\") (\"c:/home/bob\") == (\"bob\")", property $ AFP_W.makeRelative ("C:\\Home") ("c:/home/bob") == ("bob"))+    ,("W.makeRelative \"C:\\\\Home\" \"D:\\\\Home\\\\Bob\" == \"D:\\\\Home\\\\Bob\"", property $ W.makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob")+    ,("AFP_W.makeRelative (\"C:\\\\Home\") (\"D:\\\\Home\\\\Bob\") == (\"D:\\\\Home\\\\Bob\")", property $ AFP_W.makeRelative ("C:\\Home") ("D:\\Home\\Bob") == ("D:\\Home\\Bob"))+    ,("W.makeRelative \"C:\\\\Home\" \"C:Home\\\\Bob\" == \"C:Home\\\\Bob\"", property $ W.makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob")+    ,("AFP_W.makeRelative (\"C:\\\\Home\") (\"C:Home\\\\Bob\") == (\"C:Home\\\\Bob\")", property $ AFP_W.makeRelative ("C:\\Home") ("C:Home\\Bob") == ("C:Home\\Bob"))+    ,("W.makeRelative \"/Home\" \"/home/bob\" == \"bob\"", property $ W.makeRelative "/Home" "/home/bob" == "bob")+    ,("AFP_W.makeRelative (\"/Home\") (\"/home/bob\") == (\"bob\")", property $ AFP_W.makeRelative ("/Home") ("/home/bob") == ("bob"))+    ,("W.makeRelative \"/\" \"//\" == \"//\"", property $ W.makeRelative "/" "//" == "//")+    ,("AFP_W.makeRelative (\"/\") (\"//\") == (\"//\")", property $ AFP_W.makeRelative ("/") ("//") == ("//"))+    ,("P.makeRelative \"/Home\" \"/home/bob\" == \"/home/bob\"", property $ P.makeRelative "/Home" "/home/bob" == "/home/bob")+    ,("AFP_P.makeRelative (\"/Home\") (\"/home/bob\") == (\"/home/bob\")", property $ AFP_P.makeRelative ("/Home") ("/home/bob") == ("/home/bob"))+    ,("P.makeRelative \"/home/\" \"/home/bob/foo/bar\" == \"bob/foo/bar\"", property $ P.makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar")+    ,("AFP_P.makeRelative (\"/home/\") (\"/home/bob/foo/bar\") == (\"bob/foo/bar\")", property $ AFP_P.makeRelative ("/home/") ("/home/bob/foo/bar") == ("bob/foo/bar"))+    ,("P.makeRelative \"/fred\" \"bob\" == \"bob\"", property $ P.makeRelative "/fred" "bob" == "bob")+    ,("AFP_P.makeRelative (\"/fred\") (\"bob\") == (\"bob\")", property $ AFP_P.makeRelative ("/fred") ("bob") == ("bob"))+    ,("P.makeRelative \"/file/test\" \"/file/test/fred\" == \"fred\"", property $ P.makeRelative "/file/test" "/file/test/fred" == "fred")+    ,("AFP_P.makeRelative (\"/file/test\") (\"/file/test/fred\") == (\"fred\")", property $ AFP_P.makeRelative ("/file/test") ("/file/test/fred") == ("fred"))+    ,("P.makeRelative \"/file/test\" \"/file/test/fred/\" == \"fred/\"", property $ P.makeRelative "/file/test" "/file/test/fred/" == "fred/")+    ,("AFP_P.makeRelative (\"/file/test\") (\"/file/test/fred/\") == (\"fred/\")", property $ AFP_P.makeRelative ("/file/test") ("/file/test/fred/") == ("fred/"))+    ,("P.makeRelative \"some/path\" \"some/path/a/b/c\" == \"a/b/c\"", property $ P.makeRelative "some/path" "some/path/a/b/c" == "a/b/c")+    ,("AFP_P.makeRelative (\"some/path\") (\"some/path/a/b/c\") == (\"a/b/c\")", property $ AFP_P.makeRelative ("some/path") ("some/path/a/b/c") == ("a/b/c"))+    ,("P.normalise \"/file/\\\\test////\" == \"/file/\\\\test/\"", property $ P.normalise "/file/\\test////" == "/file/\\test/")+    ,("AFP_P.normalise (\"/file/\\\\test////\") == (\"/file/\\\\test/\")", property $ AFP_P.normalise ("/file/\\test////") == ("/file/\\test/"))+    ,("P.normalise \"/file/./test\" == \"/file/test\"", property $ P.normalise "/file/./test" == "/file/test")+    ,("AFP_P.normalise (\"/file/./test\") == (\"/file/test\")", property $ AFP_P.normalise ("/file/./test") == ("/file/test"))+    ,("P.normalise \"/test/file/../bob/fred/\" == \"/test/file/../bob/fred/\"", property $ P.normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/")+    ,("AFP_P.normalise (\"/test/file/../bob/fred/\") == (\"/test/file/../bob/fred/\")", property $ AFP_P.normalise ("/test/file/../bob/fred/") == ("/test/file/../bob/fred/"))+    ,("P.normalise \"../bob/fred/\" == \"../bob/fred/\"", property $ P.normalise "../bob/fred/" == "../bob/fred/")+    ,("AFP_P.normalise (\"../bob/fred/\") == (\"../bob/fred/\")", property $ AFP_P.normalise ("../bob/fred/") == ("../bob/fred/"))+    ,("P.normalise \"/a/../c\" == \"/a/../c\"", property $ P.normalise "/a/../c" == "/a/../c")+    ,("AFP_P.normalise (\"/a/../c\") == (\"/a/../c\")", property $ AFP_P.normalise ("/a/../c") == ("/a/../c"))+    ,("P.normalise \"./bob/fred/\" == \"bob/fred/\"", property $ P.normalise "./bob/fred/" == "bob/fred/")+    ,("AFP_P.normalise (\"./bob/fred/\") == (\"bob/fred/\")", property $ AFP_P.normalise ("./bob/fred/") == ("bob/fred/"))+    ,("W.normalise \"c:\\\\file/bob\\\\\" == \"C:\\\\file\\\\bob\\\\\"", property $ W.normalise "c:\\file/bob\\" == "C:\\file\\bob\\")+    ,("AFP_W.normalise (\"c:\\\\file/bob\\\\\") == (\"C:\\\\file\\\\bob\\\\\")", property $ AFP_W.normalise ("c:\\file/bob\\") == ("C:\\file\\bob\\"))+    ,("W.normalise \"c:\\\\\" == \"C:\\\\\"", property $ W.normalise "c:\\" == "C:\\")+    ,("AFP_W.normalise (\"c:\\\\\") == (\"C:\\\\\")", property $ AFP_W.normalise ("c:\\") == ("C:\\"))+    ,("W.normalise \"C:.\\\\\" == \"C:\"", property $ W.normalise "C:.\\" == "C:")+    ,("AFP_W.normalise (\"C:.\\\\\") == (\"C:\")", property $ AFP_W.normalise ("C:.\\") == ("C:"))+    ,("W.normalise \"\\\\\\\\server\\\\test\" == \"\\\\\\\\server\\\\test\"", property $ W.normalise "\\\\server\\test" == "\\\\server\\test")+    ,("AFP_W.normalise (\"\\\\\\\\server\\\\test\") == (\"\\\\\\\\server\\\\test\")", property $ AFP_W.normalise ("\\\\server\\test") == ("\\\\server\\test"))+    ,("W.normalise \"//server/test\" == \"\\\\\\\\server\\\\test\"", property $ W.normalise "//server/test" == "\\\\server\\test")+    ,("AFP_W.normalise (\"//server/test\") == (\"\\\\\\\\server\\\\test\")", property $ AFP_W.normalise ("//server/test") == ("\\\\server\\test"))+    ,("W.normalise \"c:/file\" == \"C:\\\\file\"", property $ W.normalise "c:/file" == "C:\\file")+    ,("AFP_W.normalise (\"c:/file\") == (\"C:\\\\file\")", property $ AFP_W.normalise ("c:/file") == ("C:\\file"))+    ,("W.normalise \"/file\" == \"\\\\file\"", property $ W.normalise "/file" == "\\file")+    ,("AFP_W.normalise (\"/file\") == (\"\\\\file\")", property $ AFP_W.normalise ("/file") == ("\\file"))+    ,("W.normalise \"\\\\\" == \"\\\\\"", property $ W.normalise "\\" == "\\")+    ,("AFP_W.normalise (\"\\\\\") == (\"\\\\\")", property $ AFP_W.normalise ("\\") == ("\\"))+    ,("W.normalise \"/./\" == \"\\\\\"", property $ W.normalise "/./" == "\\")+    ,("AFP_W.normalise (\"/./\") == (\"\\\\\")", property $ AFP_W.normalise ("/./") == ("\\"))+    ,("P.normalise \".\" == \".\"", property $ P.normalise "." == ".")+    ,("W.normalise \".\" == \".\"", property $ W.normalise "." == ".")+    ,("AFP_P.normalise (\".\") == (\".\")", property $ AFP_P.normalise (".") == ("."))+    ,("AFP_W.normalise (\".\") == (\".\")", property $ AFP_W.normalise (".") == ("."))+    ,("P.normalise \"./\" == \"./\"", property $ P.normalise "./" == "./")+    ,("AFP_P.normalise (\"./\") == (\"./\")", property $ AFP_P.normalise ("./") == ("./"))+    ,("P.normalise \"./.\" == \"./\"", property $ P.normalise "./." == "./")+    ,("AFP_P.normalise (\"./.\") == (\"./\")", property $ AFP_P.normalise ("./.") == ("./"))+    ,("P.normalise \"/./\" == \"/\"", property $ P.normalise "/./" == "/")+    ,("AFP_P.normalise (\"/./\") == (\"/\")", property $ AFP_P.normalise ("/./") == ("/"))+    ,("P.normalise \"/\" == \"/\"", property $ P.normalise "/" == "/")+    ,("AFP_P.normalise (\"/\") == (\"/\")", property $ AFP_P.normalise ("/") == ("/"))+    ,("P.normalise \"bob/fred/.\" == \"bob/fred/\"", property $ P.normalise "bob/fred/." == "bob/fred/")+    ,("AFP_P.normalise (\"bob/fred/.\") == (\"bob/fred/\")", property $ AFP_P.normalise ("bob/fred/.") == ("bob/fred/"))+    ,("P.normalise \"//home\" == \"/home\"", property $ P.normalise "//home" == "/home")+    ,("AFP_P.normalise (\"//home\") == (\"/home\")", property $ AFP_P.normalise ("//home") == ("/home"))+    ,("P.isValid \"\" == False", property $ P.isValid "" == False)+    ,("W.isValid \"\" == False", property $ W.isValid "" == False)+    ,("AFP_P.isValid (\"\") == False", property $ AFP_P.isValid ("") == False)+    ,("AFP_W.isValid (\"\") == False", property $ AFP_W.isValid ("") == False)+    ,("P.isValid \"\\0\" == False", property $ P.isValid "\0" == False)+    ,("W.isValid \"\\0\" == False", property $ W.isValid "\0" == False)+    ,("AFP_P.isValid (\"\\0\") == False", property $ AFP_P.isValid ("\0") == False)+    ,("AFP_W.isValid (\"\\0\") == False", property $ AFP_W.isValid ("\0") == False)+    ,("P.isValid \"/random_ path:*\" == True", property $ P.isValid "/random_ path:*" == True)+    ,("AFP_P.isValid (\"/random_ path:*\") == True", property $ AFP_P.isValid ("/random_ path:*") == True)+    ,("P.isValid x == not (null x)", property $ \(QFilePath x) -> P.isValid x == not (null x))+    ,("AFP_P.isValid x == not ((SBS.null . getPosixString) x)", property $ \(QFilePathAFP_P x) -> AFP_P.isValid x == not ((SBS.null . getPosixString) x))+    ,("W.isValid \"c:\\\\test\" == True", property $ W.isValid "c:\\test" == True)+    ,("AFP_W.isValid (\"c:\\\\test\") == True", property $ AFP_W.isValid ("c:\\test") == True)+    ,("W.isValid \"c:\\\\test:of_test\" == False", property $ W.isValid "c:\\test:of_test" == False)+    ,("AFP_W.isValid (\"c:\\\\test:of_test\") == False", property $ AFP_W.isValid ("c:\\test:of_test") == False)+    ,("W.isValid \"test*\" == False", property $ W.isValid "test*" == False)+    ,("AFP_W.isValid (\"test*\") == False", property $ AFP_W.isValid ("test*") == False)+    ,("W.isValid \"c:\\\\test\\\\nul\" == False", property $ W.isValid "c:\\test\\nul" == False)+    ,("AFP_W.isValid (\"c:\\\\test\\\\nul\") == False", property $ AFP_W.isValid ("c:\\test\\nul") == False)+    ,("W.isValid \"c:\\\\test\\\\prn.txt\" == False", property $ W.isValid "c:\\test\\prn.txt" == False)+    ,("AFP_W.isValid (\"c:\\\\test\\\\prn.txt\") == False", property $ AFP_W.isValid ("c:\\test\\prn.txt") == False)+    ,("W.isValid \"c:\\\\nul\\\\file\" == False", property $ W.isValid "c:\\nul\\file" == False)+    ,("AFP_W.isValid (\"c:\\\\nul\\\\file\") == False", property $ AFP_W.isValid ("c:\\nul\\file") == False)+    ,("W.isValid \"\\\\\\\\\" == False", property $ W.isValid "\\\\" == False)+    ,("AFP_W.isValid (\"\\\\\\\\\") == False", property $ AFP_W.isValid ("\\\\") == False)+    ,("W.isValid \"\\\\\\\\\\\\foo\" == False", property $ W.isValid "\\\\\\foo" == False)+    ,("AFP_W.isValid (\"\\\\\\\\\\\\foo\") == False", property $ AFP_W.isValid ("\\\\\\foo") == False)+    ,("W.isValid \"\\\\\\\\?\\\\D:file\" == False", property $ W.isValid "\\\\?\\D:file" == False)+    ,("AFP_W.isValid (\"\\\\\\\\?\\\\D:file\") == False", property $ AFP_W.isValid ("\\\\?\\D:file") == False)+    ,("W.isValid \"foo\\tbar\" == False", property $ W.isValid "foo\tbar" == False)+    ,("AFP_W.isValid (\"foo\\tbar\") == False", property $ AFP_W.isValid ("foo\tbar") == False)+    ,("W.isValid \"nul .txt\" == False", property $ W.isValid "nul .txt" == False)+    ,("AFP_W.isValid (\"nul .txt\") == False", property $ AFP_W.isValid ("nul .txt") == False)+    ,("W.isValid \" nul.txt\" == True", property $ W.isValid " nul.txt" == True)+    ,("AFP_W.isValid (\" nul.txt\") == True", property $ AFP_W.isValid (" nul.txt") == True)+    ,("P.isValid (P.makeValid x)", property $ \(QFilePath x) -> P.isValid (P.makeValid x))+    ,("W.isValid (W.makeValid x)", property $ \(QFilePath x) -> W.isValid (W.makeValid x))+    ,("AFP_P.isValid (AFP_P.makeValid x)", property $ \(QFilePathAFP_P x) -> AFP_P.isValid (AFP_P.makeValid x))+    ,("AFP_W.isValid (AFP_W.makeValid x)", property $ \(QFilePathAFP_W x) -> AFP_W.isValid (AFP_W.makeValid x))+    ,("P.isValid x ==> P.makeValid x == x", property $ \(QFilePath x) -> P.isValid x ==> P.makeValid x == x)+    ,("W.isValid x ==> W.makeValid x == x", property $ \(QFilePath x) -> W.isValid x ==> W.makeValid x == x)+    ,("AFP_P.isValid x ==> AFP_P.makeValid x == x", property $ \(QFilePathAFP_P x) -> AFP_P.isValid x ==> AFP_P.makeValid x == x)+    ,("AFP_W.isValid x ==> AFP_W.makeValid x == x", property $ \(QFilePathAFP_W x) -> AFP_W.isValid x ==> AFP_W.makeValid x == x)+    ,("P.makeValid \"\" == \"_\"", property $ P.makeValid "" == "_")+    ,("W.makeValid \"\" == \"_\"", property $ W.makeValid "" == "_")+    ,("AFP_P.makeValid (\"\") == (\"_\")", property $ AFP_P.makeValid ("") == ("_"))+    ,("AFP_W.makeValid (\"\") == (\"_\")", property $ AFP_W.makeValid ("") == ("_"))+    ,("P.makeValid \"file\\0name\" == \"file_name\"", property $ P.makeValid "file\0name" == "file_name")+    ,("W.makeValid \"file\\0name\" == \"file_name\"", property $ W.makeValid "file\0name" == "file_name")+    ,("AFP_P.makeValid (\"file\\0name\") == (\"file_name\")", property $ AFP_P.makeValid ("file\0name") == ("file_name"))+    ,("AFP_W.makeValid (\"file\\0name\") == (\"file_name\")", property $ AFP_W.makeValid ("file\0name") == ("file_name"))+    ,("W.makeValid \"c:\\\\already\\\\/valid\" == \"c:\\\\already\\\\/valid\"", property $ W.makeValid "c:\\already\\/valid" == "c:\\already\\/valid")+    ,("AFP_W.makeValid (\"c:\\\\already\\\\/valid\") == (\"c:\\\\already\\\\/valid\")", property $ AFP_W.makeValid ("c:\\already\\/valid") == ("c:\\already\\/valid"))+    ,("W.makeValid \"c:\\\\test:of_test\" == \"c:\\\\test_of_test\"", property $ W.makeValid "c:\\test:of_test" == "c:\\test_of_test")+    ,("AFP_W.makeValid (\"c:\\\\test:of_test\") == (\"c:\\\\test_of_test\")", property $ AFP_W.makeValid ("c:\\test:of_test") == ("c:\\test_of_test"))+    ,("W.makeValid \"test*\" == \"test_\"", property $ W.makeValid "test*" == "test_")+    ,("AFP_W.makeValid (\"test*\") == (\"test_\")", property $ AFP_W.makeValid ("test*") == ("test_"))+    ,("W.makeValid \"c:\\\\test\\\\nul\" == \"c:\\\\test\\\\nul_\"", property $ W.makeValid "c:\\test\\nul" == "c:\\test\\nul_")+    ,("AFP_W.makeValid (\"c:\\\\test\\\\nul\") == (\"c:\\\\test\\\\nul_\")", property $ AFP_W.makeValid ("c:\\test\\nul") == ("c:\\test\\nul_"))+    ,("W.makeValid \"c:\\\\test\\\\prn.txt\" == \"c:\\\\test\\\\prn_.txt\"", property $ W.makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt")+    ,("AFP_W.makeValid (\"c:\\\\test\\\\prn.txt\") == (\"c:\\\\test\\\\prn_.txt\")", property $ AFP_W.makeValid ("c:\\test\\prn.txt") == ("c:\\test\\prn_.txt"))+    ,("W.makeValid \"c:\\\\test/prn.txt\" == \"c:\\\\test/prn_.txt\"", property $ W.makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt")+    ,("AFP_W.makeValid (\"c:\\\\test/prn.txt\") == (\"c:\\\\test/prn_.txt\")", property $ AFP_W.makeValid ("c:\\test/prn.txt") == ("c:\\test/prn_.txt"))+    ,("W.makeValid \"c:\\\\nul\\\\file\" == \"c:\\\\nul_\\\\file\"", property $ W.makeValid "c:\\nul\\file" == "c:\\nul_\\file")+    ,("AFP_W.makeValid (\"c:\\\\nul\\\\file\") == (\"c:\\\\nul_\\\\file\")", property $ AFP_W.makeValid ("c:\\nul\\file") == ("c:\\nul_\\file"))+    ,("W.makeValid \"\\\\\\\\\\\\foo\" == \"\\\\\\\\drive\"", property $ W.makeValid "\\\\\\foo" == "\\\\drive")+    ,("AFP_W.makeValid (\"\\\\\\\\\\\\foo\") == (\"\\\\\\\\drive\")", property $ AFP_W.makeValid ("\\\\\\foo") == ("\\\\drive"))+    ,("W.makeValid \"\\\\\\\\?\\\\D:file\" == \"\\\\\\\\?\\\\D:\\\\file\"", property $ W.makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file")+    ,("AFP_W.makeValid (\"\\\\\\\\?\\\\D:file\") == (\"\\\\\\\\?\\\\D:\\\\file\")", property $ AFP_W.makeValid ("\\\\?\\D:file") == ("\\\\?\\D:\\file"))+    ,("W.makeValid \"nul .txt\" == \"nul _.txt\"", property $ W.makeValid "nul .txt" == "nul _.txt")+    ,("AFP_W.makeValid (\"nul .txt\") == (\"nul _.txt\")", property $ AFP_W.makeValid ("nul .txt") == ("nul _.txt"))+    ,("W.isRelative \"path\\\\test\" == True", property $ W.isRelative "path\\test" == True)+    ,("AFP_W.isRelative (\"path\\\\test\") == True", property $ AFP_W.isRelative ("path\\test") == True)+    ,("W.isRelative \"c:\\\\test\" == False", property $ W.isRelative "c:\\test" == False)+    ,("AFP_W.isRelative (\"c:\\\\test\") == False", property $ AFP_W.isRelative ("c:\\test") == False)+    ,("W.isRelative \"c:test\" == True", property $ W.isRelative "c:test" == True)+    ,("AFP_W.isRelative (\"c:test\") == True", property $ AFP_W.isRelative ("c:test") == True)+    ,("W.isRelative \"c:\\\\\" == False", property $ W.isRelative "c:\\" == False)+    ,("AFP_W.isRelative (\"c:\\\\\") == False", property $ AFP_W.isRelative ("c:\\") == False)+    ,("W.isRelative \"c:/\" == False", property $ W.isRelative "c:/" == False)+    ,("AFP_W.isRelative (\"c:/\") == False", property $ AFP_W.isRelative ("c:/") == False)+    ,("W.isRelative \"c:\" == True", property $ W.isRelative "c:" == True)+    ,("AFP_W.isRelative (\"c:\") == True", property $ AFP_W.isRelative ("c:") == True)+    ,("W.isRelative \"\\\\\\\\foo\" == False", property $ W.isRelative "\\\\foo" == False)+    ,("AFP_W.isRelative (\"\\\\\\\\foo\") == False", property $ AFP_W.isRelative ("\\\\foo") == False)+    ,("W.isRelative \"\\\\\\\\?\\\\foo\" == False", property $ W.isRelative "\\\\?\\foo" == False)+    ,("AFP_W.isRelative (\"\\\\\\\\?\\\\foo\") == False", property $ AFP_W.isRelative ("\\\\?\\foo") == False)+    ,("W.isRelative \"\\\\\\\\?\\\\UNC\\\\foo\" == False", property $ W.isRelative "\\\\?\\UNC\\foo" == False)+    ,("AFP_W.isRelative (\"\\\\\\\\?\\\\UNC\\\\foo\") == False", property $ AFP_W.isRelative ("\\\\?\\UNC\\foo") == False)+    ,("W.isRelative \"/foo\" == True", property $ W.isRelative "/foo" == True)+    ,("AFP_W.isRelative (\"/foo\") == True", property $ AFP_W.isRelative ("/foo") == True)+    ,("W.isRelative \"\\\\foo\" == True", property $ W.isRelative "\\foo" == True)+    ,("AFP_W.isRelative (\"\\\\foo\") == True", property $ AFP_W.isRelative ("\\foo") == True)+    ,("P.isRelative \"test/path\" == True", property $ P.isRelative "test/path" == True)+    ,("AFP_P.isRelative (\"test/path\") == True", property $ AFP_P.isRelative ("test/path") == True)+    ,("P.isRelative \"/test\" == False", property $ P.isRelative "/test" == False)+    ,("AFP_P.isRelative (\"/test\") == False", property $ AFP_P.isRelative ("/test") == False)+    ,("P.isRelative \"/\" == False", property $ P.isRelative "/" == False)+    ,("AFP_P.isRelative (\"/\") == False", property $ AFP_P.isRelative ("/") == False)+    ,("P.isAbsolute x == not (P.isRelative x)", property $ \(QFilePath x) -> P.isAbsolute x == not (P.isRelative x))+    ,("W.isAbsolute x == not (W.isRelative x)", property $ \(QFilePath x) -> W.isAbsolute x == not (W.isRelative x))+    ,("AFP_P.isAbsolute x == not (AFP_P.isRelative x)", property $ \(QFilePathAFP_P x) -> AFP_P.isAbsolute x == not (AFP_P.isRelative x))+    ,("AFP_W.isAbsolute x == not (AFP_W.isRelative x)", property $ \(QFilePathAFP_W x) -> AFP_W.isAbsolute x == not (AFP_W.isRelative x))+    ]