-- 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 -- | File and directory names are values of type String, whose -- precise meaning is operating system dependent. Files can be opened, -- yielding a handle which can then be used to operate on the contents of -- that file. 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 "file.tar.gz" == ("file",".tar.gz")
--
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")
--
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) --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 "\\\\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 -- | File and directory names are values of type String, whose -- precise meaning is operating system dependent. Files can be opened, -- yielding a handle which can then be used to operate on the contents of -- that file. 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 "file.tar.gz" == ("file",".tar.gz")
--
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")
--
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) --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 "\\\\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 -- | File and directory names are values of type String, whose -- precise meaning is operating system dependent. Files can be opened, -- yielding a handle which can then be used to operate on the contents of -- that file. 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 "file.tar.gz" == ("file",".tar.gz")
--
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")
--
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) --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 "\\\\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 -- | Internal low-level utilities mostly for Word16, such as -- byte-array operations and other stuff not meant to be exported from -- Word16 module. module System.OsPath.Data.ByteString.Short.Internal _nul :: Word16 isSpace :: Word16 -> Bool -- | Total conversion to char. word16ToChar :: Word16 -> Char create :: Int -> (forall s. MBA s -> ST s ()) -> ShortByteString asBA :: ShortByteString -> BA data BA BA# :: ByteArray# -> BA data MBA s MBA# :: MutableByteArray# s -> MBA s newPinnedByteArray :: Int -> ST s (MBA s) newByteArray :: Int -> ST s (MBA s) copyByteArray :: BA -> Int -> MBA s -> Int -> Int -> ST s () unsafeFreezeByteArray :: MBA s -> ST s BA copyAddrToByteArray :: Ptr a -> MBA RealWorld -> Int -> Int -> ST RealWorld () -- | O(n). Construct a new ShortByteString from a -- CWString. The resulting ShortByteString is an -- immutable copy of the original CWString, and is managed on -- the Haskell heap. The original CWString must be null -- terminated. packCWString :: Ptr Word16 -> IO ShortByteString -- | O(n). Construct a new ShortByteString from a -- CWStringLen. The resulting ShortByteString is an -- immutable copy of the original CWStringLen. The -- ShortByteString is a normal Haskell value and will be managed -- on the Haskell heap. packCWStringLen :: (Ptr Word16, Int) -> IO ShortByteString -- | O(n) construction. Use a ShortByteString with a -- function requiring a null-terminated CWString. The -- CWString is a copy and will be freed automatically; it must -- not be stored or used after the subcomputation finishes. useAsCWString :: ShortByteString -> (Ptr Word16 -> IO a) -> IO a -- | O(n) construction. Use a ShortByteString with a -- function requiring a CWStringLen. As for -- useAsCWString this function makes a copy of the original -- ShortByteString. It must not be stored or used after the -- subcomputation finishes. useAsCWStringLen :: ShortByteString -> ((Ptr Word16, Int) -> IO a) -> IO a -- | O(n) construction. Use a ShortByteString with a -- function requiring a CWStringLen. As for -- useAsCWString this function makes a copy of the original -- ShortByteString. It must not be stored or used after the -- subcomputation finishes. newCWString :: ShortByteString -> IO (Ptr Word16) moduleErrorIO :: String -> String -> IO a moduleErrorMsg :: String -> String -> String packWord16 :: [Word16] -> ShortByteString packLenWord16 :: Int -> [Word16] -> ShortByteString unpackWord16 :: ShortByteString -> [Word16] packWord16Rev :: [Word16] -> ShortByteString packLenWord16Rev :: Int -> [Word16] -> ShortByteString -- | This isn't strictly Word16 array write. Instead it's two consecutive -- Word8 array writes to avoid endianness issues due to primops doing -- automatic alignment based on host platform. We want to always write LE -- to the byte array. writeWord16Array :: MBA s -> Int -> Word16 -> ST s () indexWord8Array :: BA -> Int -> Word8 -- | This isn't strictly Word16 array read. Instead it's two Word8 array -- reads to avoid endianness issues due to primops doing automatic -- alignment based on host platform. We expect the byte array to be LE -- always. indexWord16Array :: BA -> Int -> Word16 encodeWord16LE# :: Word16# -> (# Word8#, Word8# #) decodeWord16LE# :: (# Word8#, Word8# #) -> Word16# setByteArray :: MBA s -> Int -> Int -> Int -> ST s () copyMutableByteArray :: MBA s -> Int -> MBA s -> Int -> Int -> ST s () -- | Given the maximum size needed and a function to make the contents of a -- ShortByteString, createAndTrim makes the ShortByteString. The -- generating function is required to return the actual final size (<= -- the maximum size) and the result value. The resulting byte array is -- realloced to this size. createAndTrim :: Int -> (forall s. MBA s -> ST s (Int, a)) -> (ShortByteString, a) createAndTrim' :: Int -> (forall s. MBA s -> ST s Int) -> ShortByteString createAndTrim'' :: Int -> (forall s. MBA s -> MBA s -> ST s (Int, Int)) -> (ShortByteString, ShortByteString) findIndexOrLength :: (Word16 -> Bool) -> ShortByteString -> Int -- | Returns the length of the substring matching, not the index. If no -- match, returns 0. findFromEndUntil :: (Word16 -> Bool) -> ShortByteString -> Int assertEven :: ShortByteString -> ShortByteString errorEmptySBS :: HasCallStack => String -> a moduleError :: HasCallStack => String -> String -> a -- | A compact representation suitable for storing short byte strings in -- memory. -- -- In typical use cases it can be imported alongside -- Data.ByteString, e.g. -- --
-- import qualified Data.ByteString as B -- import qualified Data.ByteString.Short as B -- (ShortByteString, toShort, fromShort) ---- -- Other ShortByteString operations clash with -- Data.ByteString or Prelude functions however, so they -- should be imported qualified with a different alias e.g. -- --
-- import qualified Data.ByteString.Short as B.Short --module System.OsPath.Data.ByteString.Short -- | A compact representation of a Word8 vector. -- -- It has a lower memory overhead than a ByteString and does not -- contribute to heap fragmentation. It can be converted to or from a -- ByteString (at the cost of copying the string data). It -- supports very few other operations. -- -- It is suitable for use as an internal representation for code that -- needs to keep many short strings in memory, but it should not -- be used as an interchange type. That is, it should not generally be -- used in public APIs. The ByteString type is usually more -- suitable for use in interfaces; it is more flexible and it supports a -- wide range of operations. data ShortByteString SBS :: ByteArray# -> ShortByteString -- | O(1). The empty ShortByteString. empty :: ShortByteString -- | O(1) Convert a Word8 into a ShortByteString singleton :: Word8 -> ShortByteString -- | O(n). Convert a list into a ShortByteString pack :: [Word8] -> ShortByteString -- | O(n). Convert a ShortByteString into a list. unpack :: ShortByteString -> [Word8] -- | O(n). Convert a ShortByteString into a -- ByteString. fromShort :: ShortByteString -> ByteString -- | O(n). Convert a ByteString into a -- ShortByteString. -- -- This makes a copy, so does not retain the input string. toShort :: ByteString -> ShortByteString -- | O(n) Append a byte to the end of a ShortByteString -- -- Note: copies the entire byte array snoc :: ShortByteString -> Word8 -> ShortByteString infixl 5 `snoc` -- | O(n) cons is analogous to (:) for lists. -- -- Note: copies the entire byte array cons :: Word8 -> ShortByteString -> ShortByteString infixr 5 `cons` append :: ShortByteString -> ShortByteString -> ShortByteString -- | O(1) Extract the last element of a ShortByteString, which must -- be finite and non-empty. An exception will be thrown in the case of an -- empty ShortByteString. -- -- This is a partial function, consider using unsnoc instead. last :: HasCallStack => ShortByteString -> Word8 -- | O(n) Extract the elements after the head of a ShortByteString, -- which must be non-empty. An exception will be thrown in the case of an -- empty ShortByteString. -- -- This is a partial function, consider using uncons instead. -- -- Note: copies the entire byte array tail :: HasCallStack => ShortByteString -> ShortByteString -- | O(n) Extract the head and tail of a ByteString, returning -- Nothing if it is empty. uncons :: ShortByteString -> Maybe (Word8, ShortByteString) uncons2 :: ShortByteString -> Maybe (Word8, Word8, ShortByteString) -- | O(1) Extract the first element of a ShortByteString, which must -- be non-empty. An exception will be thrown in the case of an empty -- ShortByteString. -- -- This is a partial function, consider using uncons instead. head :: HasCallStack => ShortByteString -> Word8 -- | O(n) Return all the elements of a ShortByteString except -- the last one. An exception will be thrown in the case of an empty -- ShortByteString. -- -- This is a partial function, consider using unsnoc instead. -- -- Note: copies the entire byte array init :: HasCallStack => ShortByteString -> ShortByteString -- | O(n) Extract the init and last of a ByteString, -- returning Nothing if it is empty. unsnoc :: ShortByteString -> Maybe (ShortByteString, Word8) -- | O(1) Test whether a ShortByteString is empty. null :: ShortByteString -> Bool -- | O(1) The length of a ShortByteString. length :: ShortByteString -> Int -- | O(n) map f xs is the ShortByteString obtained -- by applying f to each element of xs. map :: (Word8 -> Word8) -> ShortByteString -> ShortByteString -- | O(n) reverse xs efficiently returns the -- elements of xs in reverse order. reverse :: ShortByteString -> ShortByteString -- | O(n) The intercalate function takes a -- ShortByteString and a list of ShortByteStrings and -- concatenates the list after interspersing the first argument between -- each element of the list. intercalate :: ShortByteString -> [ShortByteString] -> ShortByteString -- | foldl, applied to a binary operator, a starting value -- (typically the left-identity of the operator), and a ShortByteString, -- reduces the ShortByteString using the binary operator, from left to -- right. foldl :: (a -> Word8 -> a) -> a -> ShortByteString -> a -- | foldl' is like foldl, but strict in the accumulator. foldl' :: (a -> Word8 -> a) -> a -> ShortByteString -> a -- | foldl1 is a variant of foldl that has no starting value -- argument, and thus must be applied to non-empty -- ShortByteStrings. An exception will be thrown in the case of an -- empty ShortByteString. foldl1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8 -- | foldl1' is like foldl1, but strict in the accumulator. -- An exception will be thrown in the case of an empty ShortByteString. foldl1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8 -- | foldr, applied to a binary operator, a starting value -- (typically the right-identity of the operator), and a ShortByteString, -- reduces the ShortByteString using the binary operator, from right to -- left. foldr :: (Word8 -> a -> a) -> a -> ShortByteString -> a -- | foldr' is like foldr, but strict in the accumulator. foldr' :: (Word8 -> a -> a) -> a -> ShortByteString -> a -- | foldr1 is a variant of foldr that has no starting value -- argument, and thus must be applied to non-empty -- ShortByteStrings An exception will be thrown in the case of an -- empty ShortByteString. foldr1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8 -- | foldr1' is a variant of foldr1, but is strict in the -- accumulator. foldr1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ShortByteString -> Word8 -- | O(n) Applied to a predicate and a ShortByteString, -- all determines if all elements of the ShortByteString -- satisfy the predicate. all :: (Word8 -> Bool) -> ShortByteString -> Bool -- | O(n) Applied to a predicate and a ByteString, any -- determines if any element of the ByteString satisfies the -- predicate. any :: (Word8 -> Bool) -> ShortByteString -> Bool concat :: [ShortByteString] -> ShortByteString -- | O(n) replicate n x is a ByteString of length -- n with x the value of every element. The following -- holds: -- --
-- replicate w c = unfoldr w (\u -> Just (u,u)) c --replicate :: Int -> Word8 -> ShortByteString -- | O(n), where n is the length of the result. The -- unfoldr function is analogous to the List 'unfoldr'. -- unfoldr builds a ShortByteString from a seed value. The -- function takes the element and returns Nothing if it is done -- producing the ShortByteString or returns Just (a,b), -- in which case, a is the next byte in the string, and -- b is the seed value for further production. -- -- This function is not efficient/safe. It will build a list of -- [Word8] and run the generator until it returns -- Nothing, otherwise recurse infinitely, then finally create a -- ShortByteString. -- -- If you know the maximum length, consider using unfoldrN. -- -- Examples: -- --
-- unfoldr (\x -> if x <= 5 then Just (x, x + 1) else Nothing) 0 -- == pack [0, 1, 2, 3, 4, 5] --unfoldr :: (a -> Maybe (Word8, a)) -> a -> ShortByteString -- | O(n) Like unfoldr, unfoldrN builds a -- ShortByteString from a seed value. However, the length of the result -- is limited by the first argument to unfoldrN. This function is -- more efficient than unfoldr when the maximum length of the -- result is known. -- -- The following equation relates unfoldrN and unfoldr: -- --
-- fst (unfoldrN n f s) == take n (unfoldr f s) --unfoldrN :: Int -> (a -> Maybe (Word8, a)) -> a -> (ShortByteString, Maybe a) -- | O(n) take n, applied to a ShortByteString -- xs, returns the prefix of xs of length n, -- or xs itself if n > length xs. -- -- Note: copies the entire byte array take :: Int -> ShortByteString -> ShortByteString -- | O(n) takeEnd n xs is equivalent to -- drop (length xs - n) xs. Takes n -- elements from end of bytestring. -- --
-- >>> takeEnd 3 "abcdefg" -- "efg" -- -- >>> takeEnd 0 "abcdefg" -- "" -- -- >>> takeEnd 4 "abc" -- "abc" --takeEnd :: Int -> ShortByteString -> ShortByteString -- | Returns the longest (possibly empty) suffix of elements satisfying the -- predicate. -- -- takeWhileEnd p is equivalent to reverse . -- takeWhile p . reverse. takeWhileEnd :: (Word8 -> Bool) -> ShortByteString -> ShortByteString -- | Similar to takeWhile, returns the longest (possibly empty) -- prefix of elements satisfying the predicate. takeWhile :: (Word8 -> Bool) -> ShortByteString -> ShortByteString -- | O(n) drop n xs returns the suffix of -- xs after the first n elements, or empty if n > -- length xs. -- -- Note: copies the entire byte array drop :: Int -> ShortByteString -> ShortByteString -- | O(n) dropEnd n xs is equivalent to -- take (length xs - n) xs. Drops n -- elements from end of bytestring. -- --
-- >>> dropEnd 3 "abcdefg" -- "abcd" -- -- >>> dropEnd 0 "abcdefg" -- "abcdefg" -- -- >>> dropEnd 4 "abc" -- "" --dropEnd :: Int -> ShortByteString -> ShortByteString -- | Similar to dropWhile, drops the longest (possibly empty) prefix -- of elements satisfying the predicate and returns the remainder. -- -- Note: copies the entire byte array dropWhile :: (Word8 -> Bool) -> ShortByteString -> ShortByteString -- | Similar to dropWhileEnd, drops the longest (possibly empty) -- suffix of elements satisfying the predicate and returns the remainder. -- -- dropWhileEnd p is equivalent to reverse . -- dropWhile p . reverse. dropWhileEnd :: (Word8 -> Bool) -> ShortByteString -> ShortByteString -- | Returns the longest (possibly empty) suffix of elements which do -- not satisfy the predicate and the remainder of the string. -- -- breakEnd p is equivalent to spanEnd (not . -- p) and to (takeWhileEnd (not . p) &&& -- dropWhileEnd (not . p)). breakEnd :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Similar to break, returns the longest (possibly empty) prefix -- of elements which do not satisfy the predicate and the -- remainder of the string. -- -- break p is equivalent to span (not . -- p) and to (takeWhile (not . p) &&& -- dropWhile (not . p)). break :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Similar to span, returns the longest (possibly empty) prefix of -- elements satisfying the predicate and the remainder of the string. -- -- span p is equivalent to break (not . -- p) and to (takeWhile p &&& -- dropWhile p). span :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Returns the longest (possibly empty) suffix of elements satisfying the -- predicate and the remainder of the string. -- -- spanEnd p is equivalent to breakEnd (not . -- p) and to (takeWhileEnd p &&& -- dropWhileEnd p). -- -- We have -- --
-- spanEnd (not . isSpace) "x y z" == ("x y ", "z")
--
--
-- and
--
-- -- spanEnd (not . isSpace) sbs -- == -- let (x, y) = span (not . isSpace) (reverse sbs) in (reverse y, reverse x) --spanEnd :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) splitAt n sbs is equivalent to -- (take n sbs, drop n sbs). -- -- Note: copies the substrings splitAt :: Int -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) Break a ShortByteString into pieces separated by -- the byte argument, consuming the delimiter. I.e. -- --
-- split 10 "a\nb\nd\ne" == ["a","b","d","e"] -- fromEnum '\n' == 10 -- split 97 "aXaXaXa" == ["","X","X","X",""] -- fromEnum 'a' == 97 -- split 120 "x" == ["",""] -- fromEnum 'x' == 120 -- split undefined "" == [] -- and not [""] ---- -- and -- --
-- intercalate [c] . split c == id -- split == splitWith . (==) ---- -- Note: copies the substrings split :: Word8 -> ShortByteString -> [ShortByteString] -- | O(n) Splits a ShortByteString into components delimited -- by separators, where the predicate returns True for a separator -- element. The resulting components do not contain the separators. Two -- adjacent separators result in an empty component in the output. eg. -- --
-- splitWith (==97) "aabbaca" == ["","","bb","c",""] -- fromEnum 'a' == 97 -- splitWith undefined "" == [] -- and not [""] --splitWith :: (Word8 -> Bool) -> ShortByteString -> [ShortByteString] -- | O(n) The stripSuffix function takes two ShortByteStrings -- and returns Just the remainder of the second iff the first is -- its suffix, and otherwise Nothing. stripSuffix :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | O(n) The stripPrefix function takes two ShortByteStrings -- and returns Just the remainder of the second iff the first is -- its prefix, and otherwise Nothing. stripPrefix :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | Check whether one string is a substring of another. isInfixOf :: ShortByteString -> ShortByteString -> Bool -- | O(n) The isPrefixOf function takes two ShortByteStrings -- and returns True isPrefixOf :: ShortByteString -> ShortByteString -> Bool -- | O(n) The isSuffixOf function takes two ShortByteStrings -- and returns True iff the first is a suffix of the second. -- -- The following holds: -- --
-- isSuffixOf x y == reverse x `isPrefixOf` reverse y --isSuffixOf :: ShortByteString -> ShortByteString -> Bool -- | Break a string on a substring, returning a pair of the part of the -- string prior to the match, and the rest of the string. -- -- The following relationships hold: -- --
-- break (== c) l == breakSubstring (singleton c) l ---- -- For example, to tokenise a string, dropping delimiters: -- --
-- tokenise x y = h : if null t then [] else tokenise x (drop (length x) t) -- where (h,t) = breakSubstring x y ---- -- To skip to the first occurrence of a string: -- --
-- snd (breakSubstring x y) ---- -- To take the parts of a string before a delimiter: -- --
-- fst (breakSubstring x y) ---- -- Note that calling `breakSubstring x` does some preprocessing work, so -- you should avoid unnecessarily duplicating breakSubstring calls with -- the same pattern. breakSubstring :: ShortByteString -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) elem is the ShortByteString membership -- predicate. elem :: Word8 -> ShortByteString -> Bool -- | O(n) The find function takes a predicate and a -- ByteString, and returns the first element in matching the predicate, -- or Nothing if there is no such element. -- --
-- find f p = case findIndex f p of Just n -> Just (p ! n) ; _ -> Nothing --find :: (Word8 -> Bool) -> ShortByteString -> Maybe Word8 -- | O(n) filter, applied to a predicate and a ByteString, -- returns a ByteString containing those characters that satisfy the -- predicate. filter :: (Word8 -> Bool) -> ShortByteString -> ShortByteString -- | O(n) The partition function takes a predicate a -- ByteString and returns the pair of ByteStrings with elements which do -- and do not satisfy the predicate, respectively; i.e., -- --
-- partition p bs == (filter p sbs, filter (not . p) sbs) --partition :: (Word8 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(1) ShortByteString index (subscript) operator, -- starting from 0. index :: HasCallStack => ShortByteString -> Int -> Word8 -- | O(1) ShortByteString index, starting from 0, that -- returns Just if: -- --
-- 0 <= n < length bs --indexMaybe :: ShortByteString -> Int -> Maybe Word8 -- | O(1) ShortByteString index, starting from 0, that -- returns Just if: -- --
-- 0 <= n < length bs --(!?) :: ShortByteString -> Int -> Maybe Word8 -- | O(n) The elemIndex function returns the index of the -- first element in the given ShortByteString which is equal to -- the query element, or Nothing if there is no such element. elemIndex :: Word8 -> ShortByteString -> Maybe Int -- | O(n) The elemIndices function extends elemIndex, -- by returning the indices of all elements equal to the query element, -- in ascending order. elemIndices :: Word8 -> ShortByteString -> [Int] -- | count returns the number of times its argument appears in the -- ShortByteString count :: Word8 -> ShortByteString -> Int -- | O(n) The findIndex function takes a predicate and a -- ShortByteString and returns the index of the first element in -- the ByteString satisfying the predicate. findIndex :: (Word8 -> Bool) -> ShortByteString -> Maybe Int -- | O(n) The findIndices function extends findIndex, -- by returning the indices of all elements satisfying the predicate, in -- ascending order. findIndices :: (Word8 -> Bool) -> ShortByteString -> [Int] -- | O(n). Construct a new ShortByteString from a -- CString. The resulting ShortByteString is an -- immutable copy of the original CString, and is managed on the -- Haskell heap. The original CString must be null terminated. packCString :: CString -> IO ShortByteString -- | O(n). Construct a new ShortByteString from a -- CStringLen. The resulting ShortByteString is an -- immutable copy of the original CStringLen. The -- ShortByteString is a normal Haskell value and will be managed -- on the Haskell heap. packCStringLen :: CStringLen -> IO ShortByteString -- | O(n) construction. Use a ShortByteString with a -- function requiring a null-terminated CString. The -- CString is a copy and will be freed automatically; it must -- not be stored or used after the subcomputation finishes. useAsCString :: ShortByteString -> (CString -> IO a) -> IO a -- | O(n) construction. Use a ShortByteString with a -- function requiring a CStringLen. As for useAsCString -- this function makes a copy of the original ShortByteString. -- It must not be stored or used after the subcomputation finishes. useAsCStringLen :: ShortByteString -> (CStringLen -> IO a) -> IO a -- | ShortByteStrings encoded as UTF16-LE, suitable for windows FFI calls. -- -- Word16s are *always* in BE encoding (both input and output), so e.g. -- pack takes a list of BE encoded [Word16] and produces -- a UTF16-LE encoded ShortByteString. -- -- Likewise, unpack takes a UTF16-LE encoded ShortByteString and -- produces a list of BE encoded [Word16]. -- -- Indices and lengths are always in respect to Word16, not Word8. -- -- All functions will error out if the input string is not a valid UTF16 -- stream (uneven number of bytes). So use this module with caution. module System.OsPath.Data.ByteString.Short.Word16 -- | A compact representation of a Word8 vector. -- -- It has a lower memory overhead than a ByteString and does not -- contribute to heap fragmentation. It can be converted to or from a -- ByteString (at the cost of copying the string data). It -- supports very few other operations. -- -- It is suitable for use as an internal representation for code that -- needs to keep many short strings in memory, but it should not -- be used as an interchange type. That is, it should not generally be -- used in public APIs. The ByteString type is usually more -- suitable for use in interfaces; it is more flexible and it supports a -- wide range of operations. data ShortByteString SBS :: ByteArray# -> ShortByteString -- | O(1). The empty ShortByteString. empty :: ShortByteString -- | O(1) Convert a Word16 into a ShortByteString singleton :: Word16 -> ShortByteString -- | O(n). Convert a list into a ShortByteString pack :: [Word16] -> ShortByteString -- | O(n). Convert a ShortByteString into a list. unpack :: ShortByteString -> [Word16] -- | O(n). Convert a ShortByteString into a -- ByteString. fromShort :: ShortByteString -> ByteString -- | O(n). Convert a ByteString into a -- ShortByteString. -- -- This makes a copy, so does not retain the input string. toShort :: ByteString -> ShortByteString -- | O(n) Append a Word16 to the end of a ShortByteString -- -- Note: copies the entire byte array snoc :: ShortByteString -> Word16 -> ShortByteString infixl 5 `snoc` -- | O(n) cons is analogous to (:) for lists. -- -- Note: copies the entire byte array cons :: Word16 -> ShortByteString -> ShortByteString infixr 5 `cons` append :: ShortByteString -> ShortByteString -> ShortByteString -- | O(1) Extract the last element of a ShortByteString, which must -- be finite and at least one Word16. An exception will be thrown in the -- case of an empty ShortByteString. last :: HasCallStack => ShortByteString -> Word16 -- | O(n) Extract the elements after the head of a ShortByteString, -- which must at least one Word16. An exception will be thrown in the -- case of an empty ShortByteString. -- -- Note: copies the entire byte array tail :: HasCallStack => ShortByteString -> ShortByteString -- | O(n) Extract the head and tail of a ByteString, returning -- Nothing if it is empty. uncons :: ShortByteString -> Maybe (Word16, ShortByteString) -- | O(n) Extract first two elements and the rest of a ByteString, -- returning Nothing if it is shorter than two elements. uncons2 :: ShortByteString -> Maybe (Word16, Word16, ShortByteString) -- | O(1) Extract the first element of a ShortByteString, which must -- be at least one Word16. An exception will be thrown in the case of an -- empty ShortByteString. head :: HasCallStack => ShortByteString -> Word16 -- | O(n) Return all the elements of a ShortByteString except -- the last one. An exception will be thrown in the case of an empty -- ShortByteString. -- -- Note: copies the entire byte array init :: HasCallStack => ShortByteString -> ShortByteString -- | O(n) Extract the init and last of a ByteString, -- returning Nothing if it is empty. unsnoc :: ShortByteString -> Maybe (ShortByteString, Word16) -- | O(1) Test whether a ShortByteString is empty. null :: ShortByteString -> Bool -- | O(1) The length of a ShortByteString. length :: ShortByteString -> Int -- | This is like length, but the number of Word16, not -- Word8. numWord16 :: ShortByteString -> Int -- | O(n) map f xs is the ShortByteString obtained -- by applying f to each element of xs. map :: (Word16 -> Word16) -> ShortByteString -> ShortByteString -- | O(n) reverse xs efficiently returns the -- elements of xs in reverse order. reverse :: ShortByteString -> ShortByteString -- | O(n) The intercalate function takes a -- ShortByteString and a list of ShortByteStrings and -- concatenates the list after interspersing the first argument between -- each element of the list. intercalate :: ShortByteString -> [ShortByteString] -> ShortByteString -- | foldl, applied to a binary operator, a starting value -- (typically the left-identity of the operator), and a ShortByteString, -- reduces the ShortByteString using the binary operator, from left to -- right. foldl :: (a -> Word16 -> a) -> a -> ShortByteString -> a -- | foldl' is like foldl, but strict in the accumulator. foldl' :: (a -> Word16 -> a) -> a -> ShortByteString -> a -- | foldl1 is a variant of foldl that has no starting value -- argument, and thus must be applied to non-empty -- ShortByteStrings. An exception will be thrown in the case of an -- empty ShortByteString. foldl1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16 -- | foldl1' is like foldl1, but strict in the accumulator. -- An exception will be thrown in the case of an empty ShortByteString. foldl1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16 -- | foldr, applied to a binary operator, a starting value -- (typically the right-identity of the operator), and a ShortByteString, -- reduces the ShortByteString using the binary operator, from right to -- left. foldr :: (Word16 -> a -> a) -> a -> ShortByteString -> a -- | foldr' is like foldr, but strict in the accumulator. foldr' :: (Word16 -> a -> a) -> a -> ShortByteString -> a -- | foldr1 is a variant of foldr that has no starting value -- argument, and thus must be applied to non-empty -- ShortByteStrings An exception will be thrown in the case of an -- empty ShortByteString. foldr1 :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16 -- | foldr1' is a variant of foldr1, but is strict in the -- accumulator. foldr1' :: HasCallStack => (Word16 -> Word16 -> Word16) -> ShortByteString -> Word16 -- | O(n) Applied to a predicate and a ShortByteString, -- all determines if all elements of the ShortByteString -- satisfy the predicate. all :: (Word16 -> Bool) -> ShortByteString -> Bool -- | O(n) Applied to a predicate and a ByteString, any -- determines if any element of the ByteString satisfies the -- predicate. any :: (Word16 -> Bool) -> ShortByteString -> Bool concat :: [ShortByteString] -> ShortByteString -- | O(n) replicate n x is a ByteString of length -- n with x the value of every element. The following -- holds: -- --
-- replicate w c = unfoldr w (\u -> Just (u,u)) c --replicate :: Int -> Word16 -> ShortByteString -- | O(n), where n is the length of the result. The -- unfoldr function is analogous to the List 'unfoldr'. -- unfoldr builds a ShortByteString from a seed value. The -- function takes the element and returns Nothing if it is done -- producing the ShortByteString or returns Just (a,b), -- in which case, a is the next byte in the string, and -- b is the seed value for further production. -- -- This function is not efficient/safe. It will build a list of -- [Word16] and run the generator until it returns -- Nothing, otherwise recurse infinitely, then finally create a -- ShortByteString. -- -- Examples: -- --
-- unfoldr (\x -> if x <= 5 then Just (x, x + 1) else Nothing) 0 -- == pack [0, 1, 2, 3, 4, 5] --unfoldr :: (a -> Maybe (Word16, a)) -> a -> ShortByteString -- | O(n) Like unfoldr, unfoldrN builds a -- ShortByteString from a seed value. However, the length of the result -- is limited by the first argument to unfoldrN. This function is -- more efficient than unfoldr when the maximum length of the -- result is known. -- -- The following equation relates unfoldrN and unfoldr: -- --
-- fst (unfoldrN n f s) == take n (unfoldr f s) --unfoldrN :: forall a. Int -> (a -> Maybe (Word16, a)) -> a -> (ShortByteString, Maybe a) -- | O(n) take n, applied to a ShortByteString -- xs, returns the prefix of xs of length n, -- or xs itself if n > length xs. -- -- Note: copies the entire byte array take :: Int -> ShortByteString -> ShortByteString -- | O(1) takeEnd n xs is equivalent to -- drop (length xs - n) xs. Takes n -- elements from end of bytestring. -- --
-- >>> takeEnd 3 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL" -- "e\NULf\NULg\NUL" -- -- >>> takeEnd 0 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL" -- "" -- -- >>> takeEnd 4 "a\NULb\NULc\NUL" -- "a\NULb\NULc\NUL" --takeEnd :: Int -> ShortByteString -> ShortByteString -- | Returns the longest (possibly empty) suffix of elements satisfying the -- predicate. -- -- takeWhileEnd p is equivalent to reverse . -- takeWhile p . reverse. takeWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString -- | Similar to takeWhile, returns the longest (possibly empty) -- prefix of elements satisfying the predicate. takeWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString -- | O(n) drop n xs returns the suffix of -- xs after the first n elements, or [] if n > -- length xs. -- -- Note: copies the entire byte array drop :: Int -> ShortByteString -> ShortByteString -- | O(1) dropEnd n xs is equivalent to -- take (length xs - n) xs. Drops n -- elements from end of bytestring. -- --
-- >>> dropEnd 3 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL" -- "a\NULb\NULc\NULd\NUL" -- -- >>> dropEnd 0 "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL" -- "a\NULb\NULc\NULd\NULe\NULf\NULg\NUL" -- -- >>> dropEnd 4 "a\NULb\NULc\NUL" -- "" --dropEnd :: Int -> ShortByteString -> ShortByteString -- | Similar to dropWhile, drops the longest (possibly empty) prefix -- of elements satisfying the predicate and returns the remainder. -- -- Note: copies the entire byte array dropWhile :: (Word16 -> Bool) -> ShortByteString -> ShortByteString -- | Similar to dropWhileEnd, drops the longest (possibly empty) -- suffix of elements satisfying the predicate and returns the remainder. -- -- dropWhileEnd p is equivalent to reverse . -- dropWhile p . reverse. dropWhileEnd :: (Word16 -> Bool) -> ShortByteString -> ShortByteString -- | Returns the longest (possibly empty) suffix of elements which do -- not satisfy the predicate and the remainder of the string. -- -- breakEnd p is equivalent to spanEnd (not . -- p) and to (takeWhileEnd (not . p) &&& -- dropWhileEnd (not . p)). breakEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Similar to break, returns the longest (possibly empty) prefix -- of elements which do not satisfy the predicate and the -- remainder of the string. -- -- break p is equivalent to span (not . -- p) and to (takeWhile (not . p) &&& -- dropWhile (not . p)). break :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Similar to span, returns the longest (possibly empty) prefix of -- elements satisfying the predicate and the remainder of the string. -- -- span p is equivalent to break (not . -- p) and to (takeWhile p &&& -- dropWhile p). span :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | Returns the longest (possibly empty) suffix of elements satisfying the -- predicate and the remainder of the string. -- -- spanEnd p is equivalent to breakEnd (not . -- p) and to (takeWhileEnd p &&& -- dropWhileEnd p). -- -- We have -- --
-- spanEnd (not . isSpace) "x y z" == ("x y ", "z")
--
--
-- and
--
-- -- spanEnd (not . isSpace) ps -- == -- let (x, y) = span (not . isSpace) (reverse ps) in (reverse y, reverse x) --spanEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) splitAt n xs is equivalent to -- (take n xs, drop n xs). -- -- Note: copies the substrings splitAt :: Int -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) Break a ShortByteString into pieces separated by -- the byte argument, consuming the delimiter. I.e. -- --
-- split 10 "a\nb\nd\ne" == ["a","b","d","e"] -- fromEnum '\n' == 10 -- split 97 "aXaXaXa" == ["","X","X","X",""] -- fromEnum 'a' == 97 -- split 120 "x" == ["",""] -- fromEnum 'x' == 120 -- split undefined "" == [] -- and not [""] ---- -- and -- --
-- intercalate [c] . split c == id -- split == splitWith . (==) ---- -- Note: copies the substrings split :: Word16 -> ShortByteString -> [ShortByteString] -- | O(n) Splits a ShortByteString into components delimited -- by separators, where the predicate returns True for a separator -- element. The resulting components do not contain the separators. Two -- adjacent separators result in an empty component in the output. eg. -- --
-- splitWith (==97) "aabbaca" == ["","","bb","c",""] -- fromEnum 'a' == 97 -- splitWith undefined "" == [] -- and not [""] --splitWith :: (Word16 -> Bool) -> ShortByteString -> [ShortByteString] -- | O(n) The stripSuffix function takes two ShortByteStrings -- and returns Just the remainder of the second iff the first is -- its suffix, and otherwise Nothing. stripSuffix :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | O(n) The stripPrefix function takes two ShortByteStrings -- and returns Just the remainder of the second iff the first is -- its prefix, and otherwise Nothing. stripPrefix :: ShortByteString -> ShortByteString -> Maybe ShortByteString -- | Check whether one string is a substring of another. isInfixOf :: ShortByteString -> ShortByteString -> Bool -- | O(n) The isPrefixOf function takes two ShortByteStrings -- and returns True isPrefixOf :: ShortByteString -> ShortByteString -> Bool -- | O(n) The isSuffixOf function takes two ShortByteStrings -- and returns True iff the first is a suffix of the second. -- -- The following holds: -- --
-- isSuffixOf x y == reverse x `isPrefixOf` reverse y --isSuffixOf :: ShortByteString -> ShortByteString -> Bool -- | Break a string on a substring, returning a pair of the part of the -- string prior to the match, and the rest of the string. -- -- The following relationships hold: -- --
-- break (== c) l == breakSubstring (singleton c) l ---- -- For example, to tokenise a string, dropping delimiters: -- --
-- tokenise x y = h : if null t then [] else tokenise x (drop (length x) t) -- where (h,t) = breakSubstring x y ---- -- To skip to the first occurrence of a string: -- --
-- snd (breakSubstring x y) ---- -- To take the parts of a string before a delimiter: -- --
-- fst (breakSubstring x y) ---- -- Note that calling `breakSubstring x` does some preprocessing work, so -- you should avoid unnecessarily duplicating breakSubstring calls with -- the same pattern. breakSubstring :: ShortByteString -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(n) elem is the ShortByteString membership -- predicate. elem :: Word16 -> ShortByteString -> Bool -- | O(n) The find function takes a predicate and a -- ByteString, and returns the first element in matching the predicate, -- or Nothing if there is no such element. -- --
-- find f p = case findIndex f p of Just n -> Just (p ! n) ; _ -> Nothing --find :: (Word16 -> Bool) -> ShortByteString -> Maybe Word16 -- | O(n) filter, applied to a predicate and a ByteString, -- returns a ByteString containing those characters that satisfy the -- predicate. filter :: (Word16 -> Bool) -> ShortByteString -> ShortByteString -- | O(n) The partition function takes a predicate a -- ByteString and returns the pair of ByteStrings with elements which do -- and do not satisfy the predicate, respectively; i.e., -- --
-- partition p bs == (filter p xs, filter (not . p) xs) --partition :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString) -- | O(1) ShortByteString index (subscript) operator, -- starting from 0. index :: HasCallStack => ShortByteString -> Int -> Word16 -- | O(1) ShortByteString index, starting from 0, that -- returns Just if: -- --
-- 0 <= n < length bs --indexMaybe :: ShortByteString -> Int -> Maybe Word16 -- | O(1) ShortByteString index, starting from 0, that -- returns Just if: -- --
-- 0 <= n < length bs --(!?) :: ShortByteString -> Int -> Maybe Word16 -- | O(n) The elemIndex function returns the index of the -- first element in the given ShortByteString which is equal to -- the query element, or Nothing if there is no such element. elemIndex :: Word16 -> ShortByteString -> Maybe Int -- | O(n) The elemIndices function extends elemIndex, -- by returning the indices of all elements equal to the query element, -- in ascending order. elemIndices :: Word16 -> ShortByteString -> [Int] -- | count returns the number of times its argument appears in the -- ShortByteString count :: Word16 -> ShortByteString -> Int -- | O(n) The findIndex function takes a predicate and a -- ShortByteString and returns the index of the first element in -- the ByteString satisfying the predicate. findIndex :: (Word16 -> Bool) -> ShortByteString -> Maybe Int -- | O(n) The findIndices function extends findIndex, -- by returning the indices of all elements satisfying the predicate, in -- ascending order. findIndices :: (Word16 -> Bool) -> ShortByteString -> [Int] -- | O(n). Construct a new ShortByteString from a -- CWString. The resulting ShortByteString is an -- immutable copy of the original CWString, and is managed on -- the Haskell heap. The original CWString must be null -- terminated. packCWString :: Ptr Word16 -> IO ShortByteString -- | O(n). Construct a new ShortByteString from a -- CWStringLen. The resulting ShortByteString is an -- immutable copy of the original CWStringLen. The -- ShortByteString is a normal Haskell value and will be managed -- on the Haskell heap. packCWStringLen :: (Ptr Word16, Int) -> IO ShortByteString -- | O(n) construction. Use a ShortByteString with a -- function requiring a CWStringLen. As for -- useAsCWString this function makes a copy of the original -- ShortByteString. It must not be stored or used after the -- subcomputation finishes. newCWString :: ShortByteString -> IO (Ptr Word16) -- | O(n) construction. Use a ShortByteString with a -- function requiring a null-terminated CWString. The -- CWString is a copy and will be freed automatically; it must -- not be stored or used after the subcomputation finishes. useAsCWString :: ShortByteString -> (Ptr Word16 -> IO a) -> IO a -- | O(n) construction. Use a ShortByteString with a -- function requiring a CWStringLen. As for -- useAsCWString this function makes a copy of the original -- ShortByteString. It must not be stored or used after the -- subcomputation finishes. useAsCWStringLen :: ShortByteString -> ((Ptr Word16, Int) -> IO a) -> IO a module System.OsPath.Encoding.Internal 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 cWcharsToChars_UCS2 :: [Word16] -> [Char] cWcharsToChars :: [Word16] -> [Char] charsToCWchars :: [Char] -> [Word16] withFilePathWin :: FilePath -> (Int -> Ptr Word16 -> IO a) -> IO a peekFilePathWin :: (Ptr Word16, Int) -> IO FilePath withFilePathPosix :: FilePath -> (CStringLen -> IO a) -> IO a peekFilePathPosix :: CStringLen -> IO FilePath -- | Decode with the given TextEncoding. decodeWithTE :: TextEncoding -> ShortByteString -> Either EncodingException String -- | Encode with the given TextEncoding. encodeWithTE :: TextEncoding -> String -> Either EncodingException ShortByteString -- | This mimics the filepath decoder base uses on unix, 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 unix, 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 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 -- | 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 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 wNUL :: Word16 instance GHC.Classes.Eq System.OsPath.Encoding.Internal.EncodingException instance GHC.Show.Show System.OsPath.Encoding.Internal.EncodingException instance GHC.Exception.Type.Exception System.OsPath.Encoding.Internal.EncodingException instance Control.DeepSeq.NFData System.OsPath.Encoding.Internal.EncodingException 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 dencoder base uses on unix, 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, 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 "file.tar.gz" == ("file",".tar.gz")
--
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")
--
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) --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 "\\\\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 -- | 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 "file.tar.gz" == ("file",".tar.gz")
--
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")
--
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) --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 "\\\\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.OsString.Internal.Types -- | Commonly used windows string as wide character bytes. newtype WindowsString WindowsString :: ShortByteString -> WindowsString [getWindowsString] :: WindowsString -> ShortByteString -- | Just a short bidirectional synonym for WindowsString -- constructor. pattern WS :: ShortByteString -> WindowsString unWS :: WindowsString -> ShortByteString -- | Commonly used Posix string as uninterpreted char[] array. newtype PosixString PosixString :: ShortByteString -> PosixString [getPosixString] :: PosixString -> ShortByteString unPS :: PosixString -> ShortByteString -- | Just a short bidirectional synonym for PosixString constructor. pattern PS :: ShortByteString -> PosixString type PlatformString = PosixString newtype WindowsChar WindowsChar :: Word16 -> WindowsChar [getWindowsChar] :: WindowsChar -> Word16 unWW :: WindowsChar -> Word16 -- | Just a short bidirectional synonym for WindowsChar constructor. pattern WW :: Word16 -> WindowsChar newtype PosixChar PosixChar :: Word8 -> PosixChar [getPosixChar] :: PosixChar -> Word8 unPW :: PosixChar -> Word8 -- | Just a short bidirectional synonym for PosixChar constructor. pattern PW :: Word8 -> PosixChar type PlatformChar = 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. newtype OsString OsString :: PlatformString -> OsString [getOsString] :: OsString -> PlatformString -- | Newtype representing a code unit. -- -- On Windows, this is restricted to two-octet codepoints Word16, -- on POSIX one-octet (Word8). newtype OsChar OsChar :: PlatformChar -> OsChar [getOsChar] :: OsChar -> PlatformChar instance Control.DeepSeq.NFData System.OsString.Internal.Types.WindowsString instance GHC.Generics.Generic System.OsString.Internal.Types.WindowsString instance GHC.Base.Monoid System.OsString.Internal.Types.WindowsString instance GHC.Base.Semigroup System.OsString.Internal.Types.WindowsString instance GHC.Classes.Ord System.OsString.Internal.Types.WindowsString instance GHC.Classes.Eq System.OsString.Internal.Types.WindowsString instance Control.DeepSeq.NFData System.OsString.Internal.Types.PosixString instance GHC.Generics.Generic System.OsString.Internal.Types.PosixString instance GHC.Base.Monoid System.OsString.Internal.Types.PosixString instance GHC.Base.Semigroup System.OsString.Internal.Types.PosixString instance GHC.Classes.Ord System.OsString.Internal.Types.PosixString instance GHC.Classes.Eq System.OsString.Internal.Types.PosixString instance Control.DeepSeq.NFData System.OsString.Internal.Types.WindowsChar instance GHC.Generics.Generic System.OsString.Internal.Types.WindowsChar instance GHC.Classes.Ord System.OsString.Internal.Types.WindowsChar instance GHC.Classes.Eq System.OsString.Internal.Types.WindowsChar instance Control.DeepSeq.NFData System.OsString.Internal.Types.PosixChar instance GHC.Generics.Generic System.OsString.Internal.Types.PosixChar instance GHC.Classes.Ord System.OsString.Internal.Types.PosixChar instance GHC.Classes.Eq System.OsString.Internal.Types.PosixChar instance Control.DeepSeq.NFData System.OsString.Internal.Types.OsString instance GHC.Generics.Generic System.OsString.Internal.Types.OsString instance Control.DeepSeq.NFData System.OsString.Internal.Types.OsChar instance GHC.Generics.Generic System.OsString.Internal.Types.OsChar instance GHC.Show.Show System.OsString.Internal.Types.OsChar instance GHC.Classes.Eq System.OsString.Internal.Types.OsChar instance GHC.Classes.Ord System.OsString.Internal.Types.OsChar instance GHC.Show.Show System.OsString.Internal.Types.OsString instance GHC.Classes.Eq System.OsString.Internal.Types.OsString instance GHC.Classes.Ord System.OsString.Internal.Types.OsString instance GHC.Base.Monoid System.OsString.Internal.Types.OsString instance GHC.Base.Semigroup System.OsString.Internal.Types.OsString instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.OsString instance GHC.Show.Show System.OsString.Internal.Types.PosixChar instance GHC.Show.Show System.OsString.Internal.Types.WindowsChar instance GHC.Show.Show System.OsString.Internal.Types.PosixString instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.PosixString instance GHC.Show.Show System.OsString.Internal.Types.WindowsString instance Language.Haskell.TH.Syntax.Lift System.OsString.Internal.Types.WindowsString 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.OsString.Posix -- | Commonly used Posix string as uninterpreted char[] array. data PosixString data PosixChar -- | Partial unicode friendly encoding. -- -- This encodes as UTF8 (strictly), which is a good guess. -- -- Throws an EncodingException if encoding fails. encodeUtf :: MonadThrow m => String -> m PosixString -- | Encode a String with the specified encoding. encodeWith :: TextEncoding -> String -> Either EncodingException PosixString -- | This mimics the behavior of the base library when doing filesystem -- operations, which uses shady PEP 383 style encoding (based on the -- current locale, but PEP 383 only works properly on UTF-8 encodings, so -- good luck). -- -- Looking up the locale requires IO. If you're not worried about calls -- to setFileSystemEncoding, then unsafePerformIO may be -- feasible (make sure to deeply evaluate the result to catch -- exceptions). encodeFS :: String -> IO PosixString -- | Constructs a platform string from a ByteString. -- -- This is a no-op. fromBytes :: MonadThrow m => ByteString -> m PosixString -- | QuasiQuote a PosixString. This accepts Unicode characters and -- encodes as UTF-8 on unix. 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 -- | This mimics the behavior of the base library when doing filesystem -- operations, which uses shady PEP 383 style encoding (based on the -- current locale, but PEP 383 only works properly on UTF-8 encodings, so -- good luck). -- -- Looking up the locale requires IO. If you're not worried about calls -- to setFileSystemEncoding, then unsafePerformIO may be -- feasible (make sure to deeply evaluate the result to catch -- exceptions). decodeFS :: PosixString -> 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 module System.OsString.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 a EncodingException if encoding fails. encodeUtf :: MonadThrow m => String -> m OsString -- | Encode an OsString given the platform specific encodings. encodeWith :: TextEncoding -> TextEncoding -> String -> Either EncodingException OsString -- | Like encodeUtf, except this mimics the behavior of the base -- library when doing filesystem operations, which is: -- --
-- 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 -- | An implementation of the Abstract FilePath Proposal, which aims -- to supersede type FilePath = String for various reasons: -- --
-- 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 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 a EncodingException if encoding fails. encodeUtf :: MonadThrow m => FilePath -> m OsPath -- | Encode a FilePath with the specified encoding. encodeWith :: TextEncoding -> TextEncoding -> FilePath -> Either EncodingException OsPath -- | Like encodeUtf, except this mimics the behavior of the base -- library when doing filesystem operations, which is: -- --
-- 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