use std::{
io,
path::{Path, PathBuf},
process::Command,
};
pub fn open(path: &Path) -> io::Result<()> {
if !path.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("file not found: {}", path.display()),
));
}
let status = opener_command(&as_argument(path)).status()?;
if status.success() {
Ok(())
} else {
Err(io::Error::other(format!("opener exited with {status}")))
}
}
fn as_argument(path: &Path) -> PathBuf {
std::path::absolute(path).unwrap_or_else(|error| {
log::debug!(
"could not absolutize {}: {error}; passing it as given",
path.display()
);
path.to_path_buf()
})
}
#[cfg(target_os = "macos")]
fn opener_command(path: &Path) -> Command {
let mut command = Command::new("open");
command.arg("--").arg(path);
command
}
#[cfg(target_os = "windows")]
fn opener_command(path: &Path) -> Command {
let mut command = Command::new("rundll32.exe");
command.arg("url.dll,FileProtocolHandler").arg(path);
command
}
#[cfg(all(unix, not(target_os = "macos")))]
fn opener_command(path: &Path) -> Command {
let mut command = Command::new("xdg-open");
command.arg(path);
command
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_missing_file_is_reported_as_not_found() {
let error = open(Path::new("/no/such/file/at/all")).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::NotFound);
}
#[test]
fn a_dash_leading_relative_path_becomes_absolute() {
let argument = as_argument(Path::new("-a"));
assert!(argument.is_absolute(), "{}", argument.display());
assert_eq!(argument.file_name(), Some("-a".as_ref()));
}
#[test]
fn an_absolute_path_stays_as_it_is() {
let path = Path::new("/tmp/plain.txt");
assert_eq!(as_argument(path), path);
}
}