HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

  • Packagefilepath-1.5.4.0
  • Exports66
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceTypes.hs

Types

3 declarations
newtypenewtype WindowsString
#

Commonly used windows string as wide character bytes.

Instances9Eq, Ord, Show, Generic, Semigroup, Monoid, …
newtypenewtype WindowsChar
#
Instances6Eq, Ord, Show, Generic, NFData, Rep

Filepath construction

6 declarations
valueencodeUtf :: MonadThrow m => String -> m WindowsString
#

Partial unicode friendly encoding.

This encodes as UTF16-LE (strictly), which is a pretty good guess.

Throws an EncodingException if encoding fails. If the input does not contain surrogate chars, you can use unsafeEncodeUtf.

valuepack :: [WindowsChar] -> WindowsString
#

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.

Filepath deconstruction

4 declarations

Word construction

1 declaration

Word deconstruction

1 declaration

Separator predicates

7 declarations

The character that separates directories. In the case where more than one character is possible, pathSeparator is the 'ideal' one.

pathSeparator == '\\'S

The list of all possible separators.

pathSeparators == ['\\', '/']
pathSeparator `elem` pathSeparators

The character that is used to separate the entries in the $PATH environment variable.

searchPathSeparator == ';'

$PATH methods

1 declaration

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"]

Extension functions

14 declarations

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/","")

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"

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

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"

Remove last extension, and the "." preceding it.

dropExtension "/directory/path.ext" == "/directory/path"
dropExtension x == fst (splitExtension x)

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"

Does the given filename have an extension?

hasExtension "/directory/path.ext" == True
hasExtension "/directory/path" == False
null (takeExtension x) == not (hasExtension x)

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")

Drop all extensions.

dropExtensions "/directory/path.ext" == "/directory/path"
dropExtensions "file.tar.gz" == "file"
not $ hasExtension $ dropExtensions x
not $ any isExtSeparator $ takeFileName $ dropExtensions x

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

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

Filename/directory functions

13 declarations

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:","")

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)

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)

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"

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

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:\\"

Set the directory, keeping the filename the same.

replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"
Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x

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"

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"]

Join path elements back together.

joinPath z == foldr (</>) "" z
joinPath ["/","directory/","file.ext"] == "/directory/file.ext"
Valid x => joinPath (splitPath x) == x
joinPath [] == ""

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"]

Drive functions

6 declarations

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")

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"
valuehasDrive :: WindowsPath -> Bool
#

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
valueisDrive :: WindowsPath -> Bool
#

Is an element a drive

isDrive "C:\\" == True
isDrive "C:\\foo" == False
isDrive "" == False

Trailing slash functions

3 declarations

Is an item either a directory or the last character a path separator?

hasTrailingPathSeparator "test" == False
hasTrailingPathSeparator "test/" == True

Add a trailing file path separator if one is not already present.

hasTrailingPathSeparator (addTrailingPathSeparator x)
hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x

File name manipulations

7 declarations

Normalise a file

  • // outside of the drive can be made blank

  • / -> pathSeparator

  • ./ -> ""

Does not remove "..", because of symlinks.

normalise "c:\\file/bob\\" == "C:\\file\\bob\\"
normalise "c:\\" == "C:\\"
normalise "C:.\\" == "C:"
normalise "\\\\server\\test" == "\\\\server\\test"
normalise "//server/test" == "\\\\server\\test"
normalise "c:/file" == "C:\\file"
normalise "/file" == "\\file"
normalise "\\" == "\\"
normalise "/./" == "\\"
normalise "." == "."

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:/")

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 "/" "//" == "//"
valueisRelative :: WindowsPath -> Bool
#

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]:

  • "A UNC name of any format [is never relative]."

  • "You cannot use the "\?" prefix with a relative path."

valueisValid :: 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

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"