filepath-bytestring 1.4.2.1.0 → 1.4.2.1.1
raw patch · 8 files changed
+173/−84 lines, 8 filesdep +utf8-stringPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: utf8-string
API changes (from Hackage documentation)
+ System.FilePath.Posix.ByteString: decodeFilePath :: RawFilePath -> FilePath
+ System.FilePath.Posix.ByteString: encodeFilePath :: FilePath -> RawFilePath
+ System.FilePath.Posix.ByteString: normalise :: RawFilePath -> RawFilePath
+ System.FilePath.Windows.ByteString: decodeFilePath :: RawFilePath -> FilePath
+ System.FilePath.Windows.ByteString: encodeFilePath :: FilePath -> RawFilePath
+ System.FilePath.Windows.ByteString: normalise :: RawFilePath -> RawFilePath
Files
- CHANGELOG +13/−0
- System/FilePath/ByteString.hs +0/−3
- System/FilePath/Internal.hs +129/−39
- TODO +0/−8
- filepath-bytestring.cabal +5/−3
- tests/TestEquiv.hs +2/−0
- tests/TestGen.hs +22/−0
- tests/TestUtil.hs +2/−31
CHANGELOG view
@@ -1,3 +1,16 @@+filepath-bytestring (1.4.2.1.1) unstable; urgency=medium++ * When running on Windows, RawFilePath is assumed to be encoded with+ UTF-8, rather than the windows default of UTF-16. This lets the user+ use OverloadedStrings for RawFilePaths embedded in their code.+ * Added two conversion functions, encodeFilePath and decodeFilePath.+ * Added normalise.+ * Optimise with -O2, a benchmark shows that improves the speed+ of </> by around 7%.+ * Inline </>, which speeds it up by around 3%.++ -- Joey Hess <id@joeyh.name> Wed, 18 Dec 2019 13:42:16 -0400+ filepath-bytestring (1.4.2.1.0) unstable; urgency=medium * Initial release, based on filepath 1.4.2.1.
System/FilePath/ByteString.hs view
@@ -1,7 +1,4 @@ {-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE Safe #-}-#endif {- | Module : System.FilePath.ByteString Copyright : (c) Neil Mitchell 2005-2014, (c) Joey Hess 2019
System/FilePath/Internal.hs view
@@ -1,6 +1,3 @@-#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE Safe #-}-#endif {-# LANGUAGE OverloadedStrings #-} -- This template expects CPP definitions for:@@ -63,6 +60,40 @@ ( -- * Types RawFilePath,+ -- * Filename encoding+ -- + -- | When using `FilePath`, you do not usually need to care about how+ -- it is encoded, because it is a @[Char]@ and encoding and decoding is+ -- handled by IO actions as needed. Unfortunately the situation is more+ -- complicated when using `RawFilePath`.+ --+ -- It's natural to enable `OverloadedStrings` and use it to construct+ -- a `RawFilePath`, eg @"foo" '</>' "bar"@. A gotcha though is that+ -- any non-ascii characters will be truncated to 8 bits. That is not a+ -- limitation of this library, but of the `IsString` implementation + -- of `ByteString`.+ --+ -- Posix filenames do not have any defined encoding. This library+ -- assumes that whatever encoding may be used for a `RawFilePath`,+ -- it is compatable with ASCII. In particular, 0x2F (/) is always+ -- a path separator, and 0x2E (.) is assumed to be an extension+ -- separator. All encodings in common use are compatible with ASCII,+ -- and unix tools have always made similar assumptions,+ -- so this is unlikely to be a problem, unless you are dealing with+ -- EBCDIC or similar historical oddities.+ --+ -- Windows's API expects filenames to be encoded with UTF-16.+ -- This is especially problimatic when using OverloadedStrings+ -- since a ByteString "bar" is not a valid encoding for a+ -- Windows filename (but "b\\0a\\0r\\0" is). To avoid this problem,+ -- and to simplify the implementation,+ -- `RawFilePath` is assumed to be encoded with UTF-8 (not UTF-16)+ -- when this library is used on Windows.+ -- There are not currently any libraries for Windows that use+ -- `RawFilePath`, so you will probably need to convert them back to + -- `FilePath` in order to do IO in any case.+ encodeFilePath,+ decodeFilePath, -- * Separator predicates pathSeparator, pathSeparators, isPathSeparator, searchPathSeparator, isSearchPathSeparator,@@ -95,10 +126,10 @@ dropTrailingPathSeparator, -- * File name manipulations- --normalise, equalFilePath,+ normalise, --equalFilePath, --makeRelative, isRelative, isAbsolute,- isValid, -- makeValid+ isValid, --makeValid ) where @@ -108,6 +139,13 @@ import Data.Char(ord, chr, toUpper, isAsciiLower, isAsciiUpper) import Data.Maybe(isJust) import Data.Word(Word8)+#ifdef mingw32_HOST_OS+import qualified Data.ByteString.UTF8 as UTF8+#else+import qualified GHC.Foreign as GHC+import qualified GHC.IO.Encoding as Encoding+import System.IO.Unsafe (unsafePerformIO)+#endif #ifndef mingw32_HOST_OS -- Import from unix, rather than redefining, so users who import both@@ -123,6 +161,45 @@ infixr 5 </> +-- | Convert from FilePath to RawFilePath.+--+-- When run on Unix, this applies the filesystem encoding+-- (see `Encoding.getFileSystemEncoding`).+--+-- When run on Windows, this encodes as UTF-8.+encodeFilePath :: FilePath -> RawFilePath+#ifdef mingw32_HOST_OS+encodeFilePath = UTF8.fromString+#else+encodeFilePath = B.pack . map (fromIntegral . fromEnum) . encodeFilePath'++{-# NOINLINE encodeFilePath' #-}+encodeFilePath' :: FilePath -> String+encodeFilePath' f = unsafePerformIO $ do+ enc <- Encoding.getFileSystemEncoding+ GHC.withCString enc f (GHC.peekCString Encoding.char8)+#endif++-- | Convert from RawFilePath to FilePath+--+-- When run on Unix, this applies the filesystem encoding+-- (see `Encoding.getFileSystemEncoding`).+--+-- When run on Windows, this decodes UTF-8.+decodeFilePath :: RawFilePath -> FilePath+#ifdef mingw32_HOST_OS+decodeFilePath = UTF8.toString+#else+decodeFilePath = decodeFilePath' . map (toEnum . fromIntegral) . B.unpack++{-# NOINLINE decodeFilePath' #-}+decodeFilePath' :: String -> FilePath+decodeFilePath' s = unsafePerformIO $ do+ enc <- Encoding.getFileSystemEncoding+ GHC.withCString Encoding.char8 s (GHC.peekCString enc)+#endif++ --------------------------------------------------------------------- -- Platform Abstraction Methods (private) @@ -688,9 +765,10 @@ replaceDirectory :: RawFilePath -> ByteString -> RawFilePath replaceDirectory x dir = combineAlways dir (takeFileName x) +{-# INLINE combine #-} -- | An alias for '</>'. combine :: RawFilePath -> RawFilePath -> RawFilePath-combine a b | hasLeadingPathSeparator b || hasDrive b = b+combine a b | hasLeadingPathSeparator b || (not isPosix && hasDrive b) = b | otherwise = combineAlways a b -- | Combine two paths, assuming rhs is NOT absolute.@@ -794,7 +872,6 @@ {- - --------------------------------------------------------------------- -- File name manipulators @@ -810,13 +887,17 @@ -- > untested Posix: not (equalFilePath "foo" "FOO") -- > untested Windows: equalFilePath "foo" "FOO" -- > untested Windows: not (equalFilePath "C:" "C:/")-equalFilePath :: FilePath -> FilePath -> Bool+equalFilePath :: RawFilePath -> RawFilePath -> Bool equalFilePath a b = f a == f b where- f x | isWindows = dropTrailingPathSeparator $ map toLower $ normalise x+-- FIXME: B8.map will not lower-case non-ascii characters.+ f x | isWindows = dropTrailingPathSeparator $ B8.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@@ -862,6 +943,8 @@ 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@@ -870,29 +953,31 @@ -- -- * .\/ -> \"\" ----- > untested Posix: normalise "/file/\\test////" == "/file/\\test/"--- > untested Posix: normalise "/file/./test" == "/file/test"--- > untested Posix: normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"--- > untested Posix: normalise "../bob/fred/" == "../bob/fred/"--- > untested Posix: normalise "./bob/fred/" == "bob/fred/"--- > untested Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"--- > untested Windows: normalise "c:\\" == "C:\\"--- > untested Windows: normalise "C:.\\" == "C:"--- > untested Windows: normalise "\\\\server\\test" == "\\\\server\\test"--- > untested Windows: normalise "//server/test" == "\\\\server\\test"--- > untested Windows: normalise "c:/file" == "C:\\file"--- > untested Windows: normalise "/file" == "\\file"--- > untested Windows: normalise "\\" == "\\"--- > untested Windows: normalise "/./" == "\\"--- > untested normalise "." == "."--- > untested Posix: normalise "./" == "./"--- > untested Posix: normalise "./." == "./"--- > untested Posix: normalise "/./" == "/"--- > untested Posix: normalise "/" == "/"--- > untested Posix: normalise "bob/fred/." == "bob/fred/"--- > untested Posix: normalise "//home" == "/home"-normalise :: FilePath -> FilePath-normalise path = result ++ [pathSeparator | addPathSeparator]+-- > 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 "./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 :: RawFilePath -> RawFilePath+normalise path+ | addPathSeparator = result <> B.singleton pathSeparator+ | otherwise = result where (drv,pth) = splitDrive path result = joinDrive' (normaliseDrive drv) (f pth)@@ -905,28 +990,33 @@ && not (isRelativeDrive drv) isDirPath xs = hasTrailingPathSeparator xs- || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)+ || not (B.null xs) && B.last xs == extSeparator && hasTrailingPathSeparator (B.init xs) + f :: RawFilePath -> RawFilePath f = joinPath . dropDots . propSep . splitDirectories - propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs+ propSep :: [RawFilePath] -> [RawFilePath]+ propSep (x:xs) | B.all isPathSeparator x = B.singleton pathSeparator : xs | otherwise = x : xs propSep [] = [] + dropDots :: [RawFilePath] -> [RawFilePath] dropDots = filter ("." /=) -normaliseDrive :: FilePath -> FilePath+normaliseDrive :: RawFilePath -> RawFilePath normaliseDrive "" = ""-normaliseDrive _ | isPosix = [pathSeparator]+normaliseDrive _ | isPosix = B.singleton pathSeparator normaliseDrive drive = if isJust $ readDriveLetter x2- then map toUpper x2+ then upcasedriveletter x2 else x2 where- x2 = map repSlash drive+ x2 = B.map repSlash drive repSlash x = if isPathSeparator x then pathSeparator else x --}+ -- A Windows drive letter is an ascii character, so it's safe to+ -- operate on the ByteString containing it using B8.+ upcasedriveletter = B8.map toUpper -- Information for validity functions on Windows. See [1]. isBadCharacter :: Word8 -> Bool
TODO view
@@ -1,11 +1,3 @@ * Some functions have not been ported from FilePath and are commented out: splitSearchPath, getSearchPath, normalise, equalFilePath, makeRelative, makeValid. Implementations would be accepted if you need any of these.-* combine checks hasDrive on posix, this is unnecessary- since hasLeadingPathSeparator checks the same thing- there. isWindows && hasDrive would cover windows while- avoiding that overhead. Benchmark to see if it improves.-* Test suite may not build on Windows due to its use of- getFileSystemEncoding.-* How filename encoding even works in this ByteString library should perhaps be- documented.
filepath-bytestring.cabal view
@@ -1,6 +1,6 @@ cabal-version: >= 1.18 name: filepath-bytestring-version: 1.4.2.1.0+version: 1.4.2.1.1 -- NOTE: Don't forget to update CHANGELOG and the filepath version below license: BSD3 license-file: LICENSE@@ -51,10 +51,12 @@ build-depends: base >= 4 && < 4.15, bytestring- if !os(Windows)+ if os(Windows)+ build-depends: utf8-string+ else build-depends: unix - ghc-options: -Wall+ ghc-options: -O2 -Wall test-suite filepath-tests type: exitcode-stdio-1.0
tests/TestEquiv.hs view
@@ -44,6 +44,7 @@ ,("equiv Posix.hasTrailingPathSeparator", equiv_1 OurPosix.hasTrailingPathSeparator TheirPosix.hasTrailingPathSeparator) ,("equiv Posix.addTrailingPathSeparator", equiv_1 OurPosix.addTrailingPathSeparator TheirPosix.addTrailingPathSeparator) ,("equiv Posix.dropTrailingPathSeparator", equiv_1 OurPosix.dropTrailingPathSeparator TheirPosix.dropTrailingPathSeparator)+ ,("equiv Posix.normalise", equiv_1 OurPosix.normalise TheirPosix.normalise) ,("equiv Windows.isPathSeparator", equiv_0 OurWindows.isPathSeparator TheirWindows.isPathSeparator) ,("equiv Windows.isSearchPathSeparator", equiv_0 OurWindows.isSearchPathSeparator TheirWindows.isSearchPathSeparator) ,("equiv Windows.isExtSeparator", equiv_0 OurWindows.isExtSeparator TheirWindows.isExtSeparator)@@ -80,4 +81,5 @@ ,("equiv Windows.hasTrailingPathSeparator", equiv_1 OurWindows.hasTrailingPathSeparator TheirWindows.hasTrailingPathSeparator) ,("equiv Windows.addTrailingPathSeparator", equiv_1 OurWindows.addTrailingPathSeparator TheirWindows.addTrailingPathSeparator) ,("equiv Windows.dropTrailingPathSeparator", equiv_1 OurWindows.dropTrailingPathSeparator TheirWindows.dropTrailingPathSeparator)+ ,("equiv Windows.normalise", equiv_1 OurWindows.normalise TheirWindows.normalise) ]
tests/TestGen.hs view
@@ -345,6 +345,28 @@ ,("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")+ ,("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 \"./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)
tests/TestUtil.hs view
@@ -16,11 +16,7 @@ import Control.Monad import qualified System.FilePath.Windows as W import qualified System.FilePath.Posix as P-import System.FilePath.ByteString (RawFilePath)-import qualified GHC.Foreign as GHC-import qualified GHC.IO.Encoding as Encoding-import System.IO.Unsafe-import qualified Data.ByteString as B+import System.FilePath.ByteString (RawFilePath, encodeFilePath) infixr 0 ==> a ==> b = not a || b@@ -29,32 +25,7 @@ toRawFilePath :: t -> RawFilePath instance ToRawFilePath [Char] where- toRawFilePath = B.pack . decodeW8NUL--decodeW8NUL :: String -> [Word8]-decodeW8NUL = intercalate [c2w8 nul] . map decodeW8 . splitc nul- where- nul = '\NUL'--c2w8 :: Char -> Word8-c2w8 = fromIntegral . fromEnum--s2w8 :: String -> [Word8]-s2w8 = map c2w8--splitc :: Eq c => c -> [c] -> [[c]]-splitc c s = case break (== c) s of- (i, _c:rest) -> i : splitc c rest- (i, []) -> i : []--decodeW8 :: FilePath -> [Word8]-decodeW8 = s2w8 . encodeFilePath--{-# NOINLINE encodeFilePath #-}-encodeFilePath :: String -> String-encodeFilePath fp = unsafePerformIO $ do- enc <- Encoding.getFileSystemEncoding- GHC.withCString enc fp (GHC.peekCString Encoding.char8)+ toRawFilePath = encodeFilePath newtype QFilePathValidW = QFilePathValidW FilePath deriving Show