1use std::io;
5use std::process::{Child, Command, Stdio};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10enum Platform {
11 Macos,
12 Linux,
13 Windows,
14 Unknown,
15}
16
17fn platform() -> Platform {
18 if cfg!(target_os = "macos") {
19 Platform::Macos
20 } else if cfg!(target_os = "windows") {
21 Platform::Windows
22 } else if cfg!(target_os = "linux") {
23 Platform::Linux
24 } else {
25 Platform::Unknown
26 }
27}
28
29fn browser_argv(name: &str) -> Option<Vec<&'static str>> {
34 let name = match name {
36 "google-chrome" | "google_chrome" => "chrome",
37 "chromium-browser" => "chromium",
38 other => other,
39 };
40
41 match platform() {
42 Platform::Macos => Some(match name {
43 "default" => vec!["open"],
44 "safari" => vec!["open", "-a", "Safari"],
45 "firefox" => vec!["open", "-a", "Firefox"],
46 "chrome" => vec!["open", "-a", "Google Chrome"],
47 "chromium" => vec!["open", "-a", "Chromium"],
48 "opera" => vec!["open", "-a", "Opera"],
49 "curl" => vec!["curl", "-s"],
50 _ => return None,
51 }),
52 Platform::Linux | Platform::Unknown => Some(match name {
53 "default" => vec!["xdg-open"],
54 "firefox" => vec!["firefox"],
55 "chrome" => vec!["google-chrome"],
56 "chromium" => vec!["chromium"],
57 "mozilla" => vec!["mozilla"],
58 "epiphany" => vec!["epiphany"],
59 "curl" => vec!["curl", "-s"],
60 _ => return None,
61 }),
62 Platform::Windows => Some(match name {
63 "default" | "chrome" | "firefox" | "edge" => vec!["cmd", "/c", "start", ""],
64 "curl" => vec!["curl", "-s"],
65 _ => return None,
66 }),
67 }
68}
69
70pub struct Browser {
74 name: String,
75}
76
77impl Browser {
78 pub fn new(name: impl Into<String>) -> Self {
85 Self { name: name.into() }
86 }
87
88 pub fn open(&self, url: &str) -> io::Result<Child> {
90 let argv = browser_argv(&self.name).unwrap_or_else(|| {
91 vec![] });
94
95 if argv.is_empty() {
96 return Command::new(&self.name)
98 .arg(url)
99 .stdin(Stdio::null())
100 .stdout(Stdio::null())
101 .stderr(Stdio::null())
102 .spawn();
103 }
104
105 let mut cmd = Command::new(argv[0]);
107 for arg in &argv[1..] {
108 cmd.arg(arg);
109 }
110 cmd.arg(url)
111 .stdin(Stdio::null())
112 .stdout(Stdio::null())
113 .stderr(Stdio::null())
114 .spawn()
115 }
116
117 pub fn command(&self) -> String {
119 let argv = browser_argv(&self.name);
120 match argv {
121 Some(v) => v.join(" "),
122 None => self.name.clone(),
123 }
124 }
125}
126
127#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn default_browser_has_command() {
135 let b = Browser::new("default");
136 assert!(!b.command().is_empty());
137 }
138
139 #[test]
140 fn alias_google_chrome() {
141 let argv_alias = browser_argv("google-chrome");
143 let argv_canon = browser_argv("chrome");
144 assert_eq!(argv_alias, argv_canon);
145 }
146
147 #[test]
148 fn unknown_browser_returns_none_from_table() {
149 assert!(browser_argv("netscape").is_none());
150 }
151
152 #[test]
153 fn curl_browser_known() {
154 let argv = browser_argv("curl");
155 assert!(argv.is_some());
156 let v = argv.unwrap();
157 assert_eq!(v[0], "curl");
158 }
159}