#![doc(html_root_url = "https://docs.rs/opener/0.6.1")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(
rust_2018_idioms,
deprecated_in_future,
macro_use_extern_crate,
missing_debug_implementations,
unused_qualifications
)]
#[cfg(all(feature = "reveal", target_os = "linux"))]
mod freedesktop;
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
mod linux_and_more;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
use crate::linux_and_more as sys;
#[cfg(target_os = "macos")]
use crate::macos as sys;
#[cfg(target_os = "windows")]
use crate::windows as sys;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::fmt::{self, Display, Formatter};
use std::process::{Command, ExitStatus, Stdio};
use std::{env, io};
pub fn open<P>(path: P) -> Result<(), OpenError>
where
P: AsRef<OsStr>,
{
sys::open(path.as_ref())
}
pub fn open_browser<P>(path: P) -> Result<(), OpenError>
where
P: AsRef<OsStr>,
{
let mut path = path.as_ref();
if let Ok(browser_var) = env::var("BROWSER") {
let windows_path;
if is_wsl() && browser_var.ends_with(".exe") {
if let Some(windows_path_2) = wsl_to_windows_path(path) {
windows_path = windows_path_2;
path = &windows_path;
}
};
Command::new(&browser_var)
.arg(path)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| OpenError::Spawn {
cmds: browser_var,
source: err,
})?;
Ok(())
} else {
sys::open(path)
}
}
#[cfg(feature = "reveal")]
pub fn reveal<P>(path: P) -> Result<(), OpenError>
where
P: AsRef<std::path::Path>,
{
sys::reveal(path.as_ref())
}
#[non_exhaustive]
#[derive(Debug)]
pub enum OpenError {
Io(io::Error),
Spawn {
cmds: String,
source: io::Error,
},
ExitStatus {
cmd: &'static str,
status: ExitStatus,
stderr: String,
},
}
impl Display for OpenError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
OpenError::Io(_) => {
write!(f, "IO error")?;
}
OpenError::Spawn { cmds, source: _ } => {
write!(f, "error spawning command(s) '{cmds}'")?;
}
OpenError::ExitStatus {
cmd,
status,
stderr,
} => {
write!(f, "command '{cmd}' did not execute successfully; {status}")?;
let stderr = stderr.trim();
if !stderr.is_empty() {
write!(f, "\ncommand stderr:\n{stderr}")?;
}
}
}
Ok(())
}
}
impl Error for OpenError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
OpenError::Io(inner) => Some(inner),
OpenError::Spawn { cmds: _, source } => Some(source),
OpenError::ExitStatus { .. } => None,
}
}
}
#[cfg(target_os = "linux")]
fn is_wsl() -> bool {
sys::is_wsl()
}
#[cfg(not(target_os = "linux"))]
fn is_wsl() -> bool {
false
}
#[cfg(target_os = "linux")]
fn wsl_to_windows_path(path: &OsStr) -> Option<OsString> {
use bstr::ByteSlice;
use std::os::unix::ffi::OsStringExt;
let output = Command::new("wslpath")
.arg("-w")
.arg(path)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
if !output.status.success() {
return None;
}
Some(OsString::from_vec(output.stdout.trim_end().to_vec()))
}
#[cfg(not(target_os = "linux"))]
fn wsl_to_windows_path(_path: &OsStr) -> Option<OsString> {
unreachable!()
}
#[cfg(not(target_os = "windows"))]
fn wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError> {
use std::io::Read;
let exit_status = child.wait().map_err(OpenError::Io)?;
if exit_status.success() {
Ok(())
} else {
let mut stderr_output = String::new();
if let Some(stderr) = child.stderr.as_mut() {
stderr.read_to_string(&mut stderr_output).ok();
}
Err(OpenError::ExitStatus {
cmd: cmd_name,
status: exit_status,
stderr: stderr_output,
})
}
}