File::Path::Resolve
WorksA path string turned into an absolute IO::Path, with a leading tilde expanded to the home directory first and every .. collapsed.
- Version
0.0.1- Depends
- nothing outside the core
- License
- Unlicense
- Its own test suite
- no test files, so trivially green
- Checked
- 2026-09-15 against Raku++ 3.28.0 and Rakudo 2026.08
Install it #
$ rakupp install File::Path::Resolvezef install File::Path::Resolve writes the same store; either installer leaves the module usable by both engines.
What it is for #
A path typed by a person, or read out of a configuration file, arrives in whatever shape suited whoever wrote it: relative to somewhere, with a tilde at the front, with .. segments in the middle. Before the program can compare it with another path, use it as a hash key or print it in a diagnostic, it has to become one canonical absolute thing.
Raku's .resolve does most of that. It does not expand the tilde, because the shell normally does that before a program sees it — and a path from a config file never went through a shell. This distribution is the tilde expansion plus .resolve, in one call.
Resolving #
use File::Path::Resolve;
my $R = File::Path::Resolve;
my $home = $*HOME.Str;
say $R.absolute('~').Str eq $home;
say $R.absolute('~/.config').Str eq "$home/.config";
say $R.absolute('~//.config').Str eq "$home/.config";
say $R.absolute('/tmp/../tmp').Str.ends-with('/tmp');
say $R.absolute('relative/bit').Str.starts-with($*CWD.Str);
say $R.absolute('~').^name;
say $R.absolute('/no/such/path').Str;True
True
True
True
True
IO::Path
/no/such/pathEverything comes back as an IO::Path, absolute, whether or not the path exists — the last line is a path to nothing and resolves happily, which is right, because you often want to canonicalise a name before creating it.
The one thing to know #
relative($path, $base) treats the base as a file and resolves against its parent directory, so passing a directory silently discards that directory:
use File::Path::Resolve;
my $R = File::Path::Resolve;
my $root = $*TMPDIR.add("fpr-{$*PID}");
$root.mkdir;
$root.add('project').mkdir;
$root.add('project/sub').mkdir;
$root.add('project/conf.ini').spurt("k=v\n");
$root.add('project/sub/other.ini').spurt("k=v\n");
my $dir = $root.add('project').Str;
my $file = $root.add('project/conf.ini').Str;
say 'base is the directory: ', $R.relative('sub/other.ini', $dir).e;
say 'base is a file in it : ', $R.relative('sub/other.ini', $file).e;
$root.add('project/sub/other.ini').unlink;
$root.add('project/conf.ini').unlink;
$root.add('project/sub').rmdir;
$root.add('project').rmdir;
$root.rmdir;base is the directory: False
base is a file in it : TrueThe first resolves to a path that does not exist, because project was dropped on the way. The second finds the file. So the argument means "the file whose neighbours you are resolving", not "the directory you are resolving inside" — the opposite of what the name suggests, and a mistake that produces a wrong path rather than an error.
If your base really is a directory, append a dummy file name to it, or just use $dir.IO.add($path) and skip this method.
Two smaller things: ~username is not expanded, so ~root becomes a relative path with a literal tilde in it, and an empty string throws a constraint failure rather than returning anything.