Browser::Open
WorksPicks a command that can open a URL, per kernel — and silently ignores $BROWSER when you set it to an absolute path.
- Version
1.0.1github:azawawi- Depends
File::Which- License
- MIT
- Its own test suite
- 2 files, green
- Checked
- 2026-09-15 against Raku++ 3.28.0 and Rakudo 2026.08
Install it #
$ rakupp install Browser::Openzef install Browser::Open writes the same store; either installer leaves the module usable by both engines.
What it is for #
"Open this URL in the user's browser" has a different answer on every system: open on macOS, xdg-open or one of a dozen fallbacks on Linux, a registry lookup on Windows. This distribution walks a fixed table of candidates for the current kernel and hands you the first one that exists.
Asking what it would run #
use Browser::Open;
say 'kernel : ', $*KERNEL.name;
say 'open-browser-cmd : ', open-browser-cmd();
say 'open-browser-cmd-all : ', open-browser-cmd-all();
say '';
my $cmd = open-browser-cmd();
say 'it is executable : ', $cmd.IO.x;
say 'return type : ', $cmd.WHAT.^name;
say '';
say 'open-browser($url) spawns that command with the URL as its single';
say 'argv element — Proc::Async.new($cmd, $url), no shell — so a URL';
say 'containing shell metacharacters is not an injection vector.';
say ' it would run : ', ($cmd, 'https://example.invalid/x').raku;kernel : darwin
open-browser-cmd : /usr/bin/open
open-browser-cmd-all : /usr/bin/open
it is executable : True
return type : Str
open-browser($url) spawns that command with the URL as its single
argv element — Proc::Async.new($cmd, $url), no shell — so a URL
containing shell metacharacters is not an injection vector.
it would run : ("/usr/bin/open", "https://example.invalid/x")The signature is open-browser(Str $url, Bool $all = False), and on macOS the $all flag is a no-op: both -cmd subs answer the same string.
The one thing to know #
Setting BROWSER to an absolute path — the one way everybody sets it — is silently ignored. You get the OS default instead.
use Browser::Open;
use File::Which;
say 'the candidate table stores the $BROWSER value with no "this is';
say 'already a path" flag, so it goes through which():';
say '';
say ' which("cat") = ', which('cat').raku;
say ' which("/bin/cat") = ', which('/bin/cat').raku, ' <- a real, executable file';
say ' "/bin/cat".IO.x = ', '/bin/cat'.IO.x;
say '';
say 'File::Which only scans PATH directories, so an absolute path returns';
say 'Nil and the candidate is skipped. No error, no warning.';
say '';
say ' BROWSER=/bin/cat -> falls back to the OS default';
say ' BROWSER=cat -> resolves to ', which('cat').raku;
say '';
say 'set BROWSER to a NAME on your PATH, not to a path.';the candidate table stores the $BROWSER value with no "this is
already a path" flag, so it goes through which():
which("cat") = "/bin/cat"
which("/bin/cat") = Any <- a real, executable file
"/bin/cat".IO.x = True
File::Which only scans PATH directories, so an absolute path returns
Nil and the candidate is skipped. No error, no warning.
BROWSER=/bin/cat -> falls back to the OS default
BROWSER=cat -> resolves to "/bin/cat"
set BROWSER to a NAME on your PATH, not to a path.There is a second half to the same trap: $BROWSER is read once, at module load, into a module-scope table. Assigning %*ENV<BROWSER> after use Browser::Open has no effect at all — only a require after the assignment sees it.
Failure is silent #
use Browser::Open;
say 'open-browser returns nothing useful:';
say ' the sub does Proc::Async.new($cmd, $url).start and DISCARDS the';
say ' Promise, so it returns immediately and a script that exits right';
say ' after calling it gives the child no time to start.';
say '';
say 'and if no candidate resolves at all, it returns without spawning';
say 'anything and without any indication. Check first:';
my $cmd = open-browser-cmd();
if $cmd {
say ' a browser command is available : ', $cmd;
} else {
say ' no browser command found — tell the user yourself';
}
say '';
say 'a wrapper worth having:';
say ' sub open-url($url) {';
say ' my $cmd = open-browser-cmd() or die "no browser command found";';
say ' await Proc::Async.new($cmd, $url).start;';
say ' }';open-browser returns nothing useful:
the sub does Proc::Async.new($cmd, $url).start and DISCARDS the
Promise, so it returns immediately and a script that exits right
after calling it gives the child no time to start.
and if no candidate resolves at all, it returns without spawning
anything and without any indication. Check first:
a browser command is available : /usr/bin/open
a wrapper worth having:
sub open-url($url) {
my $cmd = open-browser-cmd() or die "no browser command found";
await Proc::Async.new($cmd, $url).start;
}Where the two engines differ #
Nothing. The candidate table, the which resolution, the $BROWSER handling and the load-time capture all behave identically — this is a table walk and a File::Which call, with no container-type or laziness question in it.
use Browser::Open;
say 'what the table holds for this kernel, in order, is an implementation';
say 'detail — but which one won is not:';
say ' chosen : ', open-browser-cmd();
say '';
say 'two dead branches worth knowing about, since they explain the shape';
say 'of the code: two entries carry an unused fourth field $no_search, and';
say 'no row ever sets it, so `next if $no_search && …` and';
say '`return $cmd if $no_search` are both unreachable.';
say '';
say 'on Windows the distribution declares a Win32::Registry dependency,';
say 'which is not in the ecosystem index — `rakupp test` reports it as';
say 'skipped by distro name, which is correct off Windows.';what the table holds for this kernel, in order, is an implementation
detail — but which one won is not:
chosen : /usr/bin/open
two dead branches worth knowing about, since they explain the shape
of the code: two entries carry an unused fourth field $no_search, and
no row ever sets it, so `next if $no_search && …` and
`return $cmd if $no_search` are both unreachable.
on Windows the distribution declares a Win32::Registry dependency,
which is not in the ecosystem index — `rakupp test` reports it as
skipped by distro name, which is correct off Windows.