]>
Commit | Line | Data |
---|---|---|
136023e0 XL |
1 | #![doc(html_root_url = "https://docs.rs/opener/0.5.0")] |
2 | ||
f9f354fc | 3 | //! This crate provides the [`open`] function, which opens a file or link with the default program |
136023e0 | 4 | //! configured on the system: |
f9f354fc XL |
5 | //! |
6 | //! ```no_run | |
7 | //! # fn main() -> Result<(), ::opener::OpenError> { | |
8 | //! // open a website | |
9 | //! opener::open("https://www.rust-lang.org")?; | |
10 | //! | |
11 | //! // open a file | |
12 | //! opener::open("../Cargo.toml")?; | |
13 | //! # Ok(()) | |
14 | //! # } | |
15 | //! ``` | |
16 | //! | |
136023e0 XL |
17 | //! An [`open_browser`] function is also provided, for when you intend on opening a file or link in a |
18 | //! browser, specifically. This function works like the [`open`] function, but explicitly allows | |
19 | //! overriding the browser launched by setting the `$BROWSER` environment variable. | |
f9f354fc | 20 | |
3dfed10e XL |
21 | #![warn( |
22 | rust_2018_idioms, | |
23 | deprecated_in_future, | |
24 | macro_use_extern_crate, | |
25 | missing_debug_implementations, | |
136023e0 | 26 | unused_qualifications |
3dfed10e | 27 | )] |
f9f354fc | 28 | |
136023e0 XL |
29 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] |
30 | mod linux_and_more; | |
31 | #[cfg(target_os = "macos")] | |
32 | mod macos; | |
f9f354fc | 33 | #[cfg(target_os = "windows")] |
136023e0 XL |
34 | mod windows; |
35 | ||
36 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] | |
37 | use crate::linux_and_more as sys; | |
38 | #[cfg(target_os = "macos")] | |
39 | use crate::macos as sys; | |
40 | #[cfg(target_os = "windows")] | |
41 | use crate::windows as sys; | |
42 | ||
43 | use std::error::Error; | |
44 | use std::ffi::{OsStr, OsString}; | |
45 | use std::fmt::{self, Display, Formatter}; | |
46 | use std::process::{Command, ExitStatus, Stdio}; | |
47 | use std::{env, io}; | |
48 | ||
49 | /// Opens a file or link with the system default program. | |
50 | /// | |
51 | /// Note that a path like "rustup.rs" could potentially refer to either a file or a website. If you | |
52 | /// want to open the website, you should add the "http://" prefix, for example. | |
53 | /// | |
54 | /// Also note that a result of `Ok(())` just means a way of opening the path was found, and no error | |
55 | /// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For | |
56 | /// example, `Ok(())` would be returned even if a file was opened with a program that can't read the | |
57 | /// file, or a dead link was opened in a browser. | |
58 | /// | |
59 | /// ## Platform Implementation Details | |
60 | /// | |
61 | /// - On Windows the `ShellExecuteW` Windows API function is used. | |
62 | /// - On Mac the system `open` command is used. | |
63 | /// - On Windows Subsystem for Linux (WSL), the system `wslview` from [`wslu`] is used if available, | |
64 | /// otherwise the system `xdg-open` is used, if available. | |
65 | /// - On non-WSL Linux and other platforms, | |
66 | /// the system `xdg-open` script is used if available, otherwise an `xdg-open` script embedded in | |
67 | /// this library is used. | |
68 | /// | |
69 | /// [`wslu`]: https://github.com/wslutilities/wslu/ | |
70 | pub fn open<P>(path: P) -> Result<(), OpenError> | |
71 | where | |
72 | P: AsRef<OsStr>, | |
73 | { | |
74 | sys::open(path.as_ref()) | |
75 | } | |
f9f354fc | 76 | |
136023e0 XL |
77 | /// Opens a file or link with the system default program, using the `BROWSER` environment variable |
78 | /// when set. | |
79 | /// | |
80 | /// If the `BROWSER` environment variable is set, the program specified by it is used to open the | |
81 | /// path. If not, behavior is identical to [`open()`]. | |
82 | pub fn open_browser<P>(path: P) -> Result<(), OpenError> | |
83 | where | |
84 | P: AsRef<OsStr>, | |
85 | { | |
86 | let mut path = path.as_ref(); | |
87 | if let Ok(browser_var) = env::var("BROWSER") { | |
88 | let windows_path; | |
89 | if is_wsl() && browser_var.ends_with(".exe") { | |
90 | if let Some(windows_path_2) = wsl_to_windows_path(path) { | |
91 | windows_path = windows_path_2; | |
92 | path = &windows_path; | |
93 | } | |
94 | }; | |
95 | ||
96 | Command::new(&browser_var) | |
97 | .arg(path) | |
98 | .stdin(Stdio::null()) | |
99 | .stdout(Stdio::null()) | |
100 | .stderr(Stdio::piped()) | |
101 | .spawn() | |
102 | .map_err(OpenError::Io)?; | |
103 | ||
104 | Ok(()) | |
105 | } else { | |
106 | sys::open(path) | |
107 | } | |
108 | } | |
f9f354fc XL |
109 | |
110 | /// An error type representing the failure to open a path. Possibly returned by the [`open`] | |
111 | /// function. | |
112 | /// | |
113 | /// The `ExitStatus` variant will never be returned on Windows. | |
114 | #[derive(Debug)] | |
115 | pub enum OpenError { | |
116 | /// An IO error occurred. | |
117 | Io(io::Error), | |
118 | ||
136023e0 | 119 | /// A command exited with a non-zero exit status. |
f9f354fc XL |
120 | ExitStatus { |
121 | /// A string that identifies the command. | |
122 | cmd: &'static str, | |
123 | ||
124 | /// The failed process's exit status. | |
125 | status: ExitStatus, | |
126 | ||
127 | /// Anything the process wrote to stderr. | |
128 | stderr: String, | |
129 | }, | |
130 | } | |
131 | ||
132 | impl Display for OpenError { | |
3dfed10e | 133 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
f9f354fc XL |
134 | match self { |
135 | OpenError::Io(_) => { | |
136 | write!(f, "IO error")?; | |
137 | } | |
138 | OpenError::ExitStatus { | |
139 | cmd, | |
140 | status, | |
141 | stderr, | |
142 | } => { | |
143 | write!( | |
144 | f, | |
145 | "command '{}' did not execute successfully; {}", | |
146 | cmd, status | |
147 | )?; | |
148 | ||
149 | let stderr = stderr.trim(); | |
150 | if !stderr.is_empty() { | |
151 | write!(f, "\ncommand stderr:\n{}", stderr)?; | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | Ok(()) | |
157 | } | |
158 | } | |
159 | ||
160 | impl Error for OpenError { | |
161 | fn source(&self) -> Option<&(dyn Error + 'static)> { | |
162 | match self { | |
163 | OpenError::Io(inner) => Some(inner), | |
164 | OpenError::ExitStatus { .. } => None, | |
165 | } | |
166 | } | |
167 | } | |
168 | ||
136023e0 XL |
169 | #[cfg(target_os = "linux")] |
170 | fn is_wsl() -> bool { | |
171 | sys::is_wsl() | |
f9f354fc XL |
172 | } |
173 | ||
136023e0 XL |
174 | #[cfg(not(target_os = "linux"))] |
175 | fn is_wsl() -> bool { | |
176 | false | |
f9f354fc XL |
177 | } |
178 | ||
136023e0 XL |
179 | #[cfg(target_os = "linux")] |
180 | fn wsl_to_windows_path(path: &OsStr) -> Option<OsString> { | |
181 | use bstr::ByteSlice; | |
182 | use std::os::unix::ffi::OsStringExt; | |
f9f354fc | 183 | |
136023e0 XL |
184 | let output = Command::new("wslpath") |
185 | .arg("-w") | |
186 | .arg(path) | |
187 | .stdin(Stdio::null()) | |
188 | .stdout(Stdio::piped()) | |
189 | .stderr(Stdio::null()) | |
190 | .output() | |
191 | .ok()?; | |
192 | ||
193 | if !output.status.success() { | |
194 | return None; | |
f9f354fc | 195 | } |
f9f354fc | 196 | |
136023e0 | 197 | Some(OsString::from_vec(output.stdout.trim_end().to_vec())) |
f9f354fc XL |
198 | } |
199 | ||
136023e0 XL |
200 | #[cfg(not(target_os = "linux"))] |
201 | fn wsl_to_windows_path(_path: &OsStr) -> Option<OsString> { | |
202 | unreachable!() | |
f9f354fc XL |
203 | } |
204 | ||
205 | #[cfg(not(target_os = "windows"))] | |
136023e0 XL |
206 | fn wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError> { |
207 | use std::io::Read; | |
f9f354fc | 208 | |
136023e0 | 209 | let exit_status = child.wait().map_err(OpenError::Io)?; |
f9f354fc XL |
210 | if exit_status.success() { |
211 | Ok(()) | |
212 | } else { | |
136023e0 XL |
213 | let mut stderr_output = String::new(); |
214 | if let Some(stderr) = child.stderr.as_mut() { | |
215 | stderr.read_to_string(&mut stderr_output).ok(); | |
216 | } | |
f9f354fc XL |
217 | |
218 | Err(OpenError::ExitStatus { | |
136023e0 | 219 | cmd: cmd_name, |
f9f354fc | 220 | status: exit_status, |
136023e0 | 221 | stderr: stderr_output, |
f9f354fc XL |
222 | }) |
223 | } | |
224 | } |