Path Utilities
The :std/misc/path
library provides functions that complement those
inherited from Gambit in making it easy to manipulate POSIX-style file paths.
At this time, we make no pretense of supporting Windows-style paths;
if on Windows, use WSL or Cygwin—or help improve Gerbil to be more portable.
To use the bindings from this module:
(import :std/misc/path)
path-default-extension
(path-default-extension path ext) -> path
Add a default extension to a path:
given a path
(string) and an extension ext
(a string
starting with "."
, or ""
or #f
for no extension),
if the ext
start with "."
and the path
has no extension
(as per path-extension
), then return the concatenation
of the path
and ext
, otherwise return path
unmodified.
Examples:
> (path-default-extension "foo.ss" ".o")
"foo.ss"
> (path-default-extension "foo" ".o")
"foo.o"
> (path-default-extension "foo" #f)
"foo"
path-force-extension
(path-force-extension path ext) -> path
Add a extension to a path:
given a path
(string) and an extension ext
(a string
starting with "."
, or ""
for the empty extension,
or #f
for no actual extension to force),
if ext
is false, then return path
unmodified,
otherwise, return a next string computed by
stripping any current extension from the path
,
and replacing it with the provided extension ext
(which may be ""
at which point nothing is added to the stripped path).
Note how the semantics of #f
and ""
differ here,
unlike in path-default-extension
.
Also note that if a filename starts with "."
(after disregarding any
"/"
-delimited directory before it), then
that "."
does not count as part of an extension;
however, there might be more than one "."
in the filename, at which point
only the last one is considered as starting "the" extension,
but what comes before might suddenly become the extension if ext
is ""
.
However, these are corner cases that shouldn't matter most of the time.
Examples:
> (path-force-extension "foo.ss" ".o")
"foo.o"
> (path-force-extension "foo" ".o")
"foo.o"
> (path-force-extension "foo.ss" #f)
"foo.ss"
> (path-force-extension "foo.ss" "")
"foo"
path-extension-is?
(path-extension-is? path extension) -> bool
Return true if the given path
has the given extension
.
Examples:
> (path-extension-is? "foo.ss" ".ss")
#t
> (path-extension-is? "foo" "")
#t
> (path-extension-is? "foo.c" "") ;; Nope, extension is ".c"
#f
> (path-extension-is? ".foo" ".foo") ;; Nope, initial "." doesn't count.
#f
> (path-extension-is? "foo.b.c" ".b.c") ;; Nope, the extension is just ".c"
#f
> (path-extension-is? "foo.ss" "")
#f
subpath
;; : String String ... -> String
(subpath top . sub-components)
Given a top
path and any number of sub-components
, all of them strings,
construct a path made by joining top
and all the sub-components
in order
into a subpath of the top
path.
Examples:
> (subpath "foo" "bar" "baz/quux" "myfile.ext")
"foo/bar/baz/quux/myfile.ext"
> (subpath "/home/user" ".gerbil" "lib" "static")
"/home/user/.gerbil/lib/static"
subpath?
;; : (OrFalse String) (OrFalse String) -> (OrFalse String)
(subpath? maybe-subpath base-path)
If maybe-subpath
is a pathname that is under base-path
, return a pathname object that
when used with path-expand
with defaults base-path
, yields maybe-subpath
.
Otherwise, return #f
.
Examples:
> (subpath? "/foo" "/bar")
#f
> (subpath? "/home/user/.gerbil/lib" "/home/user")
".gerbil/lib"
> (subpath? "foo/bar/baz/quux" "foo/bar")
"baz/quux"
path-absolute?
;; : String -> Bool
(path-absolute? path)
Given path
, a string, return true if that path
is absolute.
We only support POSIX paths, where a path absolute iff it starts with "/"
.
In a hypothetical future where we better support Windows,
the test may be platform-dependent and more complex.
Examples:
> (path-absolute? "/foo")
#t
> (path-absolute? "foo")
#f
absolute-path?
;; : Any -> Bool
(absolute-path? path)
Given an object path
that may or may not be a string, return true if
that object is indeed a string, and that string indeed denotes an absolute path
as per path-absolute?
.
Examples:
> (absolute-path? "/foo")
#t
> (absolute-path? "foo")
#f
> (absolute-path? 'foo)
#f
> (absolute-path? 42)
#f
> (absolute-path? #f)
#f
> (absolute-path? #t)
#f
get-absolute-path
;; : (Or String False (-> String)) -> String
(get-absolute-path path-designator)
Return the absolute path associated to a path-designator
:
- A string designates itself.
- A thunk designates the result of calling it.
#f
designates the(current-directory)
.
Raise an error if the designator is invalid or does not designate an absolute path.
Examples:
> (get-absolute-path "/foo")
"/foo"
> (get-absolute-path "foo")
ERROR
> (get-absolute-path (lambda () (or (getenv "MY_APP_HOME") (other-default))))
"/opt/my-application" ;; or wherever that environment variable points to,
;; or an error if not defined to a absolute path
> (get-absolute-path #f)
"/home/user" ;; or wherever your (current-directory) points to
ensure-absolute-path
;; : String (Or String False (-> String)) -> String
(ensure-absolute-path path (base #f))
Given a path
, that may be absolute, or may be relative to a base
,
try hard to return the absolute path that this path
would designate
if used to open a file relative to the base
.
Raise an error if that attempt is unsuccessful.
The base
itself is only consulted if path
is not absolute,
at which point it denotes a base absolute path as per get-absolute-path
,
that will raise an exception if it cannot indeed
compute an absolute path out of it.
Examples:
> (ensure-absolute-path "/foo" #f)
"/foo"
> (ensure-absolute-path "/foo" error)
"/foo"
> (ensure-absolute-path "foo" "/bar")
"/bar/foo"
> (ensure-absolute-path "foo" current-directory)
"/home/user/foo" ;; depends on your actual current-directory
> (ensure-absolute-path "foo" #f)
"/home/user/foo" ;; same as above
> (ensure-absolute-path "foo" "bar")
*** ERROR IN ? [Error]: Path not absolute
path-maybe-normalize
;; : String -> String
(path-maybe-normalize path)
Try to path-normalize
the given path
, but if that fails, falls back to
using path-simplify
to at least simplify the path as much as possible.
There are many reasons why path-normalize
may fail:
- The file doesn't exist, and so cannot have a canonical name.
- Along the path there may be symlinks in unreadable directories,
"interesting" filesystems that defy introspection,
weird
chroot
situations, etc. - Race conditions with other threads or processes modifying the
system rights at the same moment may cause
path-normalize
to yield inconsistent results.
So you may want to gracefully fall back to a non-normalized yet simplified path when that's the case.
Note that when the path
is a directory and path-normalize
succeeds,
the path it returns will end with "/"
; however, if a directory-to-be
does not exist yet, then the fallback to path-simplify
will not add
a "/"
at the end.
Examples:
;; Assuming /etc is indeed already a normalized path
> (path-maybe-normalize "/etc/.")
"/etc/"
;; Goes through path-simplify and does not add the / at the end!
> (path-maybe-normalize "/../../../does////../not/../exist/../etc")
"/etc"
path-enough
;; : String String -> String
(path-enough sub base)
If sub
is a pathname that is under base
, return a pathname string that
when used with path-expand
with defaults base
, returns sub
.
Otherwise, return sub
unchanged.
This function is broadly similar to the Common Lisp standard function
enough-namestring
, or its semi-standard library variant uiop:enough-pathname
.
Examples:
> (path-enough "/home/user/.gerbil/lib" "/home/user")
".gerbil/lib"
> (path-enough "/etc" "/home/user")
"/etc"
> (path-enough "foo/bar/baz/quux" "foo/bar")
"bar/quux"
> (path-enough "foo/bar" "baz/quux")
"foo/bar"
path-simplify-directory
;; : String -> String
(path-simplify-directory path)
Given a path
, keep only its directory portion, then simplify it.
Examples:
> (path-simplify-directory "/opt/local/bin/../stow/foo/bin/bar.sh")
"/opt/local/stow/foo/bin"
path-normalized-directory
;; : String -> String
(path-normalized-directory path)
Given a path
, keep only its directory portion, then try to normalize it
as per path-maybe-normalize
.
Examples:
> (path-normalized-directory "/etc/password")
"/etc/"
;; Here we first take the path-directory portion, which is only "/"
> (path-normalized-directory "/etc")
"/"
path-parent
;; : String -> String
(path-parent path)
Given the path
to a directory, with or without trailing "/"
,
get the path to the parent directory, and try to normalize it
as per path-maybe-normalize
, leaving a "/"
at the end.
Examples:
> (path-parent "/home/user")
"/home/"
> (path-parent "/home/user/")
"/home/"
> (path-parent "/etc/X11")
"/etc/"
> (path-parent "/etc/X11/")
"/etc/"
> (path-parent "does/not/exist/")
"does/not/"
> (path-parent "does/not/exist")
"does/not/"
path-simplify
;; : String keep..?:Bool -> String
(path-simplify path keep..?: (keep..? #f))
Given a path
to a file that may or may not exist on the current filesystem,
return a simplified path, eliminating redundant uses of "."
or "/"
.
Unless keep..?
is true, also remove ".."
path components that follow
a non-".."
path component, which is usually a valid simplification,
but may fail to preserve subtle behavior such as:
- Deliberately following a symlink then going up from the target directory
- Simplifying away an attempt to follow a directory that may or may not exist as a back-handed way to detect whether it exists or not.
- Weird behavior that may happen due to filesystem mounts.
NB: Always simplify away a trailing "/"
except for the root directory "/"
.
Examples:
> (path-simplify-directory "/foo/./..///.../../bar/../baz////")
"/bar/"