use std::process::Command;
pub fn open_command_for(os: &str, url: &str) -> (String, Vec<String>) {
match os {
"macos" => ("open".to_string(), vec![url.to_string()]),
"windows" => (
"cmd".to_string(),
vec![
"/C".to_string(),
"start".to_string(),
String::new(),
url.to_string(),
],
),
_ => ("xdg-open".to_string(), vec![url.to_string()]),
}
}
pub fn open_url_via(spawn: fn(&mut Command) -> std::io::Result<()>, url: &str) -> bool {
let (program, args) = open_command_for(std::env::consts::OS, url);
let mut cmd = Command::new(program);
cmd.args(args);
match spawn(&mut cmd) {
Ok(()) => true,
Err(e) => {
tracing::warn!(error = %e, "Failed to launch browser");
false
}
}
}
pub fn spawn_detached(cmd: &mut Command) -> std::io::Result<()> {
cmd.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map(|_child| ())
}
pub fn open_url(url: &str) -> bool {
open_url_via(spawn_detached, url)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn macos_uses_open() {
let (program, args) = open_command_for("macos", "https://x.example");
assert_eq!(program, "open");
assert_eq!(args, vec!["https://x.example"]);
}
#[test]
fn windows_uses_start_with_a_placeholder_title() {
let (program, args) = open_command_for("windows", "https://x.example");
assert_eq!(program, "cmd");
assert_eq!(args, vec!["/C", "start", "", "https://x.example"]);
}
#[test]
fn other_unixes_fall_back_to_xdg_open() {
let (program, args) = open_command_for("linux", "https://x.example");
assert_eq!(program, "xdg-open");
assert_eq!(args, vec!["https://x.example"]);
assert_eq!(open_command_for("dragonfly", "https://x").0, "xdg-open");
}
#[test]
fn open_url_via_reports_spawn_success() {
fn ok(_: &mut Command) -> std::io::Result<()> {
Ok(())
}
assert!(open_url_via(ok, "https://x.example"));
}
#[test]
fn open_url_via_reports_spawn_failure() {
fn boom(_: &mut Command) -> std::io::Result<()> {
Err(std::io::Error::other("no browser"))
}
with_tracing(|| assert!(!open_url_via(boom, "https://x.example")));
}
struct AlwaysOnSubscriber;
impl tracing::Subscriber for AlwaysOnSubscriber {
fn enabled(&self, _metadata: &tracing::Metadata<'_>) -> bool {
true
}
fn register_callsite(
&self,
_metadata: &'static tracing::Metadata<'static>,
) -> tracing::subscriber::Interest {
tracing::subscriber::Interest::always()
}
fn new_span(&self, _span: &tracing::span::Attributes<'_>) -> tracing::span::Id {
tracing::span::Id::from_u64(1)
}
fn record(&self, _span: &tracing::span::Id, _values: &tracing::span::Record<'_>) {}
fn record_follows_from(&self, _span: &tracing::span::Id, _follows: &tracing::span::Id) {}
fn event(&self, event: &tracing::Event<'_>) {
assert!(self.enabled(event.metadata()));
}
fn enter(&self, _span: &tracing::span::Id) {}
fn exit(&self, _span: &tracing::span::Id) {}
fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
Some(tracing::metadata::LevelFilter::TRACE)
}
}
#[test]
fn always_on_subscriber_span_methods_are_all_no_ops() {
with_tracing(|| {
let span = tracing::info_span!("test-span", field = tracing::field::Empty);
span.record("field", 1);
let other = tracing::info_span!("other-span");
span.follows_from(&other);
let _enter = span.enter();
tracing::info!(parent: &span, "inside span");
});
}
fn with_tracing<T>(f: impl FnOnce() -> T) -> T {
static INSTALLED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
INSTALLED.get_or_init(|| {
let _ = tracing::subscriber::set_global_default(AlwaysOnSubscriber);
tracing::callsite::rebuild_interest_cache();
});
f()
}
#[test]
fn spawn_detached_launches_a_real_process() {
let mut cmd = Command::new("true");
let _ = spawn_detached(&mut cmd);
}
#[test]
fn spawn_detached_errors_on_a_missing_program() {
let mut cmd = Command::new("/nonexistent/browser/launcher");
assert!(spawn_detached(&mut cmd).is_err());
}
#[test]
fn open_url_runs_the_real_launcher_without_opening_a_browser() {
let _ = open_url("leviath-open-url-test-target");
}
}