hpath-0.8.0: Support for well-typed paths

Copyright© 2016 Julian Ospald
LicenseBSD3
MaintainerJulian Ospald <hasufell@posteo.de>
Stabilityexperimental
Portabilityportable
Safe HaskellSafe-Inferred
LanguageHaskell2010

System.Posix.FilePath

Contents

Description

The equivalent of System.FilePath on raw (byte string) file paths.

Not all functions of System.FilePath are implemented yet. Feel free to contribute!

Synopsis

Separator predicates

pathSeparator :: Word8 Source

Path separator character

isPathSeparator :: Word8 -> Bool Source

Check if a character is the path separator

\n ->  (_chr n == '/') == isPathSeparator n

searchPathSeparator :: Word8 Source

Search path separator

isSearchPathSeparator :: Word8 -> Bool Source

Check if a character is the search path separator

\n -> (_chr n == ':') == isSearchPathSeparator n

extSeparator :: Word8 Source

File extension separator

isExtSeparator :: Word8 -> Bool Source

Check if a character is the file extension separator

\n -> (_chr n == '.') == isExtSeparator n

$PATH methods

splitSearchPath :: ByteString -> [RawFilePath] Source

Take a ByteString, split it on the searchPathSeparator. Blank items are converted to ..

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

getSearchPath :: IO [RawFilePath] Source

Get a list of RawFilePaths in the $PATH variable.

Extension functions

splitExtension :: RawFilePath -> (RawFilePath, ByteString) Source

Split a RawFilePath into a path+filename and extension

>>> splitExtension "file.exe"
("file",".exe")
>>> splitExtension "file"
("file","")
>>> splitExtension "/path/file.tar.gz"
("/path/file.tar",".gz")
\path -> uncurry (BS.append) (splitExtension path) == path

takeExtension :: RawFilePath -> ByteString Source

Get the final extension from a RawFilePath

>>> takeExtension "file.exe"
".exe"
>>> takeExtension "file"
""
>>> takeExtension "/path/file.tar.gz"
".gz"

replaceExtension :: RawFilePath -> ByteString -> RawFilePath Source

Change a file's extension

\path -> let ext = takeExtension path in replaceExtension path ext == path

dropExtension :: RawFilePath -> RawFilePath Source

Drop the final extension from a RawFilePath

>>> dropExtension "file.exe"
"file"
>>> dropExtension "file"
"file"
>>> dropExtension "/path/file.tar.gz"
"/path/file.tar"

addExtension :: RawFilePath -> ByteString -> RawFilePath Source

Add an extension to a RawFilePath

>>> addExtension "file" ".exe"
"file.exe"
>>> addExtension "file.tar" ".gz"
"file.tar.gz"
>>> addExtension "/path/" ".ext"
"/path/.ext"

hasExtension :: RawFilePath -> Bool Source

Check if a RawFilePath has an extension

>>> hasExtension "file"
False
>>> hasExtension "file.tar"
True
>>> hasExtension "/path.part1/"
False

splitExtensions :: RawFilePath -> (RawFilePath, ByteString) Source

Split a RawFilePath on the first extension.

>>> splitExtensions "/path/file.tar.gz"
("/path/file",".tar.gz")
\path -> uncurry addExtension (splitExtensions path) == path

dropExtensions :: RawFilePath -> RawFilePath Source

Remove all extensions from a RawFilePath

>>> dropExtensions "/path/file.tar.gz"
"/path/file"

takeExtensions :: RawFilePath -> ByteString Source

Take all extensions from a RawFilePath

>>> takeExtensions "/path/file.tar.gz"
".tar.gz"

stripExtension :: ByteString -> RawFilePath -> Maybe RawFilePath Source

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
>>> 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
\path -> stripExtension "" path == Just path
\path -> dropExtension path  == fromJust (stripExtension (takeExtension path) path)
\path -> dropExtensions path == fromJust (stripExtension (takeExtensions path) path)

Filename/directory functions

splitFileName :: RawFilePath -> (RawFilePath, RawFilePath) Source

Split a RawFilePath into (path,file). combine is the inverse

>>> splitFileName "path/file.txt"
("path/","file.txt")
>>> splitFileName "path/"
("path/","")
>>> splitFileName "file.txt"
("./","file.txt")
\path -> uncurry combine (splitFileName path) == path || fst (splitFileName path) == "./"

takeFileName :: RawFilePath -> RawFilePath Source

Get the file name

>>> takeFileName "path/file.txt"
"file.txt"
>>> takeFileName "path/"
""

replaceFileName :: RawFilePath -> ByteString -> RawFilePath Source

Change the file name

\path -> replaceFileName path (takeFileName path) == path

dropFileName :: RawFilePath -> RawFilePath Source

Drop the file name

>>> dropFileName "path/file.txt"
"path/"
>>> dropFileName "file.txt"
"./"

takeBaseName :: RawFilePath -> ByteString Source

Get the file name, without a trailing extension

>>> takeBaseName "path/file.tar.gz"
"file.tar"
>>> takeBaseName ""
""

replaceBaseName :: RawFilePath -> ByteString -> RawFilePath Source

Change the base name

>>> replaceBaseName "path/file.tar.gz" "bob"
"path/bob.gz"
\path -> replaceBaseName path (takeBaseName path) == path

takeDirectory :: RawFilePath -> RawFilePath Source

Get the directory, moving up one level if it's already a directory

>>> takeDirectory "path/file.txt"
"path"
>>> takeDirectory "file"
"."
>>> takeDirectory "/path/to/"
"/path/to"
>>> takeDirectory "/path/to"
"/path"

replaceDirectory :: RawFilePath -> ByteString -> RawFilePath Source

Change the directory component of a RawFilePath

\path -> replaceDirectory path (takeDirectory path) `equalFilePath` path || takeDirectory path == "."

combine :: RawFilePath -> RawFilePath -> RawFilePath Source

Join two paths together

>>> combine "/" "file"
"/file"
>>> combine "/path/to" "file"
"/path/to/file"
>>> combine "file" "/absolute/path"
"/absolute/path"

(</>) :: RawFilePath -> RawFilePath -> RawFilePath Source

Operator version of combine

splitPath :: RawFilePath -> [RawFilePath] Source

Split a path into a list of components:

>>> splitPath "/path/to/file.txt"
["/","path/","to/","file.txt"]
\path -> BS.concat (splitPath path) == path

joinPath :: [RawFilePath] -> RawFilePath Source

Join a split path back together

\path -> joinPath (splitPath path) == path
>>> joinPath ["path","to","file.txt"]
"path/to/file.txt"

splitDirectories :: RawFilePath -> [RawFilePath] Source

Like splitPath, but without trailing slashes

>>> splitDirectories "/path/to/file.txt"
["/","path","to","file.txt"]
>>> splitDirectories ""
[]

Trailing slash functions

hasTrailingPathSeparator :: RawFilePath -> Bool Source

Check if the last character of a RawFilePath is /.

>>> hasTrailingPathSeparator "/path/"
True
>>> hasTrailingPathSeparator "/"
True
>>> hasTrailingPathSeparator "/path"
False

addTrailingPathSeparator :: RawFilePath -> RawFilePath Source

Add a trailing path separator.

>>> addTrailingPathSeparator "/path"
"/path/"
>>> addTrailingPathSeparator "/path/"
"/path/"
>>> addTrailingPathSeparator "/"
"/"

dropTrailingPathSeparator :: RawFilePath -> RawFilePath Source

Remove a trailing path separator

>>> dropTrailingPathSeparator "/path/"
"/path"
>>> dropTrailingPathSeparator "/path////"
"/path"
>>> dropTrailingPathSeparator "/"
"/"
>>> dropTrailingPathSeparator "//"
"/"

File name manipulations

normalise :: RawFilePath -> RawFilePath Source

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 "./bob/fred/"
"bob/fred/"
>>> normalise "./bob////.fred/./...///./..///#."
"bob/.fred/.../../#."
>>> normalise "."
"."
>>> normalise "./"
"./"
>>> normalise "./."
"./"
>>> normalise "/./"
"/"
>>> normalise "/"
"/"
>>> normalise "bob/fred/."
"bob/fred/"
>>> normalise "//home"
"/home"

makeRelative :: RawFilePath -> RawFilePath -> RawFilePath Source

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.

>>> makeRelative "/directory" "/directory/file.ext"
"file.ext"
>>> 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"
\p -> makeRelative p p == "."
\p -> makeRelative (takeDirectory p) p `equalFilePath` takeFileName p

prop x y -> equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y / makeRelative y x) x

equalFilePath :: RawFilePath -> RawFilePath -> Bool Source

Equality of two filepaths. The filepaths are normalised and trailing path separators are dropped.

>>> equalFilePath "foo" "foo"
True
>>> equalFilePath "foo" "foo/"
True
>>> equalFilePath "foo" "./foo"
True
>>> equalFilePath "" ""
True
>>> equalFilePath "foo" "/foo"
False
>>> equalFilePath "foo" "FOO"
False
>>> equalFilePath "foo" "../foo"
False
\p -> equalFilePath p p

isRelative :: RawFilePath -> Bool Source

Check if a path is relative

\path -> isRelative path /= isAbsolute path

isAbsolute :: RawFilePath -> Bool Source

Check if a path is absolute

>>> isAbsolute "/path"
True
>>> isAbsolute "path"
False
>>> isAbsolute ""
False

isValid :: RawFilePath -> Bool Source

Is a FilePath valid, i.e. could you create a file like it?

>>> isValid ""
False
>>> isValid "\0"
False
>>> isValid "/random_ path:*"
True

makeValid :: RawFilePath -> RawFilePath Source

Take a FilePath and make it valid; does not change already valid FilePaths.

>>> makeValid ""
"_"
>>> makeValid "file\0name"
"file_name"
\p -> if isValid p then makeValid p == p else makeValid p /= p
\p -> isValid (makeValid p)

isFileName :: RawFilePath -> Bool Source

Is the given path a valid filename? This includes "." and "..".

>>> isFileName "lal"
True
>>> isFileName "."
True
>>> isFileName ".."
True
>>> isFileName ""
False
>>> isFileName "\0"
False
>>> isFileName "/random_ path:*"
False

hasParentDir :: RawFilePath -> Bool Source

Check if the filepath has any parent directories in it.

>>> hasParentDir "/.."
True
>>> hasParentDir "foo/bar/.."
True
>>> hasParentDir "foo/../bar/."
True
>>> hasParentDir "foo/bar"
False
>>> hasParentDir "foo"
False
>>> hasParentDir ""
False
>>> hasParentDir ".."
False

hiddenFile :: RawFilePath -> Bool Source

Whether the file is a hidden file.

>>> hiddenFile ".foo"
True
>>> hiddenFile "..foo.bar"
True
>>> hiddenFile "some/path/.bar"
True
>>> hiddenFile "..."
True
>>> hiddenFile "dod.bar"
False
>>> hiddenFile "."
False
>>> hiddenFile ".."
False
>>> hiddenFile ""
False