Fix links and add more tests and comments
authorThomas Miedema <thomasmiedema@gmail.com>
Sun, 14 Sep 2014 14:52:05 +0000 (16:52 +0200)
committerThomas Miedema <thomasmiedema@gmail.com>
Thu, 23 Oct 2014 10:02:23 +0000 (12:02 +0200)
No tests are removed, only new ones added.

The semantics of `combine` is questionable. See also #8752. Not changing
anything for the moment.

System/FilePath/Internal.hs

index 16790fc..36e88e4 100644 (file)
 --
 -- The examples in code format descibed by each function are used to generate
 -- tests, and should give clear semantics for the functions.
+--
+-- References:
+-- [1] "Naming Files, Paths, and Namespaces"
+-- http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
 -----------------------------------------------------------------------------
 
 module System.FilePath.MODULE_NAME
@@ -337,9 +341,8 @@ addSlash :: FilePath -> FilePath -> (FilePath, FilePath)
 addSlash a xs = (a++c,d)
     where (c,d) = span isPathSeparator xs
 
--- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
+-- See [1].
 -- "\\?\D:\<path>" or "\\?\UNC\<server>\<share>"
--- a is "\\?\"
 readDriveUNC :: FilePath -> Maybe (FilePath, FilePath)
 readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =
     case map toUpper xs of
@@ -347,6 +350,7 @@ readDriveUNC (s1:s2:'?':s3:xs) | all isPathSeparator [s1,s2,s3] =
             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
@@ -365,7 +369,7 @@ readDriveShare (s1:s2:xs) | isPathSeparator s1 && isPathSeparator s2 =
 readDriveShare _ = Nothing
 
 {- assume you have already seen \\ -}
-{- share\bob -> "share","\","bob" -}
+{- share\bob -> "share\", "bob" -}
 readDriveShareName :: String -> (FilePath, FilePath)
 readDriveShareName name = addSlash a b
     where (a,b) = break isPathSeparator name
@@ -403,11 +407,21 @@ 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 :: FilePath -> Bool
 isDrive = null . dropDrive
 
@@ -509,9 +523,9 @@ addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pat
 -- | Remove any trailing path separators
 --
 -- > dropTrailingPathSeparator "file/test/" == "file/test"
--- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
 -- > Posix:    dropTrailingPathSeparator "/" == "/"
 -- > Windows:  dropTrailingPathSeparator "\\" == "\\"
+-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
 dropTrailingPathSeparator :: FilePath -> FilePath
 dropTrailingPathSeparator x =
     if hasTrailingPathSeparator x && not (isDrive x)
@@ -524,6 +538,8 @@ dropTrailingPathSeparator x =
 --
 -- >           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"
@@ -547,10 +563,35 @@ replaceDirectory x dir = combineAlways dir (takeFileName x)
 -- | Combine two paths, if the second path 'isAbsolute', 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"
 combine :: FilePath -> FilePath -> FilePath
 combine a b | hasDrive b || (not (null b) && isPathSeparator (head b)) = b
             | otherwise = combineAlways a b
@@ -590,8 +631,9 @@ splitPath x = [drive | drive /= ""] ++ f path
 
 -- | Just as 'splitPath', but don't add the trailing slashes to each element.
 --
--- > splitDirectories "test/file" == ["test","file"]
--- > splitDirectories "/test/file" == ["/","test","file"]
+-- >          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 "" == []
 splitDirectories :: FilePath -> [FilePath]
@@ -741,8 +783,7 @@ normaliseDrive drive = if isJust $ readDriveLetter x2
 
         repSlash x = if isPathSeparator x then pathSeparator else x
 
--- information for validity functions on Windows
--- see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
+-- Information for validity functions on Windows. See [1].
 badCharacters :: [Char]
 badCharacters = ":*?><|\""
 badElements :: [FilePath]
@@ -812,14 +853,27 @@ makeValid path = joinDrive drv $ validElements $ validChars pth
 -- > 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 = isRelativeDrive . takeDrive
 
 
+{- 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 = null x ||
     maybe False (not . isPathSeparator . last . fst) (readDriveLetter x)