h)F=ת      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                                                               1.5.2.0 (c) Neil Mitchell 2005-2014BSD3ndmitchell@gmail.comstableportable Safe-InferredZ9filepath*Is the operating system Unix or Linux likefilepath$Is the operating system Windows like2filepathThe character that separates directories. In the case where more than one character is possible, 2 is the 'ideal' one. Windows: pathSeparator == '\\' Posix: pathSeparator == '/' isPathSeparator pathSeparator3filepath$The list of all possible separators. Windows: pathSeparators == ['\\', '/'] Posix: pathSeparators == ['/'] pathSeparator `elem` pathSeparators4filepathRather than using (== 2)5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)5filepathThe character that is used to separate the entries in the $PATH environment variable. Windows: searchPathSeparator == ';' Posix: searchPathSeparator == ':'6filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)7filepathFile extension character extSeparator == '.'8filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)9filepathTake a string, split it on the 5 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"]:filepathGet a list of FILEPATHs in the $PATH variable.;filepathSplit on the extension. A 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/","")<filepath%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"=filepath. "/directory/path.txt" -<.> "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c">filepathSet 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?filepathAdd an extension, even if there is already one there, equivalent to A. "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"@filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)AfilepathAdd 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"Bfilepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)Cfilepath5Does 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" == FalseDfilepath2Drop the given extension from a FilePath, and the "." preceding it. Returns : if the FilePath does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than F5, 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 xEfilepathSplit 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")FfilepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xGfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"HfilepathReplace all extensions of a file with a new extension. Note that > and A 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"filepathIs the given character a valid drive letter? only a-z and A-Z are letters, not isAlpha which is more unicodeyIfilepathSplit 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")Jfilepath&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"KfilepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)LfilepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)MfilepathDoes 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 "" == FalseNfilepathIs an element a drive Posix: isDrive "/" == True Posix: isDrive "/foo" == False Windows: isDrive "C:\\" == True Windows: isDrive "C:\\foo" == False isDrive "" == FalseOfilepath*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:","") Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred") Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")PfilepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xQfilepathDrop the filename. Unlike X, this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x) isPrefixOf (takeDrive x) (dropFileName x)RfilepathGet the file name. takeFileName "/directory/file.ext" == "file.ext" takeFileName "test/" == "" 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)Sfilepath0Get 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"TfilepathSet 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) == xUfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TrueVfilepathAdd 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/"Wfilepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" Windows: dropTrailingPathSeparator "\\" == "\\" Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xXfilepath*Get the directory name, move up one level.  takeDirectory "/directory/other.ext" == "/directory" isPrefixOf (takeDirectory x) 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:\\"Yfilepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xZfilepath An alias for [.filepath0Combine two paths, assuming rhs is NOT absolute.[filepathCombine 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(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"]]filepathJust as \5, 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"]^filepath!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"_filepathEquality 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 a, 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:/")`filepathContract 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.htmlthis 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"afilepathNormalise a file)// outside of the drive can be made blank/ -> 2./ -> ""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 "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"bfilepathIs 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" == TruecfilepathTake 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"dfilepath/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 "/" == FalseAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."efilepath not . d "isAbsolute x == not (isRelative x)filepathThe 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.523456789:;<>=@AB?EFGHCDORPQSTXYZ[\^]IJKMLNUVWa_`debc523456789:;<>=@AB?EFGHCDORPQSTXYZ[\^]IJKMLNUVWa_`debc=7?7[5 (c) Neil Mitchell 2005-2014BSD3ndmitchell@gmail.comstableportableSafe[523456789:;<>=@AB?EFGHCDORPQSTXYZ[\^]IJKMLNUVWa_`debc523456789:;<>=@AB?EFGHCDORPQSTXYZ[\^]IJKMLNUVWa_`debc(c) Neil Mitchell 2005-2014BSD3ndmitchell@gmail.comstableportable Safe-InferredP9filepath*Is the operating system Unix or Linux likefilepath$Is the operating system Windows likeffilepathThe character that separates directories. In the case where more than one character is possible, f is the 'ideal' one. Windows: pathSeparator == '\\' Posix: pathSeparator == '/' isPathSeparator pathSeparatorgfilepath$The list of all possible separators. Windows: pathSeparators == ['\\', '/'] Posix: pathSeparators == ['/'] pathSeparator `elem` pathSeparatorshfilepathRather than using (== f)5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)ifilepathThe character that is used to separate the entries in the $PATH environment variable. Windows: searchPathSeparator == ';' Posix: searchPathSeparator == ':'jfilepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)kfilepathFile extension character extSeparator == '.'lfilepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)mfilepathTake a string, split it on the i 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"]nfilepathGet a list of FILEPATHs in the $PATH variable.ofilepathSplit on the extension. u 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/","")pfilepath%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"qfilepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"rfilepathSet the extension of a file, overwriting one if already present, equivalent to q. 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) ysfilepathAdd an extension, even if there is already one there, equivalent to u. "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"tfilepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)ufilepathAdd an extension, even if there is already one there, equivalent to s. 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"vfilepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)wfilepath5Does 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" == Falsexfilepath2Drop the given extension from a FilePath, and the "." preceding it. Returns : if the FilePath does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than z5, 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 xyfilepathSplit 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")zfilepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions x{filepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"|filepathReplace all extensions of a file with a new extension. Note that r and u 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"filepathIs the given character a valid drive letter? only a-z and A-Z are letters, not isAlpha which is more unicodey}filepathSplit 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")~filepath&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"filepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes 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 "" == FalsefilepathIs an element a drive Posix: isDrive "/" == True Posix: isDrive "/foo" == False Windows: isDrive "C:\\" == True Windows: isDrive "C:\\foo" == False isDrive "" == Falsefilepath*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:","") Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred") Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x) isPrefixOf (takeDrive x) (dropFileName x)filepathGet the file name. takeFileName "/directory/file.ext" == "file.ext" takeFileName "test/" == "" 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd 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/"filepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" Windows: dropTrailingPathSeparator "\\" == "\\" Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xfilepath*Get the directory name, move up one level.  takeDirectory "/directory/other.ext" == "/directory" isPrefixOf (takeDirectory x) 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:\\"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepath0Combine two paths, assuming rhs is NOT absolute.filepathCombine 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(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"]filepathJust as 5, 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"]filepath!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"filepathEquality 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 , 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:/")filepathContract 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.htmlthis 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"filepathNormalise a file)// outside of the drive can be made blank/ -> f./ -> ""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 "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"filepathIs 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" == TruefilepathTake 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"filepath/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 "/" == FalseAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."filepath not .  "isAbsolute x == not (isRelative x)filepathThe 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.5fghijklmnoprqtuvsyz{|wx}~5fghijklmnoprqtuvsyz{|wx}~q7s75 Safe-Inferred'  (c) Neil Mitchell 2005-2014BSD3ndmitchell@gmail.comstableportable Safe-Inferred9filepath*Is the operating system Unix or Linux likefilepath$Is the operating system Windows likefilepathThe character that separates directories. In the case where more than one character is possible,  is the 'ideal' one. Windows: pathSeparator == '\\' Posix: pathSeparator == '/' isPathSeparator pathSeparatorfilepath$The list of all possible separators. Windows: pathSeparators == ['\\', '/'] Posix: pathSeparators == ['/'] pathSeparator `elem` pathSeparatorsfilepathRather than using (== )5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)filepathThe character that is used to separate the entries in the $PATH environment variable. Windows: searchPathSeparator == ';' Posix: searchPathSeparator == ':'filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)filepathFile extension character extSeparator == '.'filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)filepathTake a string, split it on the  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"]filepathSplit on the extension.  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/","")filepath%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"filepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"filepathSet 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) yfilepathAdd an extension, even if there is already one there, equivalent to . "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)filepathAdd 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"filepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)filepath5Does 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" == Falsefilepath9Drop the given extension from a ShortByteString, and the "." preceding it. Returns  if the ShortByteString does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than 5, 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 xfilepathSplit 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")filepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"filepathReplace all extensions of a file with a new extension. Note that  and  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"filepathIs the given character a valid drive letter? only a-z and A-Z are letters, not isAlpha which is more unicodeyfilepathSplit 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")filepath&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"filepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes 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 "" == FalsefilepathIs an element a drive Posix: isDrive "/" == True Posix: isDrive "/foo" == False Windows: isDrive "C:\\" == True Windows: isDrive "C:\\foo" == False isDrive "" == Falsefilepath*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:","") Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred") Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x) isPrefixOf (takeDrive x) (dropFileName x)filepathGet the file name. takeFileName "/directory/file.ext" == "file.ext" takeFileName "test/" == "" 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd 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/"filepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" Windows: dropTrailingPathSeparator "\\" == "\\" Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xfilepath*Get the directory name, move up one level.  takeDirectory "/directory/other.ext" == "/directory" isPrefixOf (takeDirectory x) 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:\\"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepath0Combine two paths, assuming rhs is NOT absolute.filepathCombine 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(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"]filepathJust as 5, 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"]filepath!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"filepathEquality 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 , 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:/")filepathContract 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.htmlthis 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"filepathNormalise a file)// outside of the drive can be made blank/ -> ./ -> ""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 "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"filepathIs a ShortByteString 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" == TruefilepathTake a ShortByteString 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"filepath/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 "/" == FalseAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."filepath not .  "isAbsolute x == not (isRelative x)filepathTotal conversion to char.filepath-This is unsafe and clamps at Word16 maxbound.33775 Safe-Inferredtfilepath&Type representing filenames/pathnames.*This type doesn't add any guarantees over .filepath&Ifdef around current platform (either  or ).filepathFilepaths are char[]$ data on unix as passed to syscalls.filepathFilepaths are wchar_t*' data on windows as passed to syscalls.   Safe-Inferred(Bo4filepathThe character that separates directories. In the case where more than one character is possible,  is the 'ideal' one. pathSeparator == '/'filepath$The list of all possible separators. ;pathSeparators == ['/'] pathSeparator `elem` pathSeparatorsfilepathRather than using (== )5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)filepathThe character that is used to separate the entries in the $PATH environment variable. searchPathSeparator == ':'filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)filepathFile extension character extSeparator == '.'filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)filepathTake a string, split it on the  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"]filepathSplit on the extension.  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/","")filepath%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"filepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"filepathSet 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) yfilepathAdd an extension, even if there is already one there, equivalent to . "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)filepathAdd 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"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"filepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)filepath5Does 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" == Falsefilepath2Drop the given extension from a filepath, and the "." preceding it. Returns : if the filepath does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than 5, 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 xfilepathSplit 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")filepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"filepathReplace all extensions of a file with a new extension. Note that  and  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"filepath6Split 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")filepath&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"&Join a drive and the rest of the path. 0Valid x => uncurry joinDrive (splitDrive x) == xfilepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes a path have a drive. not (hasDrive x) == null (takeDrive x) hasDrive "/foo" == True hasDrive "foo" == False hasDrive "" == FalsefilepathIs an element a drive ?isDrive "/" == True isDrive "/foo" == False isDrive "" == Falsefilepath*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 "/" == ("/","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x)filepathGet 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd a trailing file path separator if one is not already present. hasTrailingPathSeparator (addTrailingPathSeparator x) hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x addTrailingPathSeparator "test/rest" == "test/rest/"filepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xfilepath*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"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepathCombine 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"filepath(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"]filepathJust as 5, 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"]filepath!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"filepath*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 , 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")filepathContract 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.htmlthis 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"filepathNormalise a file)// outside of the drive can be made blank/ -> ./ -> ""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"filepathIs 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)filepathTake 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"filepath/Is a path relative, or is it fixed to the root? isRelative "test/path" == True isRelative "/test" == False isRelative "/" == Falsefilepath not .  "isAbsolute x == not (isRelative x)filepath QuasiQuote a >. This accepts Unicode characters and encodes as UTF-8. Runs  on the input." !#$" !#$  Safe-Inferred (O filepath"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 an  if encoding fails. If the input does not contain surrogate chars, you can use .filepath!Unsafe unicode friendly encoding.Like , except it crashes when the input contains surrogate chars. For sanitized input, this can be useful.filepath Encode a  with the specified encoding.filepathLike , except this mimics the behavior of the base library when doing filesystem operations, which is: 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)on windows does permissive UTF-16 encoding, where coding errors generate Chars in the surrogate rangeLooking 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).filepath"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  if decoding fails.filepath Decode an  with the specified encoding.filepathLike , except this mimics the behavior of the base library when doing filesystem operations, which is: 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)on windows does permissive UTF-16 encoding, where coding errors generate Chars in the surrogate rangeLooking 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).filepathConstructs an OsPath from a ByteString.On windows, this ensures valid UCS-2LE, on unix it is passed unchanged/unchecked.Throws 3 on invalid UCS-2LE on windows (although unlikely).filepathQuasiQuote an . This accepts Unicode characters and encodes as UTF-8 on unix and UTF-16LE on windows. Runs > on the input. If used as a pattern, requires turning on the  ViewPatterns extension.filepath Unpack an  to a list of .filepathPack a list of  to an .)Note that using this in conjunction with unsafeFromChar to convert from [Char] to  is probably not what you want, because it will truncate unicode code points.filepathunix text encodingfilepathwindows text encodingfilepathunix text encodingfilepathwindows text encoding   2021 Julian OspaldMIT"Julian Ospald  experimentalportable Safe-Inferred3filepathIs 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" == TruefilepathThe character that separates directories. In the case where more than one character is possible,  is the 'ideal' one. >Windows: pathSeparator == '\\'S Posix: pathSeparator == '/'filepath$The list of all possible separators. Windows: pathSeparators == ['\\', '/'] Posix: pathSeparators == ['/'] pathSeparator `elem` pathSeparatorsfilepathRather than using (== )5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)filepathThe character that is used to separate the entries in the $PATH environment variable. Posix: searchPathSeparator == ':' Windows: searchPathSeparator == ';'filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)filepathFile extension character extSeparator == '.'filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)filepathTake a string, split it on the  character.On Windows, blank items are ignored on Windows, and path elements are stripped of quotes.'On Posix, blank items are converted to .3 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"]filepathSplit on the extension.  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/","")filepath%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"filepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"filepathSet 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) yfilepathAdd an extension, even if there is already one there, equivalent to . "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)filepathAdd 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"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"filepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)filepath5Does 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" == Falsefilepath2Drop the given extension from a filepath, and the "." preceding it. Returns : if the filepath does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than 5, 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 xfilepathSplit 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")filepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"filepathReplace all extensions of a file with a new extension. Note that  and  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"filepathSplit 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")filepath&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"&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"filepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes 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 "" == FalsefilepathIs an element a drive Posix: isDrive "/" == True Posix: isDrive "/foo" == False Windows: isDrive "C:\\" == True Windows: isDrive "C:\\foo" == False isDrive "" == Falsefilepath*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:","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x)filepathGet 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd 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/"filepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" Windows: dropTrailingPathSeparator "\\" == "\\" Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xfilepath*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:\\"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepathCombine 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(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"]filepathJust as 5, 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"]filepath!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"filepath*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 , 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:/")filepathContract 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.htmlthis 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"filepathNormalise a file)// outside of the drive can be made blank/ -> ./ -> ""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"filepathTake 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"filepath/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 "/" == FalseAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."filepath not .  "isAbsolute x == not (isRelative x)%&%& (c) Neil Mitchell 2005-2014BSD3ndmitchell@gmail.comstableportable Safe-Inferredd9filepath*Is the operating system Unix or Linux likefilepath$Is the operating system Windows likefilepathThe character that separates directories. In the case where more than one character is possible,  is the 'ideal' one. Windows: pathSeparator == '\\' Posix: pathSeparator == '/' isPathSeparator pathSeparatorfilepath$The list of all possible separators. Windows: pathSeparators == ['\\', '/'] Posix: pathSeparators == ['/'] pathSeparator `elem` pathSeparatorsfilepathRather than using (== )5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)filepathThe character that is used to separate the entries in the $PATH environment variable. Windows: searchPathSeparator == ';' Posix: searchPathSeparator == ':'filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)filepathFile extension character extSeparator == '.'filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)filepathTake a string, split it on the  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"]filepathSplit on the extension.  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/","")filepath%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"filepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"filepathSet 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) yfilepathAdd an extension, even if there is already one there, equivalent to . "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)filepathAdd 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"filepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)filepath5Does 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" == Falsefilepath9Drop the given extension from a ShortByteString, and the "." preceding it. Returns  if the ShortByteString does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than 5, 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 xfilepathSplit 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")filepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"filepathReplace all extensions of a file with a new extension. Note that  and  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"filepathIs the given character a valid drive letter? only a-z and A-Z are letters, not isAlpha which is more unicodeyfilepathSplit 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")filepath&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"filepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes 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 "" == FalsefilepathIs an element a drive Posix: isDrive "/" == True Posix: isDrive "/foo" == False Windows: isDrive "C:\\" == True Windows: isDrive "C:\\foo" == False isDrive "" == Falsefilepath*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:","") Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred") Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x) isPrefixOf (takeDrive x) (dropFileName x)filepathGet the file name. takeFileName "/directory/file.ext" == "file.ext" takeFileName "test/" == "" 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd 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/"filepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" Windows: dropTrailingPathSeparator "\\" == "\\" Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive xfilepath*Get the directory name, move up one level.  takeDirectory "/directory/other.ext" == "/directory" isPrefixOf (takeDirectory x) 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:\\"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepath0Combine two paths, assuming rhs is NOT absolute.filepathCombine 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(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"]filepathJust as 5, 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"]filepath!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"filepathEquality 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 , 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:/")filepathContract 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.htmlthis 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"filepathNormalise a file)// outside of the drive can be made blank/ -> ./ -> ""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 "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"filepathIs a ShortByteString 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" == TruefilepathTake a ShortByteString 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"filepath/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 "/" == FalseAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."filepath not .  "isAbsolute x == not (isRelative x)filepathTotal conversion to char.filepath-This is unsafe and clamps at Word16 maxbound.33775 Safe-Inferred(<4filepathThe character that separates directories. In the case where more than one character is possible,  is the 'ideal' one. pathSeparator == '\\'Sfilepath$The list of all possible separators. pathSeparators == ['\\', '/'] pathSeparator `elem` pathSeparatorsfilepathRather than using (== )5, use this. Test if something is a path separator. .isPathSeparator a == (a `elem` pathSeparators)filepathThe character that is used to separate the entries in the $PATH environment variable. searchPathSeparator == ';'filepath"Is the character a file separator? 5isSearchPathSeparator a == (a == searchPathSeparator)filepathFile extension character extSeparator == '.'filepath(Is the character an extension character? 'isExtSeparator a == (a == extSeparator)filepathTake a string, split it on the  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"]filepathSplit on the extension.  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/","")filepath%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"filepath "ext" == "/directory/path.ext" "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" "foo.o" -<.> "c" == "foo.c"filepathSet 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) yfilepathAdd an extension, even if there is already one there, equivalent to . "/directory/path" <.> "ext" == "/directory/path.ext" "/directory/path" <.> ".ext" == "/directory/path.ext"filepath0Remove last extension, and the "." preceding it. dropExtension "/directory/path.ext" == "/directory/path" dropExtension x == fst (splitExtension x)filepathAdd 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"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"filepath*Does the given filename have an extension? hasExtension "/directory/path.ext" == True hasExtension "/directory/path" == False null (takeExtension x) == not (hasExtension x)filepath5Does 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" == Falsefilepath2Drop the given extension from a filepath, and the "." preceding it. Returns : if the filepath does not have the given extension, or . and the part before the extension if it does.+This function can be more predictable than 5, 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 xfilepathSplit 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")filepathDrop all extensions. dropExtensions "/directory/path.ext" == "/directory/path" dropExtensions "file.tar.gz" == "file" not $ hasExtension $ dropExtensions x not $ any isExtSeparator $ takeFileName $ dropExtensions xfilepathGet all extensions. takeExtensions "/directory/path.ext" == ".ext" takeExtensions "file.tar.gz" == ".tar.gz"filepathReplace all extensions of a file with a new extension. Note that  and  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"filepath%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")filepath&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"&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"filepathGet the drive from a filepath. !takeDrive x == fst (splitDrive x)filepathDelete the drive, if it exists. !dropDrive x == snd (splitDrive x)filepathDoes a path have a drive. not (hasDrive x) == null (takeDrive x) hasDrive "C:\\foo" == True hasDrive "C:foo" == True hasDrive "foo" == False hasDrive "" == FalsefilepathIs an element a drive isDrive "C:\\" == True isDrive "C:\\foo" == False isDrive "" == Falsefilepath*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:","")filepathSet the filename. replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" Valid x => replaceFileName x (takeFileName x) == xfilepathDrop the filename. Unlike , this function will leave a trailing path separator on the directory. dropFileName "/directory/file.ext" == "/directory/" dropFileName x == fst (splitFileName x)filepathGet 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)filepath0Get 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"filepathSet 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) == xfilepathIs an item either a directory or the last character a path separator? hasTrailingPathSeparator "test" == False hasTrailingPathSeparator "test/" == TruefilepathAdd a trailing file path separator if one is not already present. hasTrailingPathSeparator (addTrailingPathSeparator x) hasTrailingPathSeparator x ==> addTrailingPathSeparator x == xfilepath#Remove any trailing path separators dropTrailingPathSeparator "file/test/" == "file/test" dropTrailingPathSeparator "/" == "/" dropTrailingPathSeparator "\\" == "\\"filepath*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:\\"filepath1Set the directory, keeping the filename the same. replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` xfilepath An alias for .filepathCombine 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"filepath(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"]filepathJust as 5, 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"]filepath!Join path elements back together. joinPath z == foldr () "" z joinPath ["/","directory/","file.ext"] == "/directory/file.ext" Valid x => joinPath (splitPath x) == x joinPath [] == ""filepath*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 , 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:/")filepathContract 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.htmlthis 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 "/" "//" == "//"filepathNormalise a file)// outside of the drive can be made blank/ -> ./ -> ""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 "." == "."filepathIs 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" == TruefilepathTake 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"filepath/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" == TrueAccording to [1]:/"A UNC name of any format [is never relative]."6"You cannot use the "\?" prefix with a relative path."filepath not .  "isAbsolute x == not (isRelative x)filepath QuasiQuote a . This accepts Unicode characters and encodes as UTF-16LE. Runs  on the input.'()*/+,-.01'()*/+,-.01  !"#$%&%'%(%)%*%+,-,.,/,0,1,2,3,4,5,6,786879-9.9/9091929394959697:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm:;<=>?@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq:;<=>?@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmrj - . / 0 1 2 3 s t 4 5:;<=>?@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiklm : ; < = > ? @ A C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m:;<=>?@ACDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmruv wx wyz{|uvz{|uvz{}~ u v z { } ~filepath-1.5.2.0-inplaceSystem.FilePath.PosixSystem.OsPath.EncodingSystem.OsPath.TypesSystem.OsPath.Posix System.OsPathSystem.OsPath.WindowsSystem.FilePath.WindowsSystem.OsPath.Posix.InternalSystem.OsPath.InternalSystem.OsPath.Windows.InternalfilepathSystem.FilePathbaseGHC.IOFilePathos-string-2.0.2-27f806a199f6f3efce10ef8a799479bc865f98fc92b3f3b6e7a8c3bcc1b7a901!System.OsString.Encoding.InternalEncodingException EncodingErrorucs2lemkUcs2le ucs2le_DF ucs2le_EF ucs2le_decode ucs2le_encode utf16le_b mkUTF16le_b utf16le_b_DF utf16le_b_EFutf16le_b_decodeutf16le_b_encodedecodeWithBasePosixencodeWithBasePosixdecodeWithBaseWindowsencodeWithBaseWindowsshowEncodingExceptionSystem.OsString.Internal.TypesOsCharOsString PosixChar WindowsChar PosixString WindowsStringSystem.OsString.Posix encodeUtfunsafeEncodeUtf encodeWithencodeFS decodeUtf decodeWithdecodeFSunpackpackunsafeFromChartoCharSystem.OsString.InternalSystem.OsString.Windows pathSeparatorpathSeparatorsisPathSeparatorsearchPathSeparatorisSearchPathSeparator extSeparatorisExtSeparatorsplitSearchPath getSearchPathsplitExtension takeExtension-<.>replaceExtension<.> dropExtension addExtension hasExtension isExtensionOfstripExtensionsplitExtensionsdropExtensionstakeExtensionsreplaceExtensions splitDrive joinDrive takeDrive dropDrivehasDriveisDrive splitFileNamereplaceFileName dropFileName takeFileName takeBaseNamereplaceBaseNamehasTrailingPathSeparatoraddTrailingPathSeparatordropTrailingPathSeparator takeDirectoryreplaceDirectorycombine splitPathsplitDirectoriesjoinPath equalFilePath makeRelative normaliseisValid makeValid isRelative isAbsoluteOsPath PlatformPath PosixPath WindowsPathpstr fromBytesospisPosix isWindows GHC.MaybeNothingJustisLetter combineAlways stripSuffix wordToChar charToWord