-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Library for manipulating FilePaths in a cross platform way. -- -- This package provides functionality for manipulating FilePath -- values, and is shipped with GHC. It provides two variants for -- filepaths: -- --
-- [replaceFileName path_to_main "Test" <.> ext | ext <- ["hs","lhs"] ] ---- -- Example 2: Download a file from url and save it to -- disk: -- --
-- do let file = makeValid url -- System.Directory.createDirectoryIfMissing True (takeDirectory file) ---- -- Example 3: Compile a Haskell file, putting the .hi -- file under interface: -- --
-- takeDirectory file </> "interface" </> (takeFileName file -<.> "hi") ---- -- References: [1] Naming Files, Paths and Namespaces (Microsoft -- MSDN) module System.FilePath.Posix type FilePath = String -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- Windows: pathSeparator == '\\' -- Posix: pathSeparator == '/' -- isPathSeparator pathSeparator --pathSeparator :: Char -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [Char] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: Char -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Windows: searchPathSeparator == ';' -- Posix: searchPathSeparator == ':' --searchPathSeparator :: Char -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: Char -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: Char -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: Char -> Bool -- | Take a string, split it on the searchPathSeparator character. -- Blank items are ignored on Windows, and converted to . on -- Posix. On Windows path elements are stripped of quotes. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] -- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: String -> [FilePath] -- | Get a list of FILEPATHs in the $PATH variable. getSearchPath :: IO [FilePath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: FilePath -> (String, String)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: FilePath -> String -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: FilePath -> String -> FilePath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: FilePath -> String -> FilePath infixr 7 -<.> -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: FilePath -> FilePath -- | Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: FilePath -> String -> FilePath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: FilePath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: FilePath -> String -> FilePath infixr 7 <.> -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
--
splitExtensions :: FilePath -> (FilePath, String)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: FilePath -> FilePath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: FilePath -> String -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: FilePath -> String -> FilePath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: String -> FilePath -> Bool -- | Drop the given extension from a FilePath, and the "." -- preceding it. Returns Nothing if the FilePath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: String -> FilePath -> Maybe FilePath -- | 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:","")
--
splitFileName :: FilePath -> (String, String)
-- | Get 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) --takeFileName :: FilePath -> FilePath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: FilePath -> String -> FilePath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) -- isPrefixOf (takeDrive x) (dropFileName x) --dropFileName :: FilePath -> FilePath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: FilePath -> String -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: FilePath -> String -> FilePath -- | 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:\\" --takeDirectory :: FilePath -> FilePath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: FilePath -> String -> FilePath -- | An alias for </>. combine :: FilePath -> FilePath -> FilePath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: FilePath -> FilePath -> FilePath infixr 5 > -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: FilePath -> [FilePath] -- | 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" --joinPath :: [FilePath] -> FilePath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: FilePath -> [FilePath] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: FilePath -> (FilePath, FilePath)
-- | Join a drive and the rest of the path.
--
-- -- Valid x => uncurry joinDrive (splitDrive x) == x -- Windows: joinDrive "C:" "foo" == "C:foo" -- Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- Windows: joinDrive "/:" "foo" == "/:\\foo" --joinDrive :: FilePath -> FilePath -> FilePath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: FilePath -> FilePath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: FilePath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: FilePath -> FilePath -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: FilePath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: FilePath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: FilePath -> FilePath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: FilePath -> FilePath -- | Normalise a file -- --
-- 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" --normalise :: FilePath -> 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 normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: FilePath -> FilePath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: FilePath -> FilePath -> FilePath -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: FilePath -> Bool -- | Is a FilePath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- Posix: isValid "/random_ path:*" == True -- Posix: isValid x == not (null x) -- Windows: isValid "c:\\test" == True -- Windows: isValid "c:\\test:of_test" == False -- Windows: isValid "test*" == False -- Windows: isValid "c:\\test\\nul" == False -- Windows: isValid "c:\\test\\prn.txt" == False -- Windows: isValid "c:\\nul\\file" == False -- Windows: isValid "\\\\" == False -- Windows: isValid "\\\\\\foo" == False -- Windows: isValid "\\\\?\\D:file" == False -- Windows: isValid "foo\tbar" == False -- Windows: isValid "nul .txt" == False -- Windows: isValid " nul.txt" == True --isValid :: FilePath -> Bool -- | Take a FilePath and make it valid; does not change already valid -- FILEPATHs. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" -- Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid" -- Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test" -- Windows: makeValid "test*" == "test_" -- Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_" -- Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt" -- Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt" -- Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file" -- Windows: makeValid "\\\\\\foo" == "\\\\drive" -- Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- Windows: makeValid "nul .txt" == "nul _.txt" --makeValid :: FilePath -> FilePath -- | A library for FilePath manipulations, using Posix or Windows -- filepaths depending on the platform. -- -- Both System.FilePath.Posix and System.FilePath.Windows -- provide the same interface. -- -- Given the example FilePath: /directory/file.ext -- -- We can use the following functions to extract pieces. -- --
-- [replaceFileName path_to_main "Test" <.> ext | ext <- ["hs","lhs"] ] ---- -- Example 2: Download a file from url and save it to -- disk: -- --
-- do let file = makeValid url -- System.Directory.createDirectoryIfMissing True (takeDirectory file) ---- -- Example 3: Compile a Haskell file, putting the .hi -- file under interface: -- --
-- takeDirectory file </> "interface" </> (takeFileName file -<.> "hi") ---- -- References: [1] Naming Files, Paths and Namespaces (Microsoft -- MSDN) module System.FilePath type FilePath = String -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- Windows: pathSeparator == '\\' -- Posix: pathSeparator == '/' -- isPathSeparator pathSeparator --pathSeparator :: Char -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [Char] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: Char -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Windows: searchPathSeparator == ';' -- Posix: searchPathSeparator == ':' --searchPathSeparator :: Char -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: Char -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: Char -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: Char -> Bool -- | Take a string, split it on the searchPathSeparator character. -- Blank items are ignored on Windows, and converted to . on -- Posix. On Windows path elements are stripped of quotes. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] -- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: String -> [FilePath] -- | Get a list of FILEPATHs in the $PATH variable. getSearchPath :: IO [FilePath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: FilePath -> (String, String)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: FilePath -> String -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: FilePath -> String -> FilePath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: FilePath -> String -> FilePath infixr 7 -<.> -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: FilePath -> FilePath -- | Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: FilePath -> String -> FilePath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: FilePath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: FilePath -> String -> FilePath infixr 7 <.> -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
--
splitExtensions :: FilePath -> (FilePath, String)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: FilePath -> FilePath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: FilePath -> String -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: FilePath -> String -> FilePath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: String -> FilePath -> Bool -- | Drop the given extension from a FilePath, and the "." -- preceding it. Returns Nothing if the FilePath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: String -> FilePath -> Maybe FilePath -- | 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:","")
--
splitFileName :: FilePath -> (String, String)
-- | Get 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) --takeFileName :: FilePath -> FilePath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: FilePath -> String -> FilePath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) -- isPrefixOf (takeDrive x) (dropFileName x) --dropFileName :: FilePath -> FilePath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: FilePath -> String -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: FilePath -> String -> FilePath -- | 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:\\" --takeDirectory :: FilePath -> FilePath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: FilePath -> String -> FilePath -- | An alias for </>. combine :: FilePath -> FilePath -> FilePath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: FilePath -> FilePath -> FilePath infixr 5 > -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: FilePath -> [FilePath] -- | 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" --joinPath :: [FilePath] -> FilePath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: FilePath -> [FilePath] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: FilePath -> (FilePath, FilePath)
-- | Join a drive and the rest of the path.
--
-- -- Valid x => uncurry joinDrive (splitDrive x) == x -- Windows: joinDrive "C:" "foo" == "C:foo" -- Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- Windows: joinDrive "/:" "foo" == "/:\\foo" --joinDrive :: FilePath -> FilePath -> FilePath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: FilePath -> FilePath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: FilePath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: FilePath -> FilePath -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: FilePath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: FilePath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: FilePath -> FilePath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: FilePath -> FilePath -- | Normalise a file -- --
-- 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" --normalise :: FilePath -> 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 normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: FilePath -> FilePath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: FilePath -> FilePath -> FilePath -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: FilePath -> Bool -- | Is a FilePath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- Posix: isValid "/random_ path:*" == True -- Posix: isValid x == not (null x) -- Windows: isValid "c:\\test" == True -- Windows: isValid "c:\\test:of_test" == False -- Windows: isValid "test*" == False -- Windows: isValid "c:\\test\\nul" == False -- Windows: isValid "c:\\test\\prn.txt" == False -- Windows: isValid "c:\\nul\\file" == False -- Windows: isValid "\\\\" == False -- Windows: isValid "\\\\\\foo" == False -- Windows: isValid "\\\\?\\D:file" == False -- Windows: isValid "foo\tbar" == False -- Windows: isValid "nul .txt" == False -- Windows: isValid " nul.txt" == True --isValid :: FilePath -> Bool -- | Take a FilePath and make it valid; does not change already valid -- FILEPATHs. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" -- Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid" -- Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test" -- Windows: makeValid "test*" == "test_" -- Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_" -- Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt" -- Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt" -- Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file" -- Windows: makeValid "\\\\\\foo" == "\\\\drive" -- Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- Windows: makeValid "nul .txt" == "nul _.txt" --makeValid :: FilePath -> FilePath -- | A library for FilePath manipulations, using Windows style paths -- on all platforms. Importing System.FilePath is usually better. -- -- Given the example FilePath: /directory/file.ext -- -- We can use the following functions to extract pieces. -- --
-- [replaceFileName path_to_main "Test" <.> ext | ext <- ["hs","lhs"] ] ---- -- Example 2: Download a file from url and save it to -- disk: -- --
-- do let file = makeValid url -- System.Directory.createDirectoryIfMissing True (takeDirectory file) ---- -- Example 3: Compile a Haskell file, putting the .hi -- file under interface: -- --
-- takeDirectory file </> "interface" </> (takeFileName file -<.> "hi") ---- -- References: [1] Naming Files, Paths and Namespaces (Microsoft -- MSDN) module System.FilePath.Windows type FilePath = String -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- Windows: pathSeparator == '\\' -- Posix: pathSeparator == '/' -- isPathSeparator pathSeparator --pathSeparator :: Char -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [Char] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: Char -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Windows: searchPathSeparator == ';' -- Posix: searchPathSeparator == ':' --searchPathSeparator :: Char -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: Char -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: Char -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: Char -> Bool -- | Take a string, split it on the searchPathSeparator character. -- Blank items are ignored on Windows, and converted to . on -- Posix. On Windows path elements are stripped of quotes. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] -- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: String -> [FilePath] -- | Get a list of FILEPATHs in the $PATH variable. getSearchPath :: IO [FilePath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: FilePath -> (String, String)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: FilePath -> String -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: FilePath -> String -> FilePath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: FilePath -> String -> FilePath infixr 7 -<.> -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: FilePath -> FilePath -- | Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: FilePath -> String -> FilePath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: FilePath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: FilePath -> String -> FilePath infixr 7 <.> -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
--
splitExtensions :: FilePath -> (FilePath, String)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: FilePath -> FilePath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: FilePath -> String -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: FilePath -> String -> FilePath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: String -> FilePath -> Bool -- | Drop the given extension from a FilePath, and the "." -- preceding it. Returns Nothing if the FilePath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: String -> FilePath -> Maybe FilePath -- | 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:","")
--
splitFileName :: FilePath -> (String, String)
-- | Get 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) --takeFileName :: FilePath -> FilePath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: FilePath -> String -> FilePath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) -- isPrefixOf (takeDrive x) (dropFileName x) --dropFileName :: FilePath -> FilePath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: FilePath -> String -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: FilePath -> String -> FilePath -- | 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:\\" --takeDirectory :: FilePath -> FilePath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: FilePath -> String -> FilePath -- | An alias for </>. combine :: FilePath -> FilePath -> FilePath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: FilePath -> FilePath -> FilePath infixr 5 > -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: FilePath -> [FilePath] -- | 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" --joinPath :: [FilePath] -> FilePath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: FilePath -> [FilePath] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: FilePath -> (FilePath, FilePath)
-- | Join a drive and the rest of the path.
--
-- -- Valid x => uncurry joinDrive (splitDrive x) == x -- Windows: joinDrive "C:" "foo" == "C:foo" -- Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- Windows: joinDrive "/:" "foo" == "/:\\foo" --joinDrive :: FilePath -> FilePath -> FilePath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: FilePath -> FilePath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: FilePath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: FilePath -> FilePath -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: FilePath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: FilePath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: FilePath -> FilePath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: FilePath -> FilePath -- | Normalise a file -- --
-- 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" --normalise :: FilePath -> 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 normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: FilePath -> FilePath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: FilePath -> FilePath -> FilePath -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: FilePath -> Bool -- | Is a FilePath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- Posix: isValid "/random_ path:*" == True -- Posix: isValid x == not (null x) -- Windows: isValid "c:\\test" == True -- Windows: isValid "c:\\test:of_test" == False -- Windows: isValid "test*" == False -- Windows: isValid "c:\\test\\nul" == False -- Windows: isValid "c:\\test\\prn.txt" == False -- Windows: isValid "c:\\nul\\file" == False -- Windows: isValid "\\\\" == False -- Windows: isValid "\\\\\\foo" == False -- Windows: isValid "\\\\?\\D:file" == False -- Windows: isValid "foo\tbar" == False -- Windows: isValid "nul .txt" == False -- Windows: isValid " nul.txt" == True --isValid :: FilePath -> Bool -- | Take a FilePath and make it valid; does not change already valid -- FILEPATHs. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" -- Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid" -- Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test" -- Windows: makeValid "test*" == "test_" -- Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_" -- Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt" -- Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt" -- Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file" -- Windows: makeValid "\\\\\\foo" == "\\\\drive" -- Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- Windows: makeValid "nul .txt" == "nul _.txt" --makeValid :: FilePath -> FilePath module System.OsPath.Encoding data EncodingException -- | Could not decode a byte sequence because it was invalid under the -- given encoding, or ran out of input in mid-decode. EncodingError :: String -> Maybe Word8 -> EncodingException showEncodingException :: EncodingException -> String ucs2le :: TextEncoding mkUcs2le :: CodingFailureMode -> TextEncoding ucs2le_DF :: CodingFailureMode -> IO (TextDecoder ()) ucs2le_EF :: CodingFailureMode -> IO (TextEncoder ()) ucs2le_decode :: DecodeBuffer ucs2le_encode :: EncodeBuffer -- | Mimics the base encoding for filesystem operations. This should be -- total on all inputs (word16 byte arrays). -- -- Note that this has a subtle difference to -- encodeWithBaseWindows/decodeWithBaseWindows: it doesn't -- care for the 0x0000 end marker and will as such produce -- different results. Use takeWhile (/= 'NUL') on the input to -- recover this behavior. utf16le_b :: TextEncoding mkUTF16le_b :: CodingFailureMode -> TextEncoding utf16le_b_DF :: CodingFailureMode -> IO (TextDecoder ()) utf16le_b_EF :: CodingFailureMode -> IO (TextEncoder ()) utf16le_b_decode :: DecodeBuffer utf16le_b_encode :: EncodeBuffer -- | This mimics the filepath encoder base uses on unix (using PEP-383), -- with the small distinction that we're not truncating at NUL bytes -- (because we're not at the outer FFI layer). encodeWithBasePosix :: String -> IO ShortByteString -- | This mimics the filepath decoder base uses on unix (using PEP-383), -- with the small distinction that we're not truncating at NUL bytes -- (because we're not at the outer FFI layer). decodeWithBasePosix :: ShortByteString -> IO String -- | This mimics the filepath dencoder base uses on windows, with the small -- distinction that we're not truncating at NUL bytes (because we're not -- at the outer FFI layer). encodeWithBaseWindows :: String -> IO ShortByteString -- | This mimics the filepath decoder base uses on windows, with the small -- distinction that we're not truncating at NUL bytes (because we're not -- at the outer FFI layer). decodeWithBaseWindows :: ShortByteString -> IO String -- | A library for FilePath manipulations, using Posix style paths -- on all platforms. Importing System.FilePath is usually better. -- -- Given the example FilePath: /directory/file.ext -- -- We can use the following functions to extract pieces. -- --
-- [replaceFileName path_to_main "Test" <.> ext | ext <- ["hs","lhs"] ] ---- -- Example 2: Download a file from url and save it to -- disk: -- --
-- do let file = makeValid url -- System.Directory.createDirectoryIfMissing True (takeDirectory file) ---- -- Example 3: Compile a Haskell file, putting the .hi -- file under interface: -- --
-- takeDirectory file </> "interface" </> (takeFileName file -<.> "hi") ---- -- References: [1] Naming Files, Paths and Namespaces (Microsoft -- MSDN) module System.OsPath.Posix.Internal -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- Windows: pathSeparator == '\\' -- Posix: pathSeparator == '/' -- isPathSeparator pathSeparator --pathSeparator :: Word8 -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [Word8] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: Word8 -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Windows: searchPathSeparator == ';' -- Posix: searchPathSeparator == ':' --searchPathSeparator :: Word8 -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: Word8 -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: Word8 -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: Word8 -> Bool -- | Take a string, split it on the searchPathSeparator character. -- Blank items are ignored on Windows, and converted to . on -- Posix. On Windows path elements are stripped of quotes. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] -- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: ShortByteString -> [ShortByteString] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: ShortByteString -> ShortByteString -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: ShortByteString -> ShortByteString -> ShortByteString -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 7 -<.> -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: ShortByteString -> ShortByteString -- | Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: ShortByteString -> ShortByteString -> ShortByteString -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: ShortByteString -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 7 <.> -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
--
splitExtensions :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: ShortByteString -> ShortByteString -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: ShortByteString -> ShortByteString -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: ShortByteString -> ShortByteString -> ShortByteString -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: ShortByteString -> ShortByteString -> Bool -- | Drop the given extension from a ShortByteString, and the "." -- preceding it. Returns Nothing if the ShortByteString does not -- have the given extension, or Just and the part before the -- extension if it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | 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:","")
--
splitFileName :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Get 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) --takeFileName :: ShortByteString -> ShortByteString -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: ShortByteString -> ShortByteString -> ShortByteString -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) -- isPrefixOf (takeDrive x) (dropFileName x) --dropFileName :: ShortByteString -> ShortByteString -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: ShortByteString -> ShortByteString -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: ShortByteString -> ShortByteString -> ShortByteString -- | 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:\\" --takeDirectory :: ShortByteString -> ShortByteString -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: ShortByteString -> ShortByteString -> ShortByteString -- | An alias for </>. combine :: ShortByteString -> ShortByteString -> ShortByteString -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 5 > -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: ShortByteString -> [ShortByteString] -- | 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" --joinPath :: [ShortByteString] -> ShortByteString -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: ShortByteString -> [ShortByteString] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Join a drive and the rest of the path.
--
-- -- Valid x => uncurry joinDrive (splitDrive x) == x -- Windows: joinDrive "C:" "foo" == "C:foo" -- Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- Windows: joinDrive "/:" "foo" == "/:\\foo" --joinDrive :: ShortByteString -> ShortByteString -> ShortByteString -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: ShortByteString -> ShortByteString -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: ShortByteString -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: ShortByteString -> ShortByteString -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: ShortByteString -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: ShortByteString -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: ShortByteString -> ShortByteString -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: ShortByteString -> ShortByteString -- | Normalise a file -- --
-- 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" --normalise :: ShortByteString -> ShortByteString -- | Equality of two FILEPATHs. If you call -- System.Directory.canonicalizePath first this has a much -- better chance of working. Note that this doesn't follow symlinks or -- DOSNAM~1s. -- -- Similar to normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: ShortByteString -> ShortByteString -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: ShortByteString -> ShortByteString -> ShortByteString -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: ShortByteString -> Bool -- | Is 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" == True --isValid :: ShortByteString -> Bool -- | Take 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" --makeValid :: ShortByteString -> ShortByteString module System.OsPath.Types -- | Type representing filenames/pathnames. -- -- This type doesn't add any guarantees over OsString. type OsPath = OsString -- | Filepaths are wchar_t* data on windows as passed to syscalls. type WindowsPath = WindowsString -- | Filepaths are char[] data on unix as passed to syscalls. type PosixPath = PosixString -- | Ifdef around current platform (either WindowsPath or -- PosixPath). type PlatformPath = PosixPath -- | Commonly used windows string as wide character bytes. data WindowsString -- | Commonly used Posix string as uninterpreted char[] array. data PosixString data WindowsChar data PosixChar -- | Newtype representing short operating system specific strings. -- -- Internally this is either WindowsString or PosixString, -- depending on the platform. Both use unpinned ShortByteString -- for efficiency. -- -- The constructor is only exported via -- System.OsString.Internal.Types, since dealing with the -- internals isn't generally recommended, but supported in case you need -- to write platform specific code. data OsString -- | Newtype representing a code unit. -- -- On Windows, this is restricted to two-octet codepoints Word16, -- on POSIX one-octet (Word8). data OsChar module System.OsPath.Posix -- | Commonly used Posix string as uninterpreted char[] array. data PosixString data PosixChar -- | Filepaths are char[] data on unix as passed to syscalls. type PosixPath = PosixString -- | Partial unicode friendly encoding. -- -- This encodes as UTF8 (strictly), which is a good guess. -- -- Throws an EncodingException if encoding fails. If the input -- does not contain surrogate chars, you can use unsafeEncodeUtf. encodeUtf :: MonadThrow m => String -> m PosixString -- | Unsafe unicode friendly encoding. -- -- Like encodeUtf, except it crashes when the input contains -- surrogate chars. For sanitized input, this can be useful. unsafeEncodeUtf :: HasCallStack => String -> PosixString -- | Encode a String with the specified encoding. encodeWith :: TextEncoding -> String -> Either EncodingException PosixString encodeFS :: String -> IO PosixPath -- | QuasiQuote a PosixPath. This accepts Unicode characters and -- encodes as UTF-8. Runs isValid on the input. pstr :: QuasiQuoter -- | Pack a list of platform words to a platform string. -- -- Note that using this in conjunction with unsafeFromChar to -- convert from [Char] to platform string is probably not what -- you want, because it will truncate unicode code points. pack :: [PosixChar] -> PosixString -- | Partial unicode friendly decoding. -- -- This decodes as UTF8 (strictly), which is a good guess. Note that -- filenames on unix are encoding agnostic char arrays. -- -- Throws a EncodingException if decoding fails. decodeUtf :: MonadThrow m => PosixString -> m String -- | Decode a PosixString with the specified encoding. -- -- The String is forced into memory to catch all exceptions. decodeWith :: TextEncoding -> PosixString -> Either EncodingException String decodeFS :: PosixPath -> IO String -- | Unpack a platform string to a list of platform words. unpack :: PosixString -> [PosixChar] -- | Truncates to 1 octet. unsafeFromChar :: Char -> PosixChar -- | Converts back to a unicode codepoint (total). toChar :: PosixChar -> Char -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- pathSeparator == '/' --pathSeparator :: PosixChar -- | The list of all possible separators. -- --
-- pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [PosixChar] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: PosixChar -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- searchPathSeparator == ':' --searchPathSeparator :: PosixChar -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: PosixChar -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: PosixChar -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: PosixChar -> Bool -- | Take a string, split it on the searchPathSeparator character. -- -- Blank items are converted to . on , and quotes are not -- treated specially. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] --splitSearchPath :: PosixString -> [PosixPath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: PosixPath -> (PosixPath, PosixString)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: PosixPath -> PosixString -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: PosixPath -> PosixString -> PosixPath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: PosixPath -> PosixString -> PosixPath -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: PosixPath -> PosixPath -- | 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" ---- -- 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 :: PosixPath -> PosixString -> PosixPath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: PosixPath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: PosixPath -> PosixString -> PosixPath -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
--
splitExtensions :: PosixPath -> (PosixPath, PosixString)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: PosixPath -> PosixPath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: PosixPath -> PosixString -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: PosixPath -> PosixString -> PosixPath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: PosixString -> PosixPath -> Bool -- | Drop the given extension from a filepath, and the "." -- preceding it. Returns Nothing if the filepath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: PosixString -> PosixPath -> Maybe PosixPath -- | 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 "/" == ("/","")
--
splitFileName :: PosixPath -> (PosixPath, PosixPath)
-- | Get the file name.
--
-- -- takeFileName "/directory/file.ext" == "file.ext" -- takeFileName "test/" == "" -- takeFileName x `isSuffixOf` x -- takeFileName x == snd (splitFileName x) -- Valid x => takeFileName (replaceFileName x "fred") == "fred" -- Valid x => takeFileName (x </> "fred") == "fred" -- Valid x => isRelative (takeFileName x) --takeFileName :: PosixPath -> PosixPath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: PosixPath -> PosixString -> PosixPath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) --dropFileName :: PosixPath -> PosixPath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: PosixPath -> PosixPath -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: PosixPath -> PosixString -> PosixPath -- | 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 :: PosixPath -> PosixPath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: PosixPath -> PosixPath -> PosixPath -- | An alias for </>. combine :: PosixPath -> PosixPath -> PosixPath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- "/directory" </> "file.ext" == "/directory/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- "/" </> "test" == "/test" -- "home" </> "bob" == "home/bob" -- "x:" </> "foo" == "x:/foo" ---- -- Not combined: -- --
-- "home" </> "/bob" == "/bob" --(>) :: PosixPath -> PosixPath -> PosixPath -- | 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"] --splitPath :: PosixPath -> [PosixPath] -- | 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" --joinPath :: [PosixPath] -> PosixPath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: PosixPath -> [PosixPath] -- | Split a path into a drive and a path. / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- splitDrive "/test" == ("/","test")
-- splitDrive "//test" == ("//","test")
-- splitDrive "test/file" == ("","test/file")
-- splitDrive "file" == ("","file")
--
splitDrive :: PosixPath -> (PosixPath, PosixPath)
-- | 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 :: PosixPath -> PosixPath -> PosixPath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: PosixPath -> PosixPath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- hasDrive "/foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: PosixPath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: PosixPath -> PosixPath -- | Is an element a drive -- --
-- isDrive "/" == True -- isDrive "/foo" == False -- isDrive "" == False --isDrive :: PosixPath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: PosixPath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: PosixPath -> PosixPath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: PosixPath -> PosixPath -- | Normalise a file -- --
-- 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" --normalise :: PosixPath -> PosixPath -- | Equality of two filepaths. If you call -- System.Directory.canonicalizePath first this has a much -- better chance of working. Note that this doesn't follow symlinks or -- DOSNAM~1s. -- -- Similar to normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- not (equalFilePath "foo" "FOO") --equalFilePath :: PosixPath -> PosixPath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- makeRelative "/Home" "/home/bob" == "/home/bob" -- makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- makeRelative "/fred" "bob" == "bob" -- makeRelative "/file/test" "/file/test/fred" == "fred" -- makeRelative "/file/test" "/file/test/fred/" == "fred/" -- makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: PosixPath -> PosixPath -> PosixPath -- | Is a path relative, or is it fixed to the root? -- --
-- isRelative "test/path" == True -- isRelative "/test" == False -- isRelative "/" == False --isRelative :: PosixPath -> Bool -- |
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: PosixPath -> Bool -- | Is a filepath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- isValid "/random_ path:*" == True -- isValid x == not (null x) --isValid :: PosixPath -> Bool -- | Take a filepath and make it valid; does not change already valid -- filepaths. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" --makeValid :: PosixPath -> PosixPath module System.OsPath.Internal -- | 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 EncodingException if encoding fails. If the input -- does not contain surrogate chars, you can use unsafeEncodeUtf. encodeUtf :: MonadThrow m => FilePath -> m OsPath -- | Unsafe unicode friendly encoding. -- -- Like encodeUtf, except it crashes when the input contains -- surrogate chars. For sanitized input, this can be useful. unsafeEncodeUtf :: HasCallStack => String -> OsString -- | Encode a FilePath with the specified encoding. -- -- Note: on windows, we expect a "wide char" encoding (e.g. UCS-2 or -- UTF-16). Anything that works with Word16 boundaries. Picking -- an incompatible encoding may crash filepath operations. encodeWith :: TextEncoding -> TextEncoding -> FilePath -> Either EncodingException OsPath -- | Like encodeUtf, except this mimics the behavior of the base -- library when doing filesystem operations, which is: -- --
-- Windows: pathSeparator == '\\'S -- Posix: pathSeparator == '/' --pathSeparator :: OsChar -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [OsChar] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: OsChar -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Posix: searchPathSeparator == ':' -- Windows: searchPathSeparator == ';' --searchPathSeparator :: OsChar -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: OsChar -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: OsChar -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: OsChar -> Bool -- | Take a string, split it on the searchPathSeparator character. -- -- On Windows, blank items are ignored on Windows, and path elements are -- stripped of quotes. -- -- On Posix, blank items are converted to . on Posix, and quotes -- are not treated specially. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] --splitSearchPath :: OsString -> [OsPath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: OsPath -> (OsPath, OsString)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: OsPath -> OsString -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: OsPath -> OsString -> OsPath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: OsPath -> OsString -> OsPath -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: OsPath -> OsPath -- | 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" ---- -- Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: OsPath -> OsString -> OsPath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: OsPath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: OsPath -> OsString -> OsPath -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
--
splitExtensions :: OsPath -> (OsPath, OsString)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: OsPath -> OsPath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: OsPath -> OsString -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: OsPath -> OsString -> OsPath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: OsString -> OsPath -> Bool -- | Drop the given extension from a filepath, and the "." -- preceding it. Returns Nothing if the filepath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: OsString -> OsPath -> Maybe OsPath -- | Split a filename into directory and file. </> is the -- inverse. The first component will often end with a trailing slash. -- --
-- splitFileName "/directory/file.ext" == ("/directory/","file.ext")
-- Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"
-- Valid x => isValid (fst (splitFileName x))
-- splitFileName "file/bob.txt" == ("file/", "bob.txt")
-- splitFileName "file/" == ("file/", "")
-- splitFileName "bob" == ("./", "bob")
-- Posix: splitFileName "/" == ("/","")
-- Windows: splitFileName "c:" == ("c:","")
--
splitFileName :: OsPath -> (OsPath, OsPath)
-- | Get the file name.
--
-- -- takeFileName "/directory/file.ext" == "file.ext" -- takeFileName "test/" == "" -- takeFileName x `isSuffixOf` x -- takeFileName x == snd (splitFileName x) -- Valid x => takeFileName (replaceFileName x "fred") == "fred" -- Valid x => takeFileName (x </> "fred") == "fred" -- Valid x => isRelative (takeFileName x) --takeFileName :: OsPath -> OsPath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: OsPath -> OsString -> OsPath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) --dropFileName :: OsPath -> OsPath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: OsPath -> OsPath -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: OsPath -> OsString -> OsPath -- | Get the directory name, move up one level. -- --
-- takeDirectory "/directory/other.ext" == "/directory" -- takeDirectory x `isPrefixOf` x || takeDirectory x == "." -- takeDirectory "foo" == "." -- takeDirectory "/" == "/" -- takeDirectory "/foo" == "/" -- takeDirectory "/foo/bar/baz" == "/foo/bar" -- takeDirectory "/foo/bar/baz/" == "/foo/bar/baz" -- takeDirectory "foo/bar/baz" == "foo/bar" -- Windows: takeDirectory "foo\\bar" == "foo" -- Windows: takeDirectory "foo\\bar\\\\" == "foo\\bar" -- Windows: takeDirectory "C:\\" == "C:\\" --takeDirectory :: OsPath -> OsPath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: OsPath -> OsPath -> OsPath -- | An alias for </>. combine :: OsPath -> OsPath -> OsPath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: OsPath -> OsPath -> OsPath -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: OsPath -> [OsPath] -- | 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" --joinPath :: [OsPath] -> OsPath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: OsPath -> [OsPath] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: OsPath -> (OsPath, OsPath)
-- | 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" --joinDrive :: OsPath -> OsPath -> OsPath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: OsPath -> OsPath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: OsPath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: OsPath -> OsPath -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: OsPath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: OsPath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: OsPath -> OsPath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: OsPath -> OsPath -- | Normalise a file -- --
-- Posix: normalise "/file/\\test////" == "/file/\\test/" -- Posix: normalise "/file/./test" == "/file/test" -- Posix: normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/" -- Posix: normalise "../bob/fred/" == "../bob/fred/" -- Posix: normalise "/a/../c" == "/a/../c" -- Posix: normalise "./bob/fred/" == "bob/fred/" -- Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\" -- Windows: normalise "c:\\" == "C:\\" -- Windows: normalise "C:.\\" == "C:" -- Windows: normalise "\\\\server\\test" == "\\\\server\\test" -- Windows: normalise "//server/test" == "\\\\server\\test" -- Windows: normalise "c:/file" == "C:\\file" -- Windows: normalise "/file" == "\\file" -- Windows: normalise "\\" == "\\" -- Windows: normalise "/./" == "\\" -- normalise "." == "." -- Posix: normalise "./" == "./" -- Posix: normalise "./." == "./" -- Posix: normalise "/./" == "/" -- Posix: normalise "/" == "/" -- Posix: normalise "bob/fred/." == "bob/fred/" -- Posix: normalise "//home" == "/home" --normalise :: OsPath -> OsPath -- | Equality of two filepaths. If you call -- System.Directory.canonicalizePath first this has a much -- better chance of working. Note that this doesn't follow symlinks or -- DOSNAM~1s. -- -- Similar to normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: OsPath -> OsPath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: OsPath -> OsPath -> OsPath -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: OsPath -> Bool -- | Is a filepath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- Posix: isValid "/random_ path:*" == True -- Posix: isValid x == not (null x) -- Windows: isValid "c:\\test" == True -- Windows: isValid "c:\\test:of_test" == False -- Windows: isValid "test*" == False -- Windows: isValid "c:\\test\\nul" == False -- Windows: isValid "c:\\test\\prn.txt" == False -- Windows: isValid "c:\\nul\\file" == False -- Windows: isValid "\\\\" == False -- Windows: isValid "\\\\\\foo" == False -- Windows: isValid "\\\\?\\D:file" == False -- Windows: isValid "foo\tbar" == False -- Windows: isValid "nul .txt" == False -- Windows: isValid " nul.txt" == True --isValid :: OsPath -> Bool -- | Take a filepath and make it valid; does not change already valid -- filepaths. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" -- Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid" -- Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test" -- Windows: makeValid "test*" == "test_" -- Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_" -- Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt" -- Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt" -- Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file" -- Windows: makeValid "\\\\\\foo" == "\\\\drive" -- Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- Windows: makeValid "nul .txt" == "nul _.txt" --makeValid :: OsPath -> OsPath -- | A library for FilePath manipulations, using Windows style -- paths on all platforms. Importing System.FilePath is usually -- better. -- -- Given the example FilePath: /directory/file.ext -- -- We can use the following functions to extract pieces. -- --
-- [replaceFileName path_to_main "Test" <.> ext | ext <- ["hs","lhs"] ] ---- -- Example 2: Download a file from url and save it to -- disk: -- --
-- do let file = makeValid url -- System.Directory.createDirectoryIfMissing True (takeDirectory file) ---- -- Example 3: Compile a Haskell file, putting the .hi -- file under interface: -- --
-- takeDirectory file </> "interface" </> (takeFileName file -<.> "hi") ---- -- References: [1] Naming Files, Paths and Namespaces (Microsoft -- MSDN) module System.OsPath.Windows.Internal -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- Windows: pathSeparator == '\\' -- Posix: pathSeparator == '/' -- isPathSeparator pathSeparator --pathSeparator :: Word16 -- | The list of all possible separators. -- --
-- Windows: pathSeparators == ['\\', '/'] -- Posix: pathSeparators == ['/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [Word16] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: Word16 -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- Windows: searchPathSeparator == ';' -- Posix: searchPathSeparator == ':' --searchPathSeparator :: Word16 -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: Word16 -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: Word16 -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: Word16 -> Bool -- | Take a string, split it on the searchPathSeparator character. -- Blank items are ignored on Windows, and converted to . on -- Posix. On Windows path elements are stripped of quotes. -- -- Follows the recommendations in -- http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -- --
-- Posix: splitSearchPath "File1:File2:File3" == ["File1","File2","File3"] -- Posix: splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"] -- Windows: splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: ShortByteString -> [ShortByteString] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: ShortByteString -> ShortByteString -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: ShortByteString -> ShortByteString -> ShortByteString -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 7 -<.> -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: ShortByteString -> ShortByteString -- | Add an extension, even if there is already one there, equivalent to -- <.>. -- --
-- addExtension "/directory/path" "ext" == "/directory/path.ext" -- addExtension "file.txt" "bib" == "file.txt.bib" -- addExtension "file." ".bib" == "file..bib" -- addExtension "file" ".bib" == "file.bib" -- addExtension "/" "x" == "/.x" -- addExtension x "" == x -- Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext" -- Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt" --addExtension :: ShortByteString -> ShortByteString -> ShortByteString -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: ShortByteString -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 7 <.> -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
--
splitExtensions :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: ShortByteString -> ShortByteString -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: ShortByteString -> ShortByteString -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: ShortByteString -> ShortByteString -> ShortByteString -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: ShortByteString -> ShortByteString -> Bool -- | Drop the given extension from a ShortByteString, and the "." -- preceding it. Returns Nothing if the ShortByteString does not -- have the given extension, or Just and the part before the -- extension if it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | 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:","")
--
splitFileName :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Get 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) --takeFileName :: ShortByteString -> ShortByteString -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: ShortByteString -> ShortByteString -> ShortByteString -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) -- isPrefixOf (takeDrive x) (dropFileName x) --dropFileName :: ShortByteString -> ShortByteString -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: ShortByteString -> ShortByteString -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: ShortByteString -> ShortByteString -> ShortByteString -- | 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:\\" --takeDirectory :: ShortByteString -> ShortByteString -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: ShortByteString -> ShortByteString -> ShortByteString -- | An alias for </>. combine :: ShortByteString -> ShortByteString -> ShortByteString -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- Posix: "/directory" </> "file.ext" == "/directory/file.ext" -- Windows: "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- Posix: "/" </> "test" == "/test" -- Posix: "home" </> "bob" == "home/bob" -- Posix: "x:" </> "foo" == "x:/foo" -- Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar" -- Windows: "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- Posix: "home" </> "/bob" == "/bob" -- Windows: "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- On Windows, if a filepath starts with a single slash, it is relative -- to the root of the current drive. In [1], this is (confusingly) -- referred to as an absolute path. The current behavior of -- </> is to never combine these forms. -- --
-- Windows: "home" </> "/bob" == "/bob" -- Windows: "home" </> "\\bob" == "\\bob" -- Windows: "C:\\home" </> "\\bob" == "\\bob" ---- -- On Windows, from [1]: "If a file name begins with only a disk -- designator but not the backslash after the colon, it is interpreted as -- a relative path to the current directory on the drive with the -- specified letter." The current behavior of </> is to -- never combine these forms. -- --
-- Windows: "D:\\foo" </> "C:bar" == "C:bar" -- Windows: "C:\\foo" </> "C:bar" == "C:bar" --(>) :: ShortByteString -> ShortByteString -> ShortByteString infixr 5 > -- | Split a path by the directory separator. -- --
-- splitPath "/directory/file.ext" == ["/","directory/","file.ext"] -- concat (splitPath x) == x -- splitPath "test//item/" == ["test//","item/"] -- splitPath "test/item/file" == ["test/","item/","file"] -- splitPath "" == [] -- Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"] -- Posix: splitPath "/file/test" == ["/","file/","test"] --splitPath :: ShortByteString -> [ShortByteString] -- | 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" --joinPath :: [ShortByteString] -> ShortByteString -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: ShortByteString -> [ShortByteString] -- | Split a path into a drive and a path. On Posix, / is a Drive. -- --
-- uncurry (<>) (splitDrive x) == x
-- Windows: splitDrive "file" == ("","file")
-- Windows: splitDrive "c:/file" == ("c:/","file")
-- Windows: splitDrive "c:\\file" == ("c:\\","file")
-- Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- Windows: splitDrive "/d" == ("","/d")
-- Posix: splitDrive "/test" == ("/","test")
-- Posix: splitDrive "//test" == ("//","test")
-- Posix: splitDrive "test/file" == ("","test/file")
-- Posix: splitDrive "file" == ("","file")
--
splitDrive :: ShortByteString -> (ShortByteString, ShortByteString)
-- | Join a drive and the rest of the path.
--
-- -- Valid x => uncurry joinDrive (splitDrive x) == x -- Windows: joinDrive "C:" "foo" == "C:foo" -- Windows: joinDrive "C:\\" "bar" == "C:\\bar" -- Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo" -- Windows: joinDrive "/:" "foo" == "/:\\foo" --joinDrive :: ShortByteString -> ShortByteString -> ShortByteString -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: ShortByteString -> ShortByteString -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- Posix: hasDrive "/foo" == True -- Windows: hasDrive "C:\\foo" == True -- Windows: hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: ShortByteString -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: ShortByteString -> ShortByteString -- | Is an element a drive -- --
-- Posix: isDrive "/" == True -- Posix: isDrive "/foo" == False -- Windows: isDrive "C:\\" == True -- Windows: isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: ShortByteString -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: ShortByteString -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x -- Posix: addTrailingPathSeparator "test/rest" == "test/rest/" --addTrailingPathSeparator :: ShortByteString -> ShortByteString -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- Windows: dropTrailingPathSeparator "\\" == "\\" -- Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x --dropTrailingPathSeparator :: ShortByteString -> ShortByteString -- | Normalise a file -- --
-- 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" --normalise :: ShortByteString -> ShortByteString -- | Equality of two FILEPATHs. If you call -- System.Directory.canonicalizePath first this has a much -- better chance of working. Note that this doesn't follow symlinks or -- DOSNAM~1s. -- -- Similar to normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- Posix: not (equalFilePath "foo" "FOO") -- Windows: equalFilePath "foo" "FOO" -- Windows: not (equalFilePath "C:" "C:/") --equalFilePath :: ShortByteString -> ShortByteString -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob" -- Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- Windows: makeRelative "/Home" "/home/bob" == "bob" -- Windows: makeRelative "/" "//" == "//" -- Posix: makeRelative "/Home" "/home/bob" == "/home/bob" -- Posix: makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar" -- Posix: makeRelative "/fred" "bob" == "bob" -- Posix: makeRelative "/file/test" "/file/test/fred" == "fred" -- Posix: makeRelative "/file/test" "/file/test/fred/" == "fred/" -- Posix: makeRelative "some/path" "some/path/a/b/c" == "a/b/c" --makeRelative :: ShortByteString -> ShortByteString -> ShortByteString -- | Is a path relative, or is it fixed to the root? -- --
-- Windows: isRelative "path\\test" == True -- Windows: isRelative "c:\\test" == False -- Windows: isRelative "c:test" == True -- Windows: isRelative "c:\\" == False -- Windows: isRelative "c:/" == False -- Windows: isRelative "c:" == True -- Windows: isRelative "\\\\foo" == False -- Windows: isRelative "\\\\?\\foo" == False -- Windows: isRelative "\\\\?\\UNC\\foo" == False -- Windows: isRelative "/foo" == True -- Windows: isRelative "\\foo" == True -- Posix: isRelative "test/path" == True -- Posix: isRelative "/test" == False -- Posix: isRelative "/" == False ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: ShortByteString -> Bool -- | Is 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" == True --isValid :: ShortByteString -> Bool -- | Take 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" --makeValid :: ShortByteString -> ShortByteString module System.OsPath.Windows -- | Commonly used windows string as wide character bytes. data WindowsString data WindowsChar -- | Filepaths are wchar_t* data on windows as passed to syscalls. type WindowsPath = WindowsString -- | Partial unicode friendly encoding. -- -- This encodes as UTF16-LE (strictly), which is a pretty good guess. -- -- Throws an EncodingException if encoding fails. If the input -- does not contain surrogate chars, you can use -- unsafeEncodeUtf. encodeUtf :: MonadThrow m => String -> m WindowsString -- | Unsafe unicode friendly encoding. -- -- Like encodeUtf, except it crashes when the input contains -- surrogate chars. For sanitized input, this can be useful. unsafeEncodeUtf :: HasCallStack => String -> WindowsString -- | Encode a String with the specified encoding. -- -- Note: We expect a "wide char" encoding (e.g. UCS-2 or UTF-16). -- Anything that works with Word16 boundaries. Picking an -- incompatible encoding may crash filepath operations. encodeWith :: TextEncoding -> String -> Either EncodingException WindowsString encodeFS :: String -> IO WindowsPath -- | QuasiQuote a WindowsPath. This accepts Unicode characters and -- encodes as UTF-16LE. Runs isValid on the input. pstr :: QuasiQuoter -- | Pack a list of platform words to a platform string. -- -- Note that using this in conjunction with unsafeFromChar to -- convert from [Char] to platform string is probably not what -- you want, because it will truncate unicode code points. pack :: [WindowsChar] -> WindowsString -- | Partial unicode friendly decoding. -- -- This decodes as UTF16-LE (strictly), which is a pretty good. -- -- Throws a EncodingException if decoding fails. decodeUtf :: MonadThrow m => WindowsString -> m String -- | Decode a WindowsString with the specified encoding. -- -- The String is forced into memory to catch all exceptions. decodeWith :: TextEncoding -> WindowsString -> Either EncodingException String decodeFS :: WindowsPath -> IO String -- | Unpack a platform string to a list of platform words. unpack :: WindowsString -> [WindowsChar] -- | Truncates to 2 octets. unsafeFromChar :: Char -> WindowsChar -- | Converts back to a unicode codepoint (total). toChar :: WindowsChar -> Char -- | The character that separates directories. In the case where more than -- one character is possible, pathSeparator is the 'ideal' one. -- --
-- pathSeparator == '\\'S --pathSeparator :: WindowsChar -- | The list of all possible separators. -- --
-- pathSeparators == ['\\', '/'] -- pathSeparator `elem` pathSeparators --pathSeparators :: [WindowsChar] -- | Rather than using (== pathSeparator), use this. Test -- if something is a path separator. -- --
-- isPathSeparator a == (a `elem` pathSeparators) --isPathSeparator :: WindowsChar -> Bool -- | The character that is used to separate the entries in the $PATH -- environment variable. -- --
-- searchPathSeparator == ';' --searchPathSeparator :: WindowsChar -- | Is the character a file separator? -- --
-- isSearchPathSeparator a == (a == searchPathSeparator) --isSearchPathSeparator :: WindowsChar -> Bool -- | File extension character -- --
-- extSeparator == '.' --extSeparator :: WindowsChar -- | Is the character an extension character? -- --
-- isExtSeparator a == (a == extSeparator) --isExtSeparator :: WindowsChar -> Bool -- | Take a string, split it on the searchPathSeparator character. -- -- Blank items are ignored and path elements are stripped of quotes. -- --
-- splitSearchPath "File1;File2;File3" == ["File1","File2","File3"] -- splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"] -- splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"] --splitSearchPath :: WindowsString -> [WindowsPath] -- | Split on the extension. addExtension is the inverse. -- --
-- splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- uncurry (<>) (splitExtension x) == x
-- Valid x => uncurry addExtension (splitExtension x) == x
-- splitExtension "file.txt" == ("file",".txt")
-- splitExtension "file" == ("file","")
-- splitExtension "file/file.txt" == ("file/file",".txt")
-- splitExtension "file.txt/boris" == ("file.txt/boris","")
-- splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- splitExtension "file/path.txt/" == ("file/path.txt/","")
--
splitExtension :: WindowsPath -> (WindowsPath, WindowsString)
-- | Get the extension of a file, returns "" for no extension,
-- .ext otherwise.
--
-- -- takeExtension "/directory/path.ext" == ".ext" -- takeExtension x == snd (splitExtension x) -- Valid x => takeExtension (addExtension x "ext") == ".ext" -- Valid x => takeExtension (replaceExtension x "ext") == ".ext" --takeExtension :: WindowsPath -> WindowsString -- | Set the extension of a file, overwriting one if already present, -- equivalent to -<.>. -- --
-- replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext" -- replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext" -- replaceExtension "file.txt" ".bob" == "file.bob" -- replaceExtension "file.txt" "bob" == "file.bob" -- replaceExtension "file" ".bob" == "file.bob" -- replaceExtension "file.txt" "" == "file" -- replaceExtension "file.fred.bob" "txt" == "file.fred.txt" -- replaceExtension x y == addExtension (dropExtension x) y --replaceExtension :: WindowsPath -> WindowsString -> WindowsPath -- | Remove the current extension and add another, equivalent to -- replaceExtension. -- --
-- "/directory/path.txt" -<.> "ext" == "/directory/path.ext" -- "/directory/path.txt" -<.> ".ext" == "/directory/path.ext" -- "foo.o" -<.> "c" == "foo.c" --(-<.>) :: WindowsPath -> WindowsString -> WindowsPath -- | Remove last extension, and the "." preceding it. -- --
-- dropExtension "/directory/path.ext" == "/directory/path" -- dropExtension x == fst (splitExtension x) --dropExtension :: WindowsPath -> WindowsPath -- | 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" ---- -- 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" --addExtension :: WindowsPath -> WindowsString -> WindowsPath -- | Does the given filename have an extension? -- --
-- hasExtension "/directory/path.ext" == True -- hasExtension "/directory/path" == False -- null (takeExtension x) == not (hasExtension x) --hasExtension :: WindowsPath -> Bool -- | Add an extension, even if there is already one there, equivalent to -- addExtension. -- --
-- "/directory/path" <.> "ext" == "/directory/path.ext" -- "/directory/path" <.> ".ext" == "/directory/path.ext" --(<.>) :: WindowsPath -> WindowsString -> WindowsPath -- | Split on all extensions. -- --
-- splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- uncurry (<>) (splitExtensions x) == x
-- Valid x => uncurry addExtension (splitExtensions x) == x
-- splitExtensions "file.tar.gz" == ("file",".tar.gz")
--
splitExtensions :: WindowsPath -> (WindowsPath, WindowsString)
-- | Drop all extensions.
--
-- -- dropExtensions "/directory/path.ext" == "/directory/path" -- dropExtensions "file.tar.gz" == "file" -- not $ hasExtension $ dropExtensions x -- not $ any isExtSeparator $ takeFileName $ dropExtensions x --dropExtensions :: WindowsPath -> WindowsPath -- | Get all extensions. -- --
-- takeExtensions "/directory/path.ext" == ".ext" -- takeExtensions "file.tar.gz" == ".tar.gz" --takeExtensions :: WindowsPath -> WindowsString -- | Replace all extensions of a file with a new extension. Note that -- replaceExtension and addExtension both work for adding -- multiple extensions, so only required when you need to drop all -- extensions first. -- --
-- replaceExtensions "file.fred.bob" "txt" == "file.txt" -- replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz" --replaceExtensions :: WindowsPath -> WindowsString -> WindowsPath -- | Does the given filename have the specified extension? -- --
-- "png" `isExtensionOf` "/directory/file.png" == True -- ".png" `isExtensionOf` "/directory/file.png" == True -- ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True -- "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False -- "png" `isExtensionOf` "/directory/file.png.jpg" == False -- "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False --isExtensionOf :: WindowsString -> WindowsPath -> Bool -- | Drop the given extension from a filepath, and the "." -- preceding it. Returns Nothing if the filepath does not have the -- given extension, or Just and the part before the extension if -- it does. -- -- This function can be more predictable than dropExtensions, -- especially if the filename might itself contain . characters. -- --
-- stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x" -- stripExtension "hi.o" "foo.x.hs.o" == Nothing -- dropExtension x == fromJust (stripExtension (takeExtension x) x) -- dropExtensions x == fromJust (stripExtension (takeExtensions x) x) -- stripExtension ".c.d" "a.b.c.d" == Just "a.b" -- stripExtension ".c.d" "a.b..c.d" == Just "a.b." -- stripExtension "baz" "foo.bar" == Nothing -- stripExtension "bar" "foobar" == Nothing -- stripExtension "" x == Just x --stripExtension :: WindowsString -> WindowsPath -> Maybe WindowsPath -- | 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:","")
--
splitFileName :: WindowsPath -> (WindowsPath, WindowsPath)
-- | Get the file name.
--
-- -- takeFileName "/directory/file.ext" == "file.ext" -- takeFileName "test/" == "" -- takeFileName x `isSuffixOf` x -- takeFileName x == snd (splitFileName x) -- Valid x => takeFileName (replaceFileName x "fred") == "fred" -- Valid x => takeFileName (x </> "fred") == "fred" -- Valid x => isRelative (takeFileName x) --takeFileName :: WindowsPath -> WindowsPath -- | Set the filename. -- --
-- replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext" -- Valid x => replaceFileName x (takeFileName x) == x --replaceFileName :: WindowsPath -> WindowsString -> WindowsPath -- | Drop the filename. Unlike takeDirectory, this function will -- leave a trailing path separator on the directory. -- --
-- dropFileName "/directory/file.ext" == "/directory/" -- dropFileName x == fst (splitFileName x) --dropFileName :: WindowsPath -> WindowsPath -- | Get the base name, without an extension or path. -- --
-- takeBaseName "/directory/file.ext" == "file" -- takeBaseName "file/test.txt" == "test" -- takeBaseName "dave.ext" == "dave" -- takeBaseName "" == "" -- takeBaseName "test" == "test" -- takeBaseName (addTrailingPathSeparator x) == "" -- takeBaseName "file/file.tar.gz" == "file.tar" --takeBaseName :: WindowsPath -> WindowsPath -- | Set the base name. -- --
-- replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext" -- replaceBaseName "file/test.txt" "bob" == "file/bob.txt" -- replaceBaseName "fred" "bill" == "bill" -- replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar" -- Valid x => replaceBaseName x (takeBaseName x) == x --replaceBaseName :: WindowsPath -> WindowsString -> WindowsPath -- | 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:\\" --takeDirectory :: WindowsPath -> WindowsPath -- | Set the directory, keeping the filename the same. -- --
-- replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext" -- Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x --replaceDirectory :: WindowsPath -> WindowsPath -> WindowsPath -- | An alias for </>. combine :: WindowsPath -> WindowsPath -> WindowsPath -- | Combine two paths with a path separator. If the second path starts -- with a path separator or a drive letter, then it returns the second. -- The intention is that readFile (dir </> file) -- will access the same file as setCurrentDirectory dir; readFile -- file. -- --
-- "/directory" </> "file.ext" == "/directory\\file.ext" -- "directory" </> "/file.ext" == "/file.ext" -- Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x ---- -- Combined: -- --
-- "C:\\foo" </> "bar" == "C:\\foo\\bar" -- "home" </> "bob" == "home\\bob" ---- -- Not combined: -- --
-- "home" </> "C:\\bob" == "C:\\bob" ---- -- Not combined (tricky): -- -- If a filepath starts with a single slash, it is relative to the root -- of the current drive. In [1], this is (confusingly) referred to as an -- absolute path. The current behavior of </> is to never -- combine these forms. -- --
-- "home" </> "/bob" == "/bob" -- "home" </> "\\bob" == "\\bob" -- "C:\\home" </> "\\bob" == "\\bob" ---- -- From [1]: "If a file name begins with only a disk designator but not -- the backslash after the colon, it is interpreted as a relative path to -- the current directory on the drive with the specified letter." The -- current behavior of </> is to never combine these forms. -- --
-- "D:\\foo" </> "C:bar" == "C:bar" -- "C:\\foo" </> "C:bar" == "C:bar" --(>) :: WindowsPath -> WindowsPath -> WindowsPath -- | 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"] --splitPath :: WindowsPath -> [WindowsPath] -- | Join path elements back together. -- --
-- joinPath z == foldr (</>) "" z -- joinPath ["/","directory/","file.ext"] == "/directory/file.ext" -- Valid x => joinPath (splitPath x) == x -- joinPath [] == "" --joinPath :: [WindowsPath] -> WindowsPath -- | Just as splitPath, but don't add the trailing slashes to each -- element. -- --
-- splitDirectories "/directory/file.ext" == ["/","directory","file.ext"] -- splitDirectories "test/file" == ["test","file"] -- splitDirectories "/test/file" == ["/","test","file"] -- splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"] -- Valid x => joinPath (splitDirectories x) `equalFilePath` x -- splitDirectories "" == [] -- splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"] -- splitDirectories "/test///file" == ["/","test","file"] --splitDirectories :: WindowsPath -> [WindowsPath] -- | 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")
--
splitDrive :: WindowsPath -> (WindowsPath, WindowsPath)
-- | 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" --joinDrive :: WindowsPath -> WindowsPath -> WindowsPath -- | Get the drive from a filepath. -- --
-- takeDrive x == fst (splitDrive x) --takeDrive :: WindowsPath -> WindowsPath -- | Does a path have a drive. -- --
-- not (hasDrive x) == null (takeDrive x) -- hasDrive "C:\\foo" == True -- hasDrive "C:foo" == True -- hasDrive "foo" == False -- hasDrive "" == False --hasDrive :: WindowsPath -> Bool -- | Delete the drive, if it exists. -- --
-- dropDrive x == snd (splitDrive x) --dropDrive :: WindowsPath -> WindowsPath -- | Is an element a drive -- --
-- isDrive "C:\\" == True -- isDrive "C:\\foo" == False -- isDrive "" == False --isDrive :: WindowsPath -> Bool -- | Is an item either a directory or the last character a path separator? -- --
-- hasTrailingPathSeparator "test" == False -- hasTrailingPathSeparator "test/" == True --hasTrailingPathSeparator :: WindowsPath -> Bool -- | Add a trailing file path separator if one is not already present. -- --
-- hasTrailingPathSeparator (addTrailingPathSeparator x) -- hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x --addTrailingPathSeparator :: WindowsPath -> WindowsPath -- | Remove any trailing path separators -- --
-- dropTrailingPathSeparator "file/test/" == "file/test" -- dropTrailingPathSeparator "/" == "/" -- dropTrailingPathSeparator "\\" == "\\" --dropTrailingPathSeparator :: WindowsPath -> WindowsPath -- | Normalise a file -- --
-- 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 "." == "." --normalise :: WindowsPath -> WindowsPath -- | Equality of two filepaths. If you call -- System.Directory.canonicalizePath first this has a much -- better chance of working. Note that this doesn't follow symlinks or -- DOSNAM~1s. -- -- Similar to normalise, this does not expand "..", -- because of symlinks. -- --
-- x == y ==> equalFilePath x y -- normalise x == normalise y ==> equalFilePath x y -- equalFilePath "foo" "foo/" -- not (equalFilePath "/a/../c" "/c") -- not (equalFilePath "foo" "/foo") -- equalFilePath "foo" "FOO" -- not (equalFilePath "C:" "C:/") --equalFilePath :: WindowsPath -> WindowsPath -> Bool -- | Contract a filename, based on a relative path. Note that the resulting -- path will never introduce .. paths, as the presence of -- symlinks means ../b may not reach a/b if it starts -- from a/c. For a worked example see this blog post. -- -- The corresponding makeAbsolute function can be found in -- System.Directory. -- --
-- makeRelative "/directory" "/directory/file.ext" == "file.ext" -- Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x -- makeRelative x x == "." -- Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x -- makeRelative "C:\\Home" "c:\\home\\bob" == "bob" -- makeRelative "C:\\Home" "c:/home/bob" == "bob" -- makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob" -- makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob" -- makeRelative "/Home" "/home/bob" == "bob" -- makeRelative "/" "//" == "//" --makeRelative :: WindowsPath -> WindowsPath -> WindowsPath -- | Is a path relative, or is it fixed to the root? -- --
-- isRelative "path\\test" == True -- isRelative "c:\\test" == False -- isRelative "c:test" == True -- isRelative "c:\\" == False -- isRelative "c:/" == False -- isRelative "c:" == True -- isRelative "\\\\foo" == False -- isRelative "\\\\?\\foo" == False -- isRelative "\\\\?\\UNC\\foo" == False -- isRelative "/foo" == True -- isRelative "\\foo" == True ---- -- According to [1]: -- --
-- not . isRelative ---- --
-- isAbsolute x == not (isRelative x) --isAbsolute :: WindowsPath -> Bool -- | Is a filepath valid, i.e. could you create a file like it? This -- function checks for invalid names, and invalid characters, but does -- not check if length limits are exceeded, as these are typically -- filesystem dependent. -- --
-- isValid "" == False -- isValid "\0" == False -- isValid "c:\\test" == True -- isValid "c:\\test:of_test" == False -- isValid "test*" == False -- isValid "c:\\test\\nul" == False -- isValid "c:\\test\\prn.txt" == False -- isValid "c:\\nul\\file" == False -- isValid "\\\\" == False -- isValid "\\\\\\foo" == False -- isValid "\\\\?\\D:file" == False -- isValid "foo\tbar" == False -- isValid "nul .txt" == False -- isValid " nul.txt" == True --isValid :: WindowsPath -> Bool -- | Take a filepath and make it valid; does not change already valid -- filepaths. -- --
-- isValid (makeValid x) -- isValid x ==> makeValid x == x -- makeValid "" == "_" -- makeValid "file\0name" == "file_name" -- makeValid "c:\\already\\/valid" == "c:\\already\\/valid" -- makeValid "c:\\test:of_test" == "c:\\test_of_test" -- makeValid "test*" == "test_" -- makeValid "c:\\test\\nul" == "c:\\test\\nul_" -- makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt" -- makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt" -- makeValid "c:\\nul\\file" == "c:\\nul_\\file" -- makeValid "\\\\\\foo" == "\\\\drive" -- makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file" -- makeValid "nul .txt" == "nul _.txt" --makeValid :: WindowsPath -> WindowsPath