packages feed

filepath 1.4.0.0 → 1.4.1.0

raw patch · 6 files changed

+193/−72 lines, 6 filesdep ~QuickCheckdep ~basePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: QuickCheck, base

API changes (from Hackage documentation)

+ System.FilePath.Posix: infixr 5 </>
+ System.FilePath.Posix: infixr 7 <.>
+ System.FilePath.Posix: replaceExtensions :: FilePath -> String -> FilePath
+ System.FilePath.Posix: stripExtension :: String -> FilePath -> Maybe FilePath
+ System.FilePath.Windows: infixr 5 </>
+ System.FilePath.Windows: infixr 7 <.>
+ System.FilePath.Windows: replaceExtensions :: FilePath -> String -> FilePath
+ System.FilePath.Windows: stripExtension :: String -> FilePath -> Maybe FilePath

Files

README.md view
@@ -1,4 +1,4 @@-# FilePath [![Hackage version](https://img.shields.io/hackage/v/filepath.svg?style=flat)](https://hackage.haskell.org/package/filepath) [![Build Status](https://img.shields.io/travis/haskell/filepath.svg?style=flat)](https://travis-ci.org/haskell/filepath)+# FilePath [![Hackage version](https://img.shields.io/hackage/v/filepath.svg?label=Hackage)](https://hackage.haskell.org/package/filepath) [![Linux Build Status](https://img.shields.io/travis/haskell/filepath.svg?label=Linux%20build)](https://travis-ci.org/haskell/filepath) [![Windows Build Status](https://img.shields.io/appveyor/ci/ndmitchell/filepath.svg?label=Windows%20build)](https://ci.appveyor.com/project/ndmitchell/filepath)  The `filepath` package provides functionality for manipulating `FilePath` values, and is shipped with both [GHC](https://www.haskell.org/ghc/) and the [Haskell Platform](https://www.haskell.org/platform/). It provides three modules: @@ -7,3 +7,13 @@ * [`System.FilePath`](http://hackage.haskell.org/package/filepath/docs/System-FilePath.html) 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).++### Should `FilePath` be an abstract data type?++The answer for this library is "no". While an abstract `FilePath` has some advantages (mostly type safety), it also has some disadvantages:++* In Haskell the definition is `type FilePath = String`, and all file-orientated functions operate on this type alias, e.g. `readFile`/`writeFile`. Any abstract type would require wrappers for these functions or lots of casts between `String` and the abstraction.+* It is not immediately obvious what a `FilePath` is, and what is just a pure `String`. For example, `/path/file.ext` is a `FilePath`. Is `/`? `/path`? `path`? `file.ext`? `.ext`? `file`?+* Often it is useful to represent invalid files, e.g. `/foo/*.txt` probably isn't an actual file, but a glob pattern. Other programs use `foo//bar` for globs, which is definitely not a file, but might want to be stored as a `FilePath`.+* Some programs use syntactic non-semantic details of the `FilePath` to change their behaviour. For example, `foo`, `foo/` and `foo/.` are all similar, and refer to the same location on disk, but may behave differently when passed to command-line tools.+* A useful step to introducing an abstract `FilePath` is to reduce the amount of manipulating `FilePath` values like lists. This library hopes to help in that effort.
System/FilePath/Internal.hs view
@@ -19,7 +19,7 @@ -- A library for 'FilePath' manipulations, using MODULE_NAME style paths on -- all platforms. Importing "System.FilePath" is usually better. ----- Given the eample 'FilePath': @\/directory\/file.ext@+-- Given the example 'FilePath': @\/directory\/file.ext@ -- -- We can use the following functions to extract pieces. --@@ -75,7 +75,8 @@     -- * Extension functions     splitExtension,     takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),-    splitExtensions, dropExtensions, takeExtensions,+    splitExtensions, dropExtensions, takeExtensions, replaceExtensions,+    stripExtension,      -- * Filename\/directory functions     splitFileName,@@ -104,6 +105,7 @@  import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper) import Data.Maybe(isJust)+import Data.List(stripPrefix)  import System.Environment(getEnv) @@ -188,6 +190,8 @@ -- 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>@@ -254,7 +258,7 @@ (-<.>) :: FilePath -> String -> FilePath (-<.>) = replaceExtension --- | Set the extension of a file, overwriting one if already present, equivalent to '<.>'.+-- | 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"@@ -308,6 +312,28 @@ hasExtension = any isExtSeparator . takeFileName  +-- | 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")@@ -338,7 +364,18 @@ 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 @@ -459,7 +496,7 @@ --------------------------------------------------------------------- -- Operations on a filepath, as a list of directories --- | Split a filename into directory and file. 'combine' is the inverse.+-- | 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")@@ -601,39 +638,7 @@ replaceDirectory x dir = combineAlways dir (takeFileName x)  --- | Combine two paths, if the second path starts with a path separator or a--- drive letter, then it returns the second.------ > Valid x => combine (takeDirectory x) (takeFileName x) `equalFilePath` x------ Combined:--- > Posix:   combine "/" "test" == "/test"--- > Posix:   combine "home" "bob" == "home/bob"--- > Posix:   combine "x:" "foo" == "x:/foo"--- > Windows: combine "C:\\foo" "bar" == "C:\\foo\\bar"--- > Windows: combine "home" "bob" == "home\\bob"------ Not combined:--- > Posix:   combine "home" "/bob" == "/bob"--- > Windows: combine "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 @combine@ is to never combine these forms.------ > Windows: combine "home" "/bob" == "/bob"--- > Windows: combine "home" "\\bob" == "\\bob"--- > Windows: combine "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 @combine@ is to never combine these forms.------ > Windows: combine "D:\\foo" "C:bar" == "C:bar"--- > Windows: combine "C:\\foo" "C:bar" == "C:bar"+-- | An alias for '</>'. combine :: FilePath -> FilePath -> FilePath combine a b | hasLeadingPathSeparator b || hasDrive b = b             | otherwise = combineAlways a b@@ -648,10 +653,47 @@                       _ -> a ++ [pathSeparator] ++ b  --- | Join two values with a path separator. For examples and caveats see the equivalent function 'combine'.+-- | 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 @@ -727,7 +769,10 @@             | otherwise = dropTrailingPathSeparator $ normalise x  --- | Contract a filename, based on a relative path.+-- | 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@.@@ -834,15 +879,22 @@         repSlash x = if isPathSeparator x then pathSeparator else x  -- Information for validity functions on Windows. See [1].-badCharacters :: [Char]-badCharacters = ":*?><|\""+isBadCharacter :: Char -> Bool+isBadCharacter x = x >= '\0' && x <= '\31' || x `elem` ":*?><|\""+ badElements :: [FilePath]-badElements = ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "CLOCK$"]+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?+-- | 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@@ -854,17 +906,21 @@ -- > 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 (`elem` badCharacters) x2) &&+        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 (dropExtensions x) `elem` badElements+        f x = map toUpper (dropWhileEnd (== ' ') $ dropExtensions x) `elem` badElements   -- | Take a FilePath and make it valid; does not change already valid FilePaths.@@ -872,6 +928,7 @@ -- > 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_"@@ -881,10 +938,11 @@ -- > 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 = 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)@@ -893,13 +951,12 @@         (drv,pth) = splitDrive path          validChars = map f-        f x | x `elem` badCharacters = '_'-            | otherwise = x+        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 a `elem` badElements then a ++ "_" <.> b else x+        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x             where (a,b) = splitExtensions x  @@ -963,3 +1020,9 @@ -- 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)
changelog.md view
@@ -2,6 +2,18 @@  _Note: below all `FilePath` values are unquoted, so `\\` really means two backslashes._ +## 1.4.1.0  *Dec 2015*++ * Bundled with GHC 8.0.1++ * Add `replaceExtensions` and `stripExtension` functions.++ * Make `isValid` detect more invalid Windows paths, e.g. `nul .txt` and `foo\nbar`.++ * Improve the documentation.++ * Bug fix: `isValid "\0"` now returns `False`, instead of `True`+ ## 1.4.0.0  *Mar 2015*    * Bundled with GHC 7.10.1
filepath.cabal view
@@ -1,5 +1,5 @@ name:           filepath-version:        1.4.0.0+version:        1.4.1.0 -- NOTE: Don't forget to update ./changelog.md license:        BSD3 license-file:   LICENSE@@ -26,13 +26,19 @@  extra-source-files:     System/FilePath/Internal.hs+extra-doc-files:     README.md     changelog.md +source-repository head+    type:     git+    location: https://github.com/haskell/filepath.git+ library     default-language: Haskell98     other-extensions:         CPP+        PatternGuards     if impl(GHC >= 7.2)         other-extensions: Safe @@ -42,7 +48,7 @@         System.FilePath.Windows      build-depends:-        base >= 4 && < 4.9+        base >= 4 && < 4.10      ghc-options: -Wall @@ -58,8 +64,4 @@     build-depends:         filepath,         base,-        QuickCheck >= 2.7 && < 2.8--source-repository head-    type:     git-    location: https://github.com/haskell/filepath.git+        QuickCheck >= 2.7 && < 2.9
tests/TestGen.hs view
@@ -105,6 +105,24 @@     ,("W.hasExtension \"/directory/path\" == False", test $ W.hasExtension "/directory/path" == False)     ,("null (P.takeExtension x) == not (P.hasExtension x)", test $ \(QFilePath x) -> null (P.takeExtension x) == not (P.hasExtension x))     ,("null (W.takeExtension x) == not (W.hasExtension x)", test $ \(QFilePath x) -> null (W.takeExtension x) == not (W.hasExtension x))+    ,("P.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", test $ P.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")+    ,("W.stripExtension \"hs.o\" \"foo.x.hs.o\" == Just \"foo.x\"", test $ W.stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x")+    ,("P.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", test $ P.stripExtension "hi.o" "foo.x.hs.o" == Nothing)+    ,("W.stripExtension \"hi.o\" \"foo.x.hs.o\" == Nothing", test $ W.stripExtension "hi.o" "foo.x.hs.o" == Nothing)+    ,("P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x)", test $ \(QFilePath x) -> P.dropExtension x == fromJust (P.stripExtension (P.takeExtension x) x))+    ,("W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x)", test $ \(QFilePath x) -> W.dropExtension x == fromJust (W.stripExtension (W.takeExtension x) x))+    ,("P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x)", test $ \(QFilePath x) -> P.dropExtensions x == fromJust (P.stripExtension (P.takeExtensions x) x))+    ,("W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x)", test $ \(QFilePath x) -> W.dropExtensions x == fromJust (W.stripExtension (W.takeExtensions x) x))+    ,("P.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", test $ P.stripExtension ".c.d" "a.b.c.d" == Just "a.b")+    ,("W.stripExtension \".c.d\" \"a.b.c.d\" == Just \"a.b\"", test $ W.stripExtension ".c.d" "a.b.c.d" == Just "a.b")+    ,("P.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", test $ P.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")+    ,("W.stripExtension \".c.d\" \"a.b..c.d\" == Just \"a.b.\"", test $ W.stripExtension ".c.d" "a.b..c.d" == Just "a.b.")+    ,("P.stripExtension \"baz\" \"foo.bar\" == Nothing", test $ P.stripExtension "baz" "foo.bar" == Nothing)+    ,("W.stripExtension \"baz\" \"foo.bar\" == Nothing", test $ W.stripExtension "baz" "foo.bar" == Nothing)+    ,("P.stripExtension \"bar\" \"foobar\" == Nothing", test $ P.stripExtension "bar" "foobar" == Nothing)+    ,("W.stripExtension \"bar\" \"foobar\" == Nothing", test $ W.stripExtension "bar" "foobar" == Nothing)+    ,("P.stripExtension \"\" x == Just x", test $ \(QFilePath x) -> P.stripExtension "" x == Just x)+    ,("W.stripExtension \"\" x == Just x", test $ \(QFilePath x) -> W.stripExtension "" x == Just x)     ,("P.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", test $ P.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))     ,("W.splitExtensions \"/directory/path.ext\" == (\"/directory/path\", \".ext\")", test $ W.splitExtensions "/directory/path.ext" == ("/directory/path", ".ext"))     ,("P.splitExtensions \"file.tar.gz\" == (\"file\", \".tar.gz\")", test $ P.splitExtensions "file.tar.gz" == ("file", ".tar.gz"))@@ -127,6 +145,10 @@     ,("W.takeExtensions \"/directory/path.ext\" == \".ext\"", test $ W.takeExtensions "/directory/path.ext" == ".ext")     ,("P.takeExtensions \"file.tar.gz\" == \".tar.gz\"", test $ P.takeExtensions "file.tar.gz" == ".tar.gz")     ,("W.takeExtensions \"file.tar.gz\" == \".tar.gz\"", test $ W.takeExtensions "file.tar.gz" == ".tar.gz")+    ,("P.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", test $ P.replaceExtensions "file.fred.bob" "txt" == "file.txt")+    ,("W.replaceExtensions \"file.fred.bob\" \"txt\" == \"file.txt\"", test $ W.replaceExtensions "file.fred.bob" "txt" == "file.txt")+    ,("P.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", test $ P.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")+    ,("W.replaceExtensions \"file.fred.bob\" \"tar.gz\" == \"file.tar.gz\"", test $ W.replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz")     ,("uncurry (++) (P.splitDrive x) == x", test $ \(QFilePath x) -> uncurry (++) (P.splitDrive x) == x)     ,("uncurry (++) (W.splitDrive x) == x", test $ \(QFilePath x) -> uncurry (++) (W.splitDrive x) == x)     ,("W.splitDrive \"file\" == (\"\", \"file\")", test $ W.splitDrive "file" == ("", "file"))@@ -265,22 +287,24 @@     ,("W.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", test $ W.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")     ,("P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x", test $ \(QFilePathValidP x) -> P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x)     ,("W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x", test $ \(QFilePathValidW x) -> W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x)-    ,("P.combine (P.takeDirectory x) (P.takeFileName x) `P.equalFilePath` x", test $ \(QFilePathValidP x) -> P.combine (P.takeDirectory x) (P.takeFileName x) `P.equalFilePath` x)-    ,("W.combine (W.takeDirectory x) (W.takeFileName x) `W.equalFilePath` x", test $ \(QFilePathValidW x) -> W.combine (W.takeDirectory x) (W.takeFileName x) `W.equalFilePath` x)-    ,("P.combine \"/\" \"test\" == \"/test\"", test $ P.combine "/" "test" == "/test")-    ,("P.combine \"home\" \"bob\" == \"home/bob\"", test $ P.combine "home" "bob" == "home/bob")-    ,("P.combine \"x:\" \"foo\" == \"x:/foo\"", test $ P.combine "x:" "foo" == "x:/foo")-    ,("W.combine \"C:\\\\foo\" \"bar\" == \"C:\\\\foo\\\\bar\"", test $ W.combine "C:\\foo" "bar" == "C:\\foo\\bar")-    ,("W.combine \"home\" \"bob\" == \"home\\\\bob\"", test $ W.combine "home" "bob" == "home\\bob")-    ,("P.combine \"home\" \"/bob\" == \"/bob\"", test $ P.combine "home" "/bob" == "/bob")-    ,("W.combine \"home\" \"C:\\\\bob\" == \"C:\\\\bob\"", test $ W.combine "home" "C:\\bob" == "C:\\bob")-    ,("W.combine \"home\" \"/bob\" == \"/bob\"", test $ W.combine "home" "/bob" == "/bob")-    ,("W.combine \"home\" \"\\\\bob\" == \"\\\\bob\"", test $ W.combine "home" "\\bob" == "\\bob")-    ,("W.combine \"C:\\\\home\" \"\\\\bob\" == \"\\\\bob\"", test $ W.combine "C:\\home" "\\bob" == "\\bob")-    ,("W.combine \"D:\\\\foo\" \"C:bar\" == \"C:bar\"", test $ W.combine "D:\\foo" "C:bar" == "C:bar")-    ,("W.combine \"C:\\\\foo\" \"C:bar\" == \"C:bar\"", test $ W.combine "C:\\foo" "C:bar" == "C:bar")     ,("\"/directory\" P.</> \"file.ext\" == \"/directory/file.ext\"", test $ "/directory" P.</> "file.ext" == "/directory/file.ext")     ,("\"/directory\" W.</> \"file.ext\" == \"/directory\\\\file.ext\"", test $ "/directory" W.</> "file.ext" == "/directory\\file.ext")+    ,("\"directory\" P.</> \"/file.ext\" == \"/file.ext\"", test $ "directory" P.</> "/file.ext" == "/file.ext")+    ,("\"directory\" W.</> \"/file.ext\" == \"/file.ext\"", test $ "directory" W.</> "/file.ext" == "/file.ext")+    ,("(P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x", test $ \(QFilePathValidP x) -> (P.takeDirectory x P.</> P.takeFileName x) `P.equalFilePath` x)+    ,("(W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x", test $ \(QFilePathValidW x) -> (W.takeDirectory x W.</> W.takeFileName x) `W.equalFilePath` x)+    ,("\"/\" P.</> \"test\" == \"/test\"", test $ "/" P.</> "test" == "/test")+    ,("\"home\" P.</> \"bob\" == \"home/bob\"", test $ "home" P.</> "bob" == "home/bob")+    ,("\"x:\" P.</> \"foo\" == \"x:/foo\"", test $ "x:" P.</> "foo" == "x:/foo")+    ,("\"C:\\\\foo\" W.</> \"bar\" == \"C:\\\\foo\\\\bar\"", test $ "C:\\foo" W.</> "bar" == "C:\\foo\\bar")+    ,("\"home\" W.</> \"bob\" == \"home\\\\bob\"", test $ "home" W.</> "bob" == "home\\bob")+    ,("\"home\" P.</> \"/bob\" == \"/bob\"", test $ "home" P.</> "/bob" == "/bob")+    ,("\"home\" W.</> \"C:\\\\bob\" == \"C:\\\\bob\"", test $ "home" W.</> "C:\\bob" == "C:\\bob")+    ,("\"home\" W.</> \"/bob\" == \"/bob\"", test $ "home" W.</> "/bob" == "/bob")+    ,("\"home\" W.</> \"\\\\bob\" == \"\\\\bob\"", test $ "home" W.</> "\\bob" == "\\bob")+    ,("\"C:\\\\home\" W.</> \"\\\\bob\" == \"\\\\bob\"", test $ "C:\\home" W.</> "\\bob" == "\\bob")+    ,("\"D:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", test $ "D:\\foo" W.</> "C:bar" == "C:bar")+    ,("\"C:\\\\foo\" W.</> \"C:bar\" == \"C:bar\"", test $ "C:\\foo" W.</> "C:bar" == "C:bar")     ,("P.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", test $ P.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])     ,("W.splitPath \"/directory/file.ext\" == [\"/\", \"directory/\", \"file.ext\"]", test $ W.splitPath "/directory/file.ext" == ["/", "directory/", "file.ext"])     ,("concat (P.splitPath x) == x", test $ \(QFilePath x) -> concat (P.splitPath x) == x)@@ -369,6 +393,8 @@     ,("P.normalise \"//home\" == \"/home\"", test $ P.normalise "//home" == "/home")     ,("P.isValid \"\" == False", test $ P.isValid "" == False)     ,("W.isValid \"\" == False", test $ W.isValid "" == False)+    ,("P.isValid \"\\0\" == False", test $ P.isValid "\0" == False)+    ,("W.isValid \"\\0\" == False", test $ W.isValid "\0" == False)     ,("P.isValid \"/random_ path:*\" == True", test $ P.isValid "/random_ path:*" == True)     ,("P.isValid x == not (null x)", test $ \(QFilePath x) -> P.isValid x == not (null x))     ,("W.isValid \"c:\\\\test\" == True", test $ W.isValid "c:\\test" == True)@@ -380,12 +406,17 @@     ,("W.isValid \"\\\\\\\\\" == False", test $ W.isValid "\\\\" == False)     ,("W.isValid \"\\\\\\\\\\\\foo\" == False", test $ W.isValid "\\\\\\foo" == False)     ,("W.isValid \"\\\\\\\\?\\\\D:file\" == False", test $ W.isValid "\\\\?\\D:file" == False)+    ,("W.isValid \"foo\\tbar\" == False", test $ W.isValid "foo\tbar" == False)+    ,("W.isValid \"nul .txt\" == False", test $ W.isValid "nul .txt" == False)+    ,("W.isValid \" nul.txt\" == True", test $ W.isValid " nul.txt" == True)     ,("P.isValid (P.makeValid x)", test $ \(QFilePath x) -> P.isValid (P.makeValid x))     ,("W.isValid (W.makeValid x)", test $ \(QFilePath x) -> W.isValid (W.makeValid x))     ,("P.isValid x ==> P.makeValid x == x", test $ \(QFilePath x) -> P.isValid x ==> P.makeValid x == x)     ,("W.isValid x ==> W.makeValid x == x", test $ \(QFilePath x) -> W.isValid x ==> W.makeValid x == x)     ,("P.makeValid \"\" == \"_\"", test $ P.makeValid "" == "_")     ,("W.makeValid \"\" == \"_\"", test $ W.makeValid "" == "_")+    ,("P.makeValid \"file\\0name\" == \"file_name\"", test $ P.makeValid "file\0name" == "file_name")+    ,("W.makeValid \"file\\0name\" == \"file_name\"", test $ W.makeValid "file\0name" == "file_name")     ,("W.makeValid \"c:\\\\already\\\\/valid\" == \"c:\\\\already\\\\/valid\"", test $ W.makeValid "c:\\already\\/valid" == "c:\\already\\/valid")     ,("W.makeValid \"c:\\\\test:of_test\" == \"c:\\\\test_of_test\"", test $ W.makeValid "c:\\test:of_test" == "c:\\test_of_test")     ,("W.makeValid \"test*\" == \"test_\"", test $ W.makeValid "test*" == "test_")@@ -395,6 +426,7 @@     ,("W.makeValid \"c:\\\\nul\\\\file\" == \"c:\\\\nul_\\\\file\"", test $ W.makeValid "c:\\nul\\file" == "c:\\nul_\\file")     ,("W.makeValid \"\\\\\\\\\\\\foo\" == \"\\\\\\\\drive\"", test $ W.makeValid "\\\\\\foo" == "\\\\drive")     ,("W.makeValid \"\\\\\\\\?\\\\D:file\" == \"\\\\\\\\?\\\\D:\\\\file\"", test $ W.makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file")+    ,("W.makeValid \"nul .txt\" == \"nul _.txt\"", test $ W.makeValid "nul .txt" == "nul _.txt")     ,("W.isRelative \"path\\\\test\" == True", test $ W.isRelative "path\\test" == True)     ,("W.isRelative \"c:\\\\test\" == False", test $ W.isRelative "c:\\test" == False)     ,("W.isRelative \"c:test\" == True", test $ W.isRelative "c:test" == True)
tests/TestUtil.hs view
@@ -3,11 +3,13 @@     (==>), QFilePath(..), QFilePathValidW(..), QFilePathValidP(..),     test, Test,     module Test.QuickCheck,-    module Data.List+    module Data.List,+    module Data.Maybe     ) where  import Test.QuickCheck hiding ((==>)) import Data.List+import Data.Maybe import Control.Monad import qualified System.FilePath.Windows as W import qualified System.FilePath.Posix as P