]> git.proxmox.com Git - rustc.git/blobdiff - vendor/opener/src/lib.rs
New upstream version 1.55.0+dfsg1
[rustc.git] / vendor / opener / src / lib.rs
index 93da550aefca03bd5e07cb1d2b49fc2368abc9be..a84f9491a8174775f5fc8bfea464f60d6932cd73 100644 (file)
@@ -1,5 +1,7 @@
+#![doc(html_root_url = "https://docs.rs/opener/0.5.0")]
+
 //! This crate provides the [`open`] function, which opens a file or link with the default program
-//! configured on the system.
+//! configured on the system:
 //!
 //! ```no_run
 //! # fn main() -> Result<(), ::opener::OpenError> {
 //! # }
 //! ```
 //!
-//! ## Platform Implementation Details
-//! On Windows the `ShellExecuteW` Windows API function is used. On Mac the system `open` command is
-//! used. On other platforms, the `xdg-open` script is used. The system `xdg-open` is not used;
-//! instead a version is embedded within this library.
+//! An [`open_browser`] function is also provided, for when you intend on opening a file or link in a
+//! browser, specifically. This function works like the [`open`] function, but explicitly allows
+//! overriding the browser launched by setting the `$BROWSER` environment variable.
 
 #![warn(
     rust_2018_idioms,
     deprecated_in_future,
     macro_use_extern_crate,
     missing_debug_implementations,
-    unused_labels,
-    unused_qualifications,
-    clippy::cast_possible_truncation
+    unused_qualifications
 )]
 
+#[cfg(not(any(target_os = "windows", target_os = "macos")))]
+mod linux_and_more;
+#[cfg(target_os = "macos")]
+mod macos;
 #[cfg(target_os = "windows")]
-use crate::windows::open_sys;
+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};
+
+/// Opens a file or link with the system default program.
+///
+/// Note that a path like "rustup.rs" could potentially refer to either a file or a website. If you
+/// want to open the website, you should add the "http://" prefix, for example.
+///
+/// Also note that a result of `Ok(())` just means a way of opening the path was found, and no error
+/// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For
+/// example, `Ok(())` would be returned even if a file was opened with a program that can't read the
+/// file, or a dead link was opened in a browser.
+///
+/// ## Platform Implementation Details
+///
+/// - On Windows the `ShellExecuteW` Windows API function is used.
+/// - On Mac the system `open` command is used.
+/// - On Windows Subsystem for Linux (WSL), the system `wslview` from [`wslu`] is used if available,
+/// otherwise the system `xdg-open` is used, if available.
+/// - On non-WSL Linux and other platforms,
+/// the system `xdg-open` script is used if available, otherwise an `xdg-open` script embedded in
+/// this library is used.
+///
+/// [`wslu`]: https://github.com/wslutilities/wslu/
+pub fn open<P>(path: P) -> Result<(), OpenError>
+where
+    P: AsRef<OsStr>,
+{
+    sys::open(path.as_ref())
+}
 
-use std::{
-    error::Error,
-    ffi::OsStr,
-    fmt::{self, Display, Formatter},
-    io,
-    process::ExitStatus,
-};
+/// Opens a file or link with the system default program, using the `BROWSER` environment variable
+/// when set.
+///
+/// If the `BROWSER` environment variable is set, the program specified by it is used to open the
+/// path. If not, behavior is identical to [`open()`].
+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(OpenError::Io)?;
+
+        Ok(())
+    } else {
+        sys::open(path)
+    }
+}
 
 /// An error type representing the failure to open a path. Possibly returned by the [`open`]
 /// function.
@@ -47,7 +116,7 @@ pub enum OpenError {
     /// An IO error occurred.
     Io(io::Error),
 
-    /// The command exited with a non-zero exit status.
+    /// A command exited with a non-zero exit status.
     ExitStatus {
         /// A string that identifies the command.
         cmd: &'static str,
@@ -97,129 +166,59 @@ impl Error for OpenError {
     }
 }
 
-impl From<io::Error> for OpenError {
-    fn from(err: io::Error) -> Self {
-        OpenError::Io(err)
-    }
+#[cfg(target_os = "linux")]
+fn is_wsl() -> bool {
+    sys::is_wsl()
 }
 
-/// Opens a file or link with the system default program.
-///
-/// Note that a path like "rustup.rs" could potentially refer to either a file or a website. If you
-/// want to open the website, you should add the "http://" prefix, for example.
-///
-/// Also note that a result of `Ok(())` just means a way of opening the path was found, and no error
-/// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For
-/// example, `Ok(())` would be returned even if a file was opened with a program that can't read the
-/// file, or a dead link was opened in a browser.
-pub fn open<P>(path: P) -> Result<(), OpenError>
-where
-    P: AsRef<OsStr>,
-{
-    open_sys(path.as_ref())
+#[cfg(not(target_os = "linux"))]
+fn is_wsl() -> bool {
+    false
 }
 
-#[cfg(target_os = "windows")]
-mod windows {
-    use super::OpenError;
-    use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, ptr};
-    use winapi::{ctypes::c_int, um::shellapi::ShellExecuteW};
-
-    pub fn open_sys(path: &OsStr) -> Result<(), OpenError> {
-        const SW_SHOW: c_int = 5;
-
-        let path = convert_path(path)?;
-        let operation: Vec<u16> = OsStr::new("open\0").encode_wide().collect();
-        let result = unsafe {
-            ShellExecuteW(
-                ptr::null_mut(),
-                operation.as_ptr(),
-                path.as_ptr(),
-                ptr::null(),
-                ptr::null(),
-                SW_SHOW,
-            )
-        };
-        if result as c_int > 32 {
-            Ok(())
-        } else {
-            Err(io::Error::last_os_error().into())
-        }
-    }
+#[cfg(target_os = "linux")]
+fn wsl_to_windows_path(path: &OsStr) -> Option<OsString> {
+    use bstr::ByteSlice;
+    use std::os::unix::ffi::OsStringExt;
 
-    fn convert_path(path: &OsStr) -> io::Result<Vec<u16>> {
-        let mut maybe_result: Vec<u16> = path.encode_wide().collect();
-        if maybe_result.iter().any(|&u| u == 0) {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "path contains NUL byte(s)",
-            ));
-        }
-        maybe_result.push(0);
-        Ok(maybe_result)
+    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;
     }
-}
 
-#[cfg(target_os = "macos")]
-fn open_sys(path: &OsStr) -> Result<(), OpenError> {
-    open_not_windows("open", path, &[], None, "open")
+    Some(OsString::from_vec(output.stdout.trim_end().to_vec()))
 }
 
-#[cfg(not(any(target_os = "windows", target_os = "macos")))]
-fn open_sys(path: &OsStr) -> Result<(), OpenError> {
-    const XDG_OPEN_SCRIPT: &[u8] = include_bytes!("xdg-open");
-
-    open_not_windows(
-        "sh",
-        path,
-        &["-s"],
-        Some(XDG_OPEN_SCRIPT),
-        "xdg-open (internal)",
-    )
+#[cfg(not(target_os = "linux"))]
+fn wsl_to_windows_path(_path: &OsStr) -> Option<OsString> {
+    unreachable!()
 }
 
 #[cfg(not(target_os = "windows"))]
-fn open_not_windows(
-    cmd: &str,
-    path: &OsStr,
-    extra_args: &[&str],
-    piped_input: Option<&[u8]>,
-    cmd_friendly_name: &'static str,
-) -> Result<(), OpenError> {
-    use std::{
-        io::{Read, Write},
-        process::{Command, Stdio},
-    };
-
-    let stdin_type = if piped_input.is_some() {
-        Stdio::piped()
-    } else {
-        Stdio::null()
-    };
-
-    let mut cmd = Command::new(cmd)
-        .args(extra_args)
-        .arg(path)
-        .stdin(stdin_type)
-        .stdout(Stdio::null())
-        .stderr(Stdio::piped())
-        .spawn()?;
-
-    if let Some(stdin) = cmd.stdin.as_mut() {
-        stdin.write_all(piped_input.unwrap())?;
-    }
+fn wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError> {
+    use std::io::Read;
 
-    let exit_status = cmd.wait()?;
+    let exit_status = child.wait().map_err(OpenError::Io)?;
     if exit_status.success() {
         Ok(())
     } else {
-        let mut stderr = String::new();
-        cmd.stderr.as_mut().unwrap().read_to_string(&mut stderr)?;
+        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_friendly_name,
+            cmd: cmd_name,
             status: exit_status,
-            stderr,
+            stderr: stderr_output,
         })
     }
 }