<commit_before>macro_rules! enforce {
    ($($e:expr),+,) => {
        if $(!$e)||+ {
            return TestResult::discard();
        }
    }
}

macro_rules! test {
    ($e:expr) => {
        TestResult::from_bool($e)
    }
}

trait IsClose {
    fn is_close(self, rhs: Self) -> bool;
}

macro_rules! float {
    ($($ty:ident),+) => {$(
        mod $ty {
            impl ::test::IsClose for $ty {
                fn is_close(self, rhs: $ty) -> bool {
                    use std::num::Float;

                    const TOL: $ty = 1e-3;

                    (self - rhs).abs() < TOL
                }
            }

            mod linspace {
                use quickcheck::TestResult;

                mod rev {
                    use quickcheck::TestResult;

                    // Check that `linspace(..).rev()` yields evenly spaced numbers
                    #[quickcheck]
                    fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                        use test::IsClose;

                        enforce!{
                            start <= end,
                        }

                        let v = ::linspace(start, end, n).rev().collect::<Vec<_>>();
                        let mut spaces = v.windows(2).map(|w| w[1] - w[0]);

                        test!(match spaces.next() {
                            None => true,
                            Some(first) => spaces.all(|space| space.is_close(first))
                        })
                    }

                    // Check that `linspace(..).rev()` produces a monotonically decreasing sequence
                    #[quickcheck]
                    fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce!{
                            start <= end,
                        }

                        let v = ::linspace(start, end, n).rev().collect::<Vec<_>>();

                        test!(v.windows(2).all(|w| w[1] <= w[0]))
                    }

                    // Check that `linspace(_, _, n).rev()` yields exactly `n` numbers
                    #[quickcheck]
                    fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce!{
                            start <= end,
                        }

                        test!(::linspace(start, end, n).rev().count() == n)
                    }
                }

                // Check that `linspace(..)` yields evenly spaced numbers
                #[quickcheck]
                fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                    use test::IsClose;

                    enforce!{
                        start <= end,
                    }

                    let v = ::linspace(start, end, n).collect::<Vec<_>>();
                    let mut spaces = v.windows(2).map(|w| w[1] - w[0]);

                    test!(match spaces.next() {
                        None => true,
                        Some(first) => spaces.all(|space| space.is_close(first))
                    })
                }

                // Check that `linspace(..)` produces a monotonic increasing sequence
                #[quickcheck]
                fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce!{
                        start <= end,
                    }

                    let v = ::linspace(start, end, n).collect::<Vec<_>>();

                    test!(v.windows(2).all(|w| w[1] >= w[0]))
                }

                // Check that `linspace(_, _, n)` yields exactly `n` numbers
                #[quickcheck]
                fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce!{
                        start <= end,
                    }

                    test!(::linspace(start, end, n).count() == n)
                }
            }

            mod logspace {
                use quickcheck::TestResult;

                mod rev {
                    use quickcheck::TestResult;

                    // Check that `logspace(..).rev()` yields evenly spaced numbers
                    #[quickcheck]
                    fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                        use test::IsClose;

                        enforce!{
                            start > 0.,
                            start <= end,
                        }

                        let v = ::logspace(start, end, n).rev().collect::<Vec<_>>();
                        let mut spaces = v.windows(2).map(|w| {
                            use std::num::Float;

                            w[1].ln() - w[0].ln()
                        });

                        test!(match spaces.next() {
                            None => true,
                            Some(first) => spaces.all(|space| space.is_close(first))
                        })
                    }

                    // Check that `logspace(..).rev()` produces a monotonically decreasing sequence
                    #[quickcheck]
                    fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce!{
                            start > 0.,
                            start <= end,
                        }

                        let v = ::logspace(start, end, n).rev().collect::<Vec<_>>();

                        test!(v.windows(2).all(|w| w[1] <= w[0]))

                    }

                    // Check that `logspace(_, _, n).rev()` yields exactly `n` numbers
                    #[quickcheck]
                    fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce!{
                            start > 0.,
                            start <= end,
                        }

                        test!(::logspace(start, end, n).count() == n)
                    }
                }

                // Check that `logspace(..)` yields evenly spaced numbers
                #[quickcheck]
                fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                    use test::IsClose;

                    enforce!{
                        start > 0.,
                        start <= end,
                    }

                    let v = ::logspace(start, end, n).collect::<Vec<_>>();
                    let mut spaces = v.windows(2).map(|w| {
                        use std::num::Float;

                        w[1].ln() - w[0].ln()
                    });

                    test!(match spaces.next() {
                        None => true,
                        Some(first) => spaces.all(|space| space.is_close(first))
                    })
                }

                // Check that `logspace(..)` produces a monotonically increasing sequence
                #[quickcheck]
                fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce!{
                        start > 0.,
                        start <= end,
                    }

                    let v = ::logspace(start, end, n).collect::<Vec<_>>();

                    test!(v.windows(2).all(|w| w[1] >= w[0]))
                }

                // Check that `logspace(_, _, n)` yields exactly `n` numbers
                #[quickcheck]
                fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce!{
                        start > 0.,
                        start <= end,
                    }

                    test!(::logspace(start, end, n).count() == n)
                }
            }
        })+
    }
}

float!(f32, f64)
<commit_msg>fix(lib): adapt to changes in macro parsing<commit_after>macro_rules! enforce {
    ($($e:expr),+,) => {
        if $(!$e)||+ {
            return TestResult::discard();
        }
    }
}

macro_rules! test {
    ($e:expr) => {
        TestResult::from_bool($e)
    }
}

trait IsClose {
    fn is_close(self, rhs: Self) -> bool;
}

macro_rules! float {
    ($($ty:ident),+) => {$(
        mod $ty {
            impl ::test::IsClose for $ty {
                fn is_close(self, rhs: $ty) -> bool {
                    use std::num::Float;

                    const TOL: $ty = 1e-3;

                    (self - rhs).abs() < TOL
                }
            }

            mod linspace {
                use quickcheck::TestResult;

                mod rev {
                    use quickcheck::TestResult;

                    // Check that `linspace(..).rev()` yields evenly spaced numbers
                    #[quickcheck]
                    fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                        use test::IsClose;

                        enforce! {
                            start <= end,
                        }

                        let v = ::linspace(start, end, n).rev().collect::<Vec<_>>();
                        let mut spaces = v.windows(2).map(|w| w[1] - w[0]);

                        test!(match spaces.next() {
                            None => true,
                            Some(first) => spaces.all(|space| space.is_close(first))
                        })
                    }

                    // Check that `linspace(..).rev()` produces a monotonically decreasing sequence
                    #[quickcheck]
                    fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce! {
                            start <= end,
                        }

                        let v = ::linspace(start, end, n).rev().collect::<Vec<_>>();

                        test!(v.windows(2).all(|w| w[1] <= w[0]))
                    }

                    // Check that `linspace(_, _, n).rev()` yields exactly `n` numbers
                    #[quickcheck]
                    fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce! {
                            start <= end,
                        }

                        test!(::linspace(start, end, n).rev().count() == n)
                    }
                }

                // Check that `linspace(..)` yields evenly spaced numbers
                #[quickcheck]
                fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                    use test::IsClose;

                    enforce! {
                        start <= end,
                    }

                    let v = ::linspace(start, end, n).collect::<Vec<_>>();
                    let mut spaces = v.windows(2).map(|w| w[1] - w[0]);

                    test!(match spaces.next() {
                        None => true,
                        Some(first) => spaces.all(|space| space.is_close(first))
                    })
                }

                // Check that `linspace(..)` produces a monotonic increasing sequence
                #[quickcheck]
                fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce! {
                        start <= end,
                    }

                    let v = ::linspace(start, end, n).collect::<Vec<_>>();

                    test!(v.windows(2).all(|w| w[1] >= w[0]))
                }

                // Check that `linspace(_, _, n)` yields exactly `n` numbers
                #[quickcheck]
                fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce! {
                        start <= end,
                    }

                    test!(::linspace(start, end, n).count() == n)
                }
            }

            mod logspace {
                use quickcheck::TestResult;

                mod rev {
                    use quickcheck::TestResult;

                    // Check that `logspace(..).rev()` yields evenly spaced numbers
                    #[quickcheck]
                    fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                        use test::IsClose;

                        enforce! {
                            start > 0.,
                            start <= end,
                        }

                        let v = ::logspace(start, end, n).rev().collect::<Vec<_>>();
                        let mut spaces = v.windows(2).map(|w| {
                            use std::num::Float;

                            w[1].ln() - w[0].ln()
                        });

                        test!(match spaces.next() {
                            None => true,
                            Some(first) => spaces.all(|space| space.is_close(first))
                        })
                    }

                    // Check that `logspace(..).rev()` produces a monotonically decreasing sequence
                    #[quickcheck]
                    fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce! {
                            start > 0.,
                            start <= end,
                        }

                        let v = ::logspace(start, end, n).rev().collect::<Vec<_>>();

                        test!(v.windows(2).all(|w| w[1] <= w[0]))

                    }

                    // Check that `logspace(_, _, n).rev()` yields exactly `n` numbers
                    #[quickcheck]
                    fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                        enforce! {
                            start > 0.,
                            start <= end,
                        }

                        test!(::logspace(start, end, n).count() == n)
                    }
                }

                // Check that `logspace(..)` yields evenly spaced numbers
                #[quickcheck]
                fn evenly_spaced(start: $ty, end: $ty, n: uint) -> TestResult {
                    use test::IsClose;

                    enforce! {
                        start > 0.,
                        start <= end,
                    }

                    let v = ::logspace(start, end, n).collect::<Vec<_>>();
                    let mut spaces = v.windows(2).map(|w| {
                        use std::num::Float;

                        w[1].ln() - w[0].ln()
                    });

                    test!(match spaces.next() {
                        None => true,
                        Some(first) => spaces.all(|space| space.is_close(first))
                    })
                }

                // Check that `logspace(..)` produces a monotonically increasing sequence
                #[quickcheck]
                fn monotonic(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce! {
                        start > 0.,
                        start <= end,
                    }

                    let v = ::logspace(start, end, n).collect::<Vec<_>>();

                    test!(v.windows(2).all(|w| w[1] >= w[0]))
                }

                // Check that `logspace(_, _, n)` yields exactly `n` numbers
                #[quickcheck]
                fn size(start: $ty, end: $ty, n: uint) -> TestResult {
                    enforce! {
                        start > 0.,
                        start <= end,
                    }

                    test!(::logspace(start, end, n).count() == n)
                }
            }
        })+
    }
}

float!(f32, f64);
<|endoftext|>


<commit_before>use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

macro_rules! try_browser {
    ( $options: expr, $name:expr, $( $arg:expr ),+ ) => {
        for_matching_path($name, |pb| {
            let mut cmd = Command::new(pb);
            $(
                cmd.arg($arg);
            )+
            run_command(&mut cmd, !is_text_browser(&pb), $options)
        })
    }
}

/// Deal with opening of browsers on Linux and *BSD - currently supports only the default browser
///
/// The mechanism of opening the default browser is as follows:
/// 1. Attempt to use $BROWSER env var if available
/// 2. Attempt to use xdg-open
/// 3. Attempt to use window manager specific commands, like gnome-open, kde-open etc.
/// 4. Fallback to x-www-browser
#[inline]
pub fn open_browser_internal(browser: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
    match browser {
        Browser::Default => open_browser_default(url, options),
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "only default browser supported",
        )),
    }
}

/// Open the default browser.
///
/// [BrowserOptions::dry_run] is handled inside [run_command], as all execution paths eventually
/// rely on it to execute.
#[inline]
fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> {
    // we first try with the $BROWSER env
    try_with_browser_env(url, options)
        // allow for haiku's open specifically
        .or_else(|_| try_haiku(options, url))
        // then we try with xdg-open
        .or_else(|_| try_browser!(options, "xdg-open", url))
        // else do desktop specific stuff
        .or_else(|r| match guess_desktop_env() {
            "kde" => try_browser!(options, "kde-open", url)
                .or_else(|_| try_browser!(options, "kde-open5", url))
                .or_else(|_| try_browser!(options, "kfmclient", "newTab", url)),

            "gnome" => try_browser!(options, "gio", "open", url)
                .or_else(|_| try_browser!(options, "gvfs-open", url))
                .or_else(|_| try_browser!(options, "gnome-open", url)),

            "mate" => try_browser!(options, "gio", "open", url)
                .or_else(|_| try_browser!(options, "gvfs-open", url))
                .or_else(|_| try_browser!(options, "mate-open", url)),

            "xfce" => try_browser!(options, "exo-open", url)
                .or_else(|_| try_browser!(options, "gio", "open", url))
                .or_else(|_| try_browser!(options, "gvfs-open", url)),

            _ => Err(r),
        })
        // at the end, we'll try x-www-browser and return the result as is
        .or_else(|_| try_browser!(options, "x-www-browser", url))
        // if all above failed, map error to not found
        .map_err(|_| {
            Error::new(
                ErrorKind::NotFound,
                "No valid browsers detected. You can specify one in BROWSERS environment variable",
            )
        })
        // and convert a successful result into a ()
        .map(|_| ())
}

#[inline]
fn try_with_browser_env(url: &str, options: &BrowserOptions) -> Result<()> {
    // $BROWSER can contain ':' delimited options, each representing a potential browser command line
    for browser in std::env::var("BROWSER")
        .unwrap_or_else(|_| String::from(""))
        .split(':')
    {
        if !browser.is_empty() {
            // each browser command can have %s to represent URL, while %c needs to be replaced
            // with ':' and %% with '%'
            let cmdline = browser
                .replace("%s", url)
                .replace("%c", ":")
                .replace("%%", "%");
            let cmdarr: Vec<&str> = cmdline.split_ascii_whitespace().collect();
            let browser_cmd = cmdarr[0];
            let env_exit = for_matching_path(browser_cmd, |pb| {
                let mut cmd = Command::new(pb);
                for arg in cmdarr.iter().skip(1) {
                    cmd.arg(arg);
                }
                if !browser.contains("%s") {
                    // append the url as an argument only if it was not already set via %s
                    cmd.arg(url);
                }
                run_command(&mut cmd, !is_text_browser(pb), options)
            });
            if env_exit.is_ok() {
                return Ok(());
            }
        }
    }
    Err(Error::new(
        ErrorKind::NotFound,
        "No valid browser configured in BROWSER environment variable",
    ))
}

/// Detect the desktop environment
#[inline]
fn guess_desktop_env() -> &'static str {
    let unknown = "unknown";
    let xcd: String = std::env::var("XDG_CURRENT_DESKTOP")
        .unwrap_or_else(|_| unknown.into())
        .to_ascii_lowercase();
    let dsession: String = std::env::var("DESKTOP_SESSION")
        .unwrap_or_else(|_| unknown.into())
        .to_ascii_lowercase();

    if xcd.contains("gnome") || xcd.contains("cinnamon") || dsession.contains("gnome") {
        // GNOME and its derivatives
        "gnome"
    } else if xcd.contains("kde")
        || std::env::var("KDE_FULL_SESSION").is_ok()
        || std::env::var("KDE_SESSION_VERSION").is_ok()
    {
        // KDE: https://userbase.kde.org/KDE_System_Administration/Environment_Variables#Automatically_Set_Variables
        "kde"
    } else if xcd.contains("mate") || dsession.contains("mate") {
        // We'll treat MATE as distinct from GNOME due to mate-open
        "mate"
    } else if xcd.contains("xfce") || dsession.contains("xfce") {
        // XFCE
        "xfce"
    } else {
        // All others
        unknown
    }
}

// Handle Haiku explicitly, as it uses an "open" command, similar to macos
// but on other Unixes, open ends up translating to shell open fd
#[inline]
fn try_haiku(options: &BrowserOptions, url: &str) -> Result<()> {
    if cfg!(target_os = "haiku") {
        try_browser!(options, "open", url).map(|_| ())
    } else {
        Err(Error::new(ErrorKind::NotFound, "Not on haiku"))
    }
}

/// Returns true if specified command refers to a known list of text browsers
#[inline]
fn is_text_browser(pb: &Path) -> bool {
    for browser in TEXT_BROWSERS.iter() {
        if pb.ends_with(&browser) {
            return true;
        }
    }
    false
}

#[inline]
fn for_matching_path<F>(name: &str, op: F) -> Result<()>
where
    F: FnOnce(&PathBuf) -> Result<()>,
{
    let err = Err(Error::new(ErrorKind::NotFound, "command not found"));

    // if the name already includes path separator, we should not try to do a PATH search on it
    // as it's likely an absolutely or relative name, so we treat it as such.
    if name.contains(std::path::MAIN_SEPARATOR) {
        let pb = std::path::PathBuf::from(name);
        if let Ok(metadata) = pb.metadata() {
            if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 {
                return op(&pb);
            }
        } else {
            return err;
        }
    } else {
        // search for this name inside PATH
        if let Ok(path) = std::env::var("PATH") {
            for entry in path.split(':') {
                let mut pb = std::path::PathBuf::from(entry);
                pb.push(name);
                if let Ok(metadata) = pb.metadata() {
                    if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 {
                        return op(&pb);
                    }
                }
            }
        }
    }
    // return the not found err, if we didn't find anything above
    err
}

/// Run the specified command in foreground/background
#[inline]
fn run_command(cmd: &mut Command, background: bool, options: &BrowserOptions) -> Result<()> {
    // if dry_run, we return a true, as executable existence check has
    // already been done
    if options.dry_run {
        return Ok(());
    }

    if background {
        // if we're in background, set stdin/stdout to null and spawn a child, as we're
        // not supposed to have any interaction.
        if options.suppress_output {
            cmd.stdin(Stdio::null())
                .stdout(Stdio::null())
                .stderr(Stdio::null())
        } else {
            cmd
        }
        .spawn()
        .map(|_| ())
    } else {
        // if we're in foreground, use status() instead of spawn(), as we'd like to wait
        // till completion.
        // We also specifically don't supress anything here, because we're running here
        // most likely because of a text browser
        cmd.status().and_then(|status| {
            if status.success() {
                Ok(())
            } else {
                Err(Error::new(
                    ErrorKind::Other,
                    "command present but exited unsuccessfully",
                ))
            }
        })
    }
}

static TEXT_BROWSERS: [&str; 9] = [
    "lynx", "links", "links2", "elinks", "w3m", "eww", "netrik", "retawq", "curl",
];
<commit_msg>fix lint warning on nightly toolchain<commit_after>use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

macro_rules! try_browser {
    ( $options: expr, $name:expr, $( $arg:expr ),+ ) => {
        for_matching_path($name, |pb| {
            let mut cmd = Command::new(pb);
            $(
                cmd.arg($arg);
            )+
            run_command(&mut cmd, !is_text_browser(&pb), $options)
        })
    }
}

/// Deal with opening of browsers on Linux and *BSD - currently supports only the default browser
///
/// The mechanism of opening the default browser is as follows:
/// 1. Attempt to use $BROWSER env var if available
/// 2. Attempt to use xdg-open
/// 3. Attempt to use window manager specific commands, like gnome-open, kde-open etc.
/// 4. Fallback to x-www-browser
#[inline]
pub fn open_browser_internal(browser: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
    match browser {
        Browser::Default => open_browser_default(url, options),
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "only default browser supported",
        )),
    }
}

/// Open the default browser.
///
/// [BrowserOptions::dry_run] is handled inside [run_command], as all execution paths eventually
/// rely on it to execute.
#[inline]
fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> {
    // we first try with the $BROWSER env
    try_with_browser_env(url, options)
        // allow for haiku's open specifically
        .or_else(|_| try_haiku(options, url))
        // then we try with xdg-open
        .or_else(|_| try_browser!(options, "xdg-open", url))
        // else do desktop specific stuff
        .or_else(|r| match guess_desktop_env() {
            "kde" => try_browser!(options, "kde-open", url)
                .or_else(|_| try_browser!(options, "kde-open5", url))
                .or_else(|_| try_browser!(options, "kfmclient", "newTab", url)),

            "gnome" => try_browser!(options, "gio", "open", url)
                .or_else(|_| try_browser!(options, "gvfs-open", url))
                .or_else(|_| try_browser!(options, "gnome-open", url)),

            "mate" => try_browser!(options, "gio", "open", url)
                .or_else(|_| try_browser!(options, "gvfs-open", url))
                .or_else(|_| try_browser!(options, "mate-open", url)),

            "xfce" => try_browser!(options, "exo-open", url)
                .or_else(|_| try_browser!(options, "gio", "open", url))
                .or_else(|_| try_browser!(options, "gvfs-open", url)),

            _ => Err(r),
        })
        // at the end, we'll try x-www-browser and return the result as is
        .or_else(|_| try_browser!(options, "x-www-browser", url))
        // if all above failed, map error to not found
        .map_err(|_| {
            Error::new(
                ErrorKind::NotFound,
                "No valid browsers detected. You can specify one in BROWSERS environment variable",
            )
        })
        // and convert a successful result into a ()
        .map(|_| ())
}

#[inline]
fn try_with_browser_env(url: &str, options: &BrowserOptions) -> Result<()> {
    // $BROWSER can contain ':' delimited options, each representing a potential browser command line
    for browser in std::env::var("BROWSER")
        .unwrap_or_else(|_| String::from(""))
        .split(':')
    {
        if !browser.is_empty() {
            // each browser command can have %s to represent URL, while %c needs to be replaced
            // with ':' and %% with '%'
            let cmdline = browser
                .replace("%s", url)
                .replace("%c", ":")
                .replace("%%", "%");
            let cmdarr: Vec<&str> = cmdline.split_ascii_whitespace().collect();
            let browser_cmd = cmdarr[0];
            let env_exit = for_matching_path(browser_cmd, |pb| {
                let mut cmd = Command::new(pb);
                for arg in cmdarr.iter().skip(1) {
                    cmd.arg(arg);
                }
                if !browser.contains("%s") {
                    // append the url as an argument only if it was not already set via %s
                    cmd.arg(url);
                }
                run_command(&mut cmd, !is_text_browser(pb), options)
            });
            if env_exit.is_ok() {
                return Ok(());
            }
        }
    }
    Err(Error::new(
        ErrorKind::NotFound,
        "No valid browser configured in BROWSER environment variable",
    ))
}

/// Detect the desktop environment
#[inline]
fn guess_desktop_env() -> &'static str {
    let unknown = "unknown";
    let xcd: String = std::env::var("XDG_CURRENT_DESKTOP")
        .unwrap_or_else(|_| unknown.into())
        .to_ascii_lowercase();
    let dsession: String = std::env::var("DESKTOP_SESSION")
        .unwrap_or_else(|_| unknown.into())
        .to_ascii_lowercase();

    if xcd.contains("gnome") || xcd.contains("cinnamon") || dsession.contains("gnome") {
        // GNOME and its derivatives
        "gnome"
    } else if xcd.contains("kde")
        || std::env::var("KDE_FULL_SESSION").is_ok()
        || std::env::var("KDE_SESSION_VERSION").is_ok()
    {
        // KDE: https://userbase.kde.org/KDE_System_Administration/Environment_Variables#Automatically_Set_Variables
        "kde"
    } else if xcd.contains("mate") || dsession.contains("mate") {
        // We'll treat MATE as distinct from GNOME due to mate-open
        "mate"
    } else if xcd.contains("xfce") || dsession.contains("xfce") {
        // XFCE
        "xfce"
    } else {
        // All others
        unknown
    }
}

// Handle Haiku explicitly, as it uses an "open" command, similar to macos
// but on other Unixes, open ends up translating to shell open fd
#[inline]
fn try_haiku(options: &BrowserOptions, url: &str) -> Result<()> {
    if cfg!(target_os = "haiku") {
        try_browser!(options, "open", url).map(|_| ())
    } else {
        Err(Error::new(ErrorKind::NotFound, "Not on haiku"))
    }
}

/// Returns true if specified command refers to a known list of text browsers
#[inline]
fn is_text_browser(pb: &Path) -> bool {
    for browser in TEXT_BROWSERS.iter() {
        if pb.ends_with(browser) {
            return true;
        }
    }
    false
}

#[inline]
fn for_matching_path<F>(name: &str, op: F) -> Result<()>
where
    F: FnOnce(&PathBuf) -> Result<()>,
{
    let err = Err(Error::new(ErrorKind::NotFound, "command not found"));

    // if the name already includes path separator, we should not try to do a PATH search on it
    // as it's likely an absolutely or relative name, so we treat it as such.
    if name.contains(std::path::MAIN_SEPARATOR) {
        let pb = std::path::PathBuf::from(name);
        if let Ok(metadata) = pb.metadata() {
            if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 {
                return op(&pb);
            }
        } else {
            return err;
        }
    } else {
        // search for this name inside PATH
        if let Ok(path) = std::env::var("PATH") {
            for entry in path.split(':') {
                let mut pb = std::path::PathBuf::from(entry);
                pb.push(name);
                if let Ok(metadata) = pb.metadata() {
                    if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 {
                        return op(&pb);
                    }
                }
            }
        }
    }
    // return the not found err, if we didn't find anything above
    err
}

/// Run the specified command in foreground/background
#[inline]
fn run_command(cmd: &mut Command, background: bool, options: &BrowserOptions) -> Result<()> {
    // if dry_run, we return a true, as executable existence check has
    // already been done
    if options.dry_run {
        return Ok(());
    }

    if background {
        // if we're in background, set stdin/stdout to null and spawn a child, as we're
        // not supposed to have any interaction.
        if options.suppress_output {
            cmd.stdin(Stdio::null())
                .stdout(Stdio::null())
                .stderr(Stdio::null())
        } else {
            cmd
        }
        .spawn()
        .map(|_| ())
    } else {
        // if we're in foreground, use status() instead of spawn(), as we'd like to wait
        // till completion.
        // We also specifically don't supress anything here, because we're running here
        // most likely because of a text browser
        cmd.status().and_then(|status| {
            if status.success() {
                Ok(())
            } else {
                Err(Error::new(
                    ErrorKind::Other,
                    "command present but exited unsuccessfully",
                ))
            }
        })
    }
}

static TEXT_BROWSERS: [&str; 9] = [
    "lynx", "links", "links2", "elinks", "w3m", "eww", "netrik", "retawq", "curl",
];
<|endoftext|>


<commit_before>//! Unix-specific types for signal handling.
//!
//! This module is only defined on Unix platforms and contains the primary
//! `Signal` type for receiving notifications of signals.

#![cfg(unix)]

pub extern crate libc;
extern crate mio;
extern crate mio_uds;

use std::cell::UnsafeCell;
use std::collections::HashSet;
use std::io::prelude::*;
use std::io;
use std::mem;
use std::os::unix::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, Once, ONCE_INIT};

use futures::future;
use futures::sync::mpsc::{Receiver, Sender, channel};
use futures::{Async, AsyncSink, Future};
use futures::{Sink, Stream, Poll};
use self::libc::c_int;
use self::mio::Poll as MioPoll;
use self::mio::unix::EventedFd;
use self::mio::{Evented, Token, Ready, PollOpt};
use self::mio_uds::UnixStream;
use tokio_core::io::IoFuture;
use tokio_core::reactor::{Handle, CoreId, PollEvented};

pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP};

// Number of different unix signals
const SIGNUM: usize = 32;

struct SignalInfo {
    pending: AtomicBool,
    // The ones interested in this signal
    recipients: Mutex<Vec<Sender<c_int>>>,

	init: Once,
    initialized: UnsafeCell<bool>,
    prev: UnsafeCell<libc::sigaction>,
}

struct Globals {
    sender: UnixStream,
    receiver: UnixStream,
    signals: [SignalInfo; SIGNUM],
    drivers: Mutex<HashSet<CoreId>>,
}

impl Default for SignalInfo {
    fn default() -> SignalInfo {
        SignalInfo {
            pending: AtomicBool::new(false),
            init: ONCE_INIT,
            initialized: UnsafeCell::new(false),
            recipients: Mutex::new(Vec::new()),
            prev: UnsafeCell::new(unsafe { mem::zeroed() }),
        }
    }
}

static mut GLOBALS: *mut Globals = 0 as *mut Globals;

fn globals() -> &'static Globals {
    static INIT: Once = ONCE_INIT;

    unsafe {
        INIT.call_once(|| {
            let (receiver, sender) = UnixStream::pair().unwrap();
            let globals = Globals {
                sender: sender,
                receiver: receiver,
                signals: Default::default(),
                drivers: Mutex::new(HashSet::new()),
            };
            GLOBALS = Box::into_raw(Box::new(globals));
        });
        &*GLOBALS
    }
}

// Flag the relevant signal and wake up through a self-pipe
extern fn handler(signum: c_int,
                  info: *mut libc::siginfo_t,
                  ptr: *mut libc::c_void) {
	type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void);
	type FnHandler = extern fn(c_int);
    unsafe {
        let slot = match (*GLOBALS).signals.get(signum as usize) {
            Some(slot) => slot,
            None => return,
        };
        slot.pending.store(true, Ordering::SeqCst);

        // Send a wakeup, ignore any errors (anything reasonably possible is
        // full pipe and then it will wake up anyway).
        drop((*GLOBALS).sender.write(&[1]));

        let fnptr = (*slot.prev.get()).sa_sigaction;
        if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN {
            return
        }
        if (*slot.prev.get()).sa_flags & libc::SA_SIGINFO == 0 {
            let action = mem::transmute::<usize, FnHandler>(fnptr);
            action(signum)
        } else {
            let action = mem::transmute::<usize, FnSigaction>(fnptr);
            action(signum, info, ptr)
        }
    }
}

// Make sure we listen to the given signal and provide the recipient end of the
// self-pipe
fn signal_enable(signal: c_int) -> io::Result<()> {
    let siginfo = &globals().signals[signal as usize];
	unsafe {
        let mut err = None;
        siginfo.init.call_once(|| {
            let mut new: libc::sigaction = mem::zeroed();
            new.sa_sigaction = handler as usize;
            new.sa_flags = libc::SA_RESTART |
                            libc::SA_SIGINFO |
                            libc::SA_NOCLDSTOP;
            if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 {
                err = Some(io::Error::last_os_error());
            } else {
                *siginfo.initialized.get() = true;
            }
        });
        if let Some(err) = err {
            return Err(err)
        }
        if *siginfo.initialized.get() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other,
                               "failed to register signal handler"))
        }
	}
}

struct EventedReceiver;

impl Evented for EventedReceiver {
    fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).register(poll, token, events, opts)
    }
    fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).reregister(poll, token, events, opts)
    }
    fn deregister(&self, poll: &MioPoll) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).deregister(poll)
    }
}

struct Driver {
    id: CoreId,
    wakeup: PollEvented<EventedReceiver>,
}

impl Future for Driver {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<(), ()> {
        // Drain the data from the pipe and maintain interest in getting more
        let any_wakeup = self.drain();
        if any_wakeup {
            self.broadcast();
        }
        // This task just lives until the end of the event loop
        Ok(Async::NotReady)
    }
}

impl Drop for Driver {
    fn drop(&mut self) {
        let mut drivers = globals().drivers.lock().unwrap();
        drivers.remove(&self.id);
    }
}

impl Driver {
    fn new(handle: &Handle) -> io::Result<Driver> {
        Ok(Driver {
            id: handle.id(),
            wakeup: try!(PollEvented::new(EventedReceiver, handle)),
        })
    }

    // Drain all data in the pipe and maintain an interest in read-ready
    fn drain(&self) -> bool {
        // Inform tokio we're interested in reading. It also hints on
        // if we may be readable.
        if let Async::NotReady = self.wakeup.poll_read() {
            return false;
        }
        // Read all available data (until EAGAIN)
        let mut received = false;
        loop {
            match (&globals().receiver).read(&mut [0; 128]) {
                Ok(0) => panic!("EOF on self-pipe"),
                Ok(_) => received = true,
                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
                Err(e) => panic!("Bad read on self-pipe: {}", e),
            }
        }

        // If we got here, it's because we got EAGAIN above. Ask for more data.
        self.wakeup.need_read();

        received
    }
    // Go through all the signals and broadcast everything
    fn broadcast(&self) {
        for (sig, slot) in globals().signals.iter().enumerate() {
            // Any signal of this kind arrived since we checked last?
            if !slot.pending.swap(false, Ordering::SeqCst) {
                continue
            }

            let signum = sig as c_int;
            let mut recipients = slot.recipients.lock().unwrap();

            // Notify all waiters on this signal that the signal has been
            // received. If we can't push a message into the queue then we don't
            // worry about it as everything is coalesced anyway.
            for i in (0..recipients.len()).rev() {
                // TODO: This thing probably generates unnecessary wakups of
                //       this task. But let's optimise it later on, when we
                //       know this works.
                match recipients[i].start_send(signum) {
                    Ok(AsyncSink::Ready) => {}
                    Ok(AsyncSink::NotReady(_)) => {}
                    Err(_) => { recipients.swap_remove(i); }
                }
            }
        }
    }
}

// TODO: Go through the docs, they are a copy-paste from the previous version
/// An implementation of `Stream` for receiving a particular type of signal.
///
/// This structure implements the `Stream` trait and represents notifications
/// of the current process receiving a particular signal. The signal being
/// listened for is passed to `Signal::new`, and the same signal number is then
/// yielded as each element for the stream.
///
/// In general signal handling on Unix is a pretty tricky topic, and this
/// structure is no exception! There are some important limitations to keep in
/// mind when using `Signal` streams:
///
/// * While multiple event loops are supported, the *first* event loop to
///   register a signal handler is required to be active to ensure that signals
///   for other event loops are delivered. In other words, once an event loop
///   registers a signal, it's best to keep it around and running. This is
///   normally just a problem for tests, and the "workaround" is to spawn a
///   thread in the background at the beginning of the test suite which is
///   running an event loop (and listening for a signal).
///
/// * Signals handling in Unix already necessitates coalescing signals
///   together sometimes. This `Signal` stream is also no exception here in
///   that it will also coalesce signals. That is, even if the signal handler
///   for this process runs multiple times, the `Signal` stream may only return
///   one signal notification. Specifically, before `poll` is called, all
///   signal notifications are coalesced into one item returned from `poll`.
///   Once `poll` has been called, however, a further signal is guaranteed to
///   be yielded as an item.
///
/// * Signal handling in general is relatively inefficient. Although some
///   improvements are possible in this crate, it's recommended to not plan on
///   having millions of signal channels open.
///
/// * Currently the "driver task" to process incoming signals never exits.
///
/// If you've got any questions about this feel free to open an issue on the
/// repo, though, as I'd love to chat about this! In other words, I'd love to
/// alleviate some of these limitations if possible!
pub struct Signal(Receiver<c_int>);

impl Signal {
    // TODO: Revisit the docs, they are from the previous version
    /// Creates a new stream which will receive notifications when the current
    /// process receives the signal `signum`.
    ///
    /// This function will create a new stream which may be based on the
    /// event loop handle provided. This function returns a future which will
    /// then resolve to the signal stream, if successful.
    ///
    /// The `Signal` stream is an infinite stream which will receive
    /// notifications whenever a signal is received. More documentation can be
    /// found on `Signal` itself, but to reiterate:
    ///
    /// * Signals may be coalesced beyond what the kernel already does.
    /// * While multiple event loops are supported, the first event loop to
    ///   register a signal handler must be active to deliver signal
    ///   notifications
    /// * Once a signal handle is registered with the process the underlying
    ///   libc signal handler is never unregistered.
    ///
    /// A `Signal` stream can be created for a particular signal number
    /// multiple times. When a signal is received then all the associated
    /// channels will receive the signal notification.
    pub fn new(signal: c_int, handle: &Handle) -> IoFuture<Signal> {
        let result = (|| {
            // Turn the signal delivery on once we are ready for it
            try!(signal_enable(signal));

            // Ensure there's a driver for our associated event loop processing
            // signals.
            let id = handle.id();
            let mut drivers = globals().drivers.lock().unwrap();
            if !drivers.contains(&id) {
                handle.spawn(try!(Driver::new(handle)));
                drivers.insert(id);
            }
            drop(drivers);

            // One wakeup in a queue is enough, no need for us to buffer up any
            // more.
            let (tx, rx) = channel(1);
            globals().signals[signal as usize].recipients.lock().unwrap().push(tx);
            Ok(Signal(rx))
        })();

        future::result(result).boxed()
    }
}

impl Stream for Signal {
    type Item = c_int;
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Option<c_int>, io::Error> {
        // It seems the channel doesn't generate any errors anyway
        self.0.poll().map_err(|_| io::Error::new(io::ErrorKind::Other, "Unknown futures::sync::mpsc error"))
    }
}

// TODO: Drop for Signal and remove the other end proactively?
<commit_msg>signal: Touch up more impls and comments<commit_after>//! Unix-specific types for signal handling.
//!
//! This module is only defined on Unix platforms and contains the primary
//! `Signal` type for receiving notifications of signals.

#![cfg(unix)]

pub extern crate libc;
extern crate mio;
extern crate mio_uds;

use std::cell::UnsafeCell;
use std::collections::HashSet;
use std::io::prelude::*;
use std::io;
use std::mem;
use std::os::unix::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, Once, ONCE_INIT};

use futures::future;
use futures::sync::mpsc::{Receiver, Sender, channel};
use futures::{Async, AsyncSink, Future};
use futures::{Sink, Stream, Poll};
use self::libc::c_int;
use self::mio::Poll as MioPoll;
use self::mio::unix::EventedFd;
use self::mio::{Evented, Token, Ready, PollOpt};
use self::mio_uds::UnixStream;
use tokio_core::io::IoFuture;
use tokio_core::reactor::{Handle, CoreId, PollEvented};

pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP};

// Number of different unix signals
const SIGNUM: usize = 32;

struct SignalInfo {
    pending: AtomicBool,
    // The ones interested in this signal
    recipients: Mutex<Vec<Sender<c_int>>>,

	init: Once,
    initialized: UnsafeCell<bool>,
    prev: UnsafeCell<libc::sigaction>,
}

struct Globals {
    sender: UnixStream,
    receiver: UnixStream,
    signals: [SignalInfo; SIGNUM],
    drivers: Mutex<HashSet<CoreId>>,
}

impl Default for SignalInfo {
    fn default() -> SignalInfo {
        SignalInfo {
            pending: AtomicBool::new(false),
            init: ONCE_INIT,
            initialized: UnsafeCell::new(false),
            recipients: Mutex::new(Vec::new()),
            prev: UnsafeCell::new(unsafe { mem::zeroed() }),
        }
    }
}

static mut GLOBALS: *mut Globals = 0 as *mut Globals;

fn globals() -> &'static Globals {
    static INIT: Once = ONCE_INIT;

    unsafe {
        INIT.call_once(|| {
            let (receiver, sender) = UnixStream::pair().unwrap();
            let globals = Globals {
                sender: sender,
                receiver: receiver,
                signals: Default::default(),
                drivers: Mutex::new(HashSet::new()),
            };
            GLOBALS = Box::into_raw(Box::new(globals));
        });
        &*GLOBALS
    }
}

/// Our global signal handler for all signals registered by this module.
///
/// The purpose of this signal handler is to primarily:
///
/// 1. Flag that our specific signal was received (e.g. store an atomic flag)
/// 2. Wake up driver tasks by writing a byte to a pipe
///
/// Those two operations shoudl both be async-signal safe. After that's done we
/// just try to call a previous signal handler, if any, to be "good denizens of
/// the internet"
extern fn handler(signum: c_int,
                  info: *mut libc::siginfo_t,
                  ptr: *mut libc::c_void) {
	type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void);
	type FnHandler = extern fn(c_int);
    unsafe {
        let slot = match (*GLOBALS).signals.get(signum as usize) {
            Some(slot) => slot,
            None => return,
        };
        slot.pending.store(true, Ordering::SeqCst);

        // Send a wakeup, ignore any errors (anything reasonably possible is
        // full pipe and then it will wake up anyway).
        drop((*GLOBALS).sender.write(&[1]));

        let fnptr = (*slot.prev.get()).sa_sigaction;
        if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN {
            return
        }
        if (*slot.prev.get()).sa_flags & libc::SA_SIGINFO == 0 {
            let action = mem::transmute::<usize, FnHandler>(fnptr);
            action(signum)
        } else {
            let action = mem::transmute::<usize, FnSigaction>(fnptr);
            action(signum, info, ptr)
        }
    }
}

/// Enable this module to receive signal notifications for the `signal`
/// provided.
///
/// This will register the signal handler if it hasn't already been registered,
/// returning any error along the way if that fails.
fn signal_enable(signal: c_int) -> io::Result<()> {
    let siginfo = match globals().signals.get(signal as usize) {
        Some(slot) => slot,
        None => {
            return Err(io::Error::new(io::ErrorKind::Other, "signal too large"))
        }
    };
	unsafe {
        let mut err = None;
        siginfo.init.call_once(|| {
            let mut new: libc::sigaction = mem::zeroed();
            new.sa_sigaction = handler as usize;
            new.sa_flags = libc::SA_RESTART |
                            libc::SA_SIGINFO |
                            libc::SA_NOCLDSTOP;
            if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 {
                err = Some(io::Error::last_os_error());
            } else {
                *siginfo.initialized.get() = true;
            }
        });
        if let Some(err) = err {
            return Err(err)
        }
        if *siginfo.initialized.get() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other,
                               "failed to register signal handler"))
        }
	}
}

/// A helper struct to register our global receiving end of the signal pipe on
/// multiple event loops.
///
/// This structure represents registering the receiving end on all event loops,
/// and uses `EventedFd` in mio to do so. It's stored in each driver task and is
/// used to read data and register interest in new signals coming in.
struct EventedReceiver;

impl Evented for EventedReceiver {
    fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).register(poll, token, events, opts)
    }
    fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).reregister(poll, token, events, opts)
    }
    fn deregister(&self, poll: &MioPoll) -> io::Result<()> {
        let fd = globals().receiver.as_raw_fd();
        EventedFd(&fd).deregister(poll)
    }
}

impl Read for EventedReceiver {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&globals().receiver).read(buf)
    }
}

struct Driver {
    id: CoreId,
    wakeup: PollEvented<EventedReceiver>,
}

impl Future for Driver {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<(), ()> {
        // Drain the data from the pipe and maintain interest in getting more
        let any_wakeup = self.drain();
        if any_wakeup {
            self.broadcast();
        }
        // This task just lives until the end of the event loop
        Ok(Async::NotReady)
    }
}

impl Drop for Driver {
    fn drop(&mut self) {
        let mut drivers = globals().drivers.lock().unwrap();
        drivers.remove(&self.id);
    }
}

impl Driver {
    fn new(handle: &Handle) -> io::Result<Driver> {
        Ok(Driver {
            id: handle.id(),
            wakeup: try!(PollEvented::new(EventedReceiver, handle)),
        })
    }

    /// Drain all data in the global receiver, returning whether data was to be
    /// had.
    ///
    /// If this function returns `true` then some signal has been received since
    /// we last checked, otherwise `false` indicates that no signal has been
    /// received.
    fn drain(&mut self) -> bool {
        let mut received = false;
        loop {
            match self.wakeup.read(&mut [0; 128]) {
                Ok(0) => panic!("EOF on self-pipe"),
                Ok(_) => received = true,
                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
                Err(e) => panic!("Bad read on self-pipe: {}", e),
            }
        }
        received
    }

    /// Go through all the signals and broadcast everything.
    ///
    /// Driver tasks wake up for *any* signal and simply process all globally
    /// registered signal streams, so each task is sort of cooperatively working
    /// for all the rest as well.
    fn broadcast(&self) {
        for (sig, slot) in globals().signals.iter().enumerate() {
            // Any signal of this kind arrived since we checked last?
            if !slot.pending.swap(false, Ordering::SeqCst) {
                continue
            }

            let signum = sig as c_int;
            let mut recipients = slot.recipients.lock().unwrap();

            // Notify all waiters on this signal that the signal has been
            // received. If we can't push a message into the queue then we don't
            // worry about it as everything is coalesced anyway. If the channel
            // has gone away then we can remove that slot.
            for i in (0..recipients.len()).rev() {
                // TODO: This thing probably generates unnecessary wakups of
                //       this task. But let's optimise it later on, when we
                //       know this works.
                match recipients[i].start_send(signum) {
                    Ok(AsyncSink::Ready) => {}
                    Ok(AsyncSink::NotReady(_)) => {}
                    Err(_) => { recipients.swap_remove(i); }
                }
            }
        }
    }
}

/// An implementation of `Stream` for receiving a particular type of signal.
///
/// This structure implements the `Stream` trait and represents notifications
/// of the current process receiving a particular signal. The signal being
/// listened for is passed to `Signal::new`, and the same signal number is then
/// yielded as each element for the stream.
///
/// In general signal handling on Unix is a pretty tricky topic, and this
/// structure is no exception! There are some important limitations to keep in
/// mind when using `Signal` streams:
///
/// * Signals handling in Unix already necessitates coalescing signals
///   together sometimes. This `Signal` stream is also no exception here in
///   that it will also coalesce signals. That is, even if the signal handler
///   for this process runs multiple times, the `Signal` stream may only return
///   one signal notification. Specifically, before `poll` is called, all
///   signal notifications are coalesced into one item returned from `poll`.
///   Once `poll` has been called, however, a further signal is guaranteed to
///   be yielded as an item.
///
///   Put another way, any element pulled off the returned stream corresponds to
///   *at least one* signal, but possibly more.
///
/// * Signal handling in general is relatively inefficient. Although some
///   improvements are possible in this crate, it's recommended to not plan on
///   having millions of signal channels open.
///
/// * Currently the "driver task" to process incoming signals never exits. This
///   driver task runs in the background of the event loop provided, and
///   in general you shouldn't need to worry about it.
///
/// If you've got any questions about this feel free to open an issue on the
/// repo, though, as I'd love to chat about this! In other words, I'd love to
/// alleviate some of these limitations if possible!
pub struct Signal(Receiver<c_int>);

impl Signal {
    /// Creates a new stream which will receive notifications when the current
    /// process receives the signal `signal`.
    ///
    /// This function will create a new stream which may be based on the
    /// event loop handle provided. This function returns a future which will
    /// then resolve to the signal stream, if successful.
    ///
    /// The `Signal` stream is an infinite stream which will receive
    /// notifications whenever a signal is received. More documentation can be
    /// found on `Signal` itself, but to reiterate:
    ///
    /// * Signals may be coalesced beyond what the kernel already does.
    /// * Once a signal handler is registered with the process the underlying
    ///   libc signal handler is never unregistered.
    ///
    /// A `Signal` stream can be created for a particular signal number
    /// multiple times. When a signal is received then all the associated
    /// channels will receive the signal notification.
    pub fn new(signal: c_int, handle: &Handle) -> IoFuture<Signal> {
        let result = (|| {
            // Turn the signal delivery on once we are ready for it
            try!(signal_enable(signal));

            // Ensure there's a driver for our associated event loop processing
            // signals.
            let id = handle.id();
            let mut drivers = globals().drivers.lock().unwrap();
            if !drivers.contains(&id) {
                handle.spawn(try!(Driver::new(handle)));
                drivers.insert(id);
            }
            drop(drivers);

            // One wakeup in a queue is enough, no need for us to buffer up any
            // more.
            let (tx, rx) = channel(1);
            globals().signals[signal as usize].recipients.lock().unwrap().push(tx);
            Ok(Signal(rx))
        })();

        future::result(result).boxed()
    }
}

impl Stream for Signal {
    type Item = c_int;
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Option<c_int>, io::Error> {
        // It seems the channel doesn't generate any errors anyway
        self.0.poll().map_err(|_| io::Error::new(io::ErrorKind::Other, "Unknown futures::sync::mpsc error"))
    }
}

// TODO: Drop for Signal and remove the other end proactively?
<|endoftext|>


<commit_before>use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{Local, TimeZone};
use chrono::Duration;

use nom::IResult::Done;
use nom;

/* For running git and html-tidy */
use std::process::Command;

use std::env;

/* For from::utf8 */
use std::str;

pub fn get_seconds() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs()
}

pub fn ts_to_date(timestamp: u64) -> String {
    Local
        .timestamp(timestamp as i64, 0)
        .format("%Y-%m-%d, %H:%M")
        .to_string()
}

pub fn sec_to_hms_string(seconds: u64) -> String {
    let hours = seconds / 3600;
    let minutes = (seconds - hours * 3600) / 60;
    let seconds = seconds - minutes * 60 - hours * 3600;
    match (hours, minutes, seconds) {
        (0, 0, 1) => format!("1 second"),
        (0, 0, s) => format!("{} seconds", s),
        (0, 1, _) => format!("1 minute"),
        (0, m, _) => format!("{} minutes", m),
        /* Range matching: slightly dubious feature here */
        (1, 0...4, _)   => format!("1 hour"),
        (h, 0...4, _)   => format!("{} hours", h),
        (h, 56...59, _) => format!("{} hours", h + 1),
        (h, m, _)       => format!("{} hours and {} minutes", h, m),
    }
}

/* For parsing time in HH:MM format. */
named!(duration_hhmm(&[u8]) -> Duration,
    do_parse!(
        hour: map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>
        tag!(":") >>
        min:  map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>
        /*tag!(":") >>
        sec: map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>*/
        (Duration::minutes(hour * 60 + min))
    )
);

pub fn parse_hhmm_to_seconds(timestr: &str) -> Option<u64> {
    match duration_hhmm(timestr.as_bytes()) {
        Done(_, out) => Some(out.num_seconds() as u64),
        _ => None,
    }
}

pub fn set_to_trk_dir() -> bool {
    let mut path = env::current_dir().unwrap();
    loop {
        path.push(".trk");
        if path.exists() {
            path.pop();
            env::set_current_dir(&path).is_ok();
            return true;
        } else {
            path.pop();
            if !path.pop() {
                println!("Fatal: not a .trk directory (or subdirectory of one).");
                return false;
            }
        }
    }
}

pub fn git_init_trk() -> bool {
    if !set_to_trk_dir() {
        println!("Could not initialise trk internal git repo!\
                 (couldn't find upper level .trk dir).");
        return false;
    }

    let mut path = env::current_dir().unwrap();
    path.push(".trk");
    if path.exists() {
        env::set_current_dir(&path).is_ok();
    } else {
        println!("Couldn't access .trk sub directory to initialise trk internal git repo.");
        return false;
    }
    let output = Command::new("git")
           .arg("init")
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git init!");
            return false;
        }
    }
    let output = Command::new("git")
           .arg("add")
           .arg("timesheet.json")
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git init!");
            return false;
        }
    }

    /* Reset current_dir to previous value */
    path.pop();
    env::set_current_dir(&path).is_ok();
    true
}

pub fn git_commit_trk(message: &str) -> bool {
    if !set_to_trk_dir() {
        println!("Could not commit to trk internal git repo!\
                 (couldn't find upper level .trk dir).");
        return false;
    }

    let mut p = env::current_dir().unwrap();
    p.push(".trk");
    if p.exists() {
        env::set_current_dir(&p).is_ok();
    } else {
        println!("Couldn't access .trk sub directory to initialise trk internal git repo.");
        return false;
    }
    let output = Command::new("git")
           .arg("commit")
           .arg("timesheet.json")
           .arg("-m")
           .arg(message)
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git commit!");
            return false;
        }
    }

    /* Reset current_dir to previous value */
    p.pop();
    env::set_current_dir(&p).is_ok();
    true
}
pub fn git_author() -> Option<String> {
    if let Ok(output) = Command::new("git")
           .arg("config")
           .arg("user.name")
           .output() {
        if output.status.success() {
            let output = String::from_utf8_lossy(&output.stdout);
            /* Remove trailing newline character */
            let mut output = output.to_string();
            output
                .pop()
                .expect("Empty name in git config? Not even a newline?!?");
            Some(output)
        } else {
            let output = String::from_utf8_lossy(&output.stderr);
            println!("git config user.name failed. {}", output);
            None
        }
    } else {
        None
    }
}

pub fn git_commit_message(hash: &str) -> Option<String> {
    if let Ok(output) = Command::new("git")
           .arg("log")
           .arg("--format=%B")
           .arg("-n")
           .arg("1")
           .arg(hash)
           .output() {
        if output.status.success() {
            let output = String::from_utf8_lossy(&output.stdout);
            Some(output.to_string())
        } else {
            let output = String::from_utf8_lossy(&output.stderr);
            println!("git log --format=%B -n 1 <hash> failed. {}", output);
            None
        }
    } else {
        None
    }
}

pub fn format_file(filename: &str) {
    if let Ok(_) = Command::new("tidy")
           .arg("--tidy-mark")
           .arg("no")
           .arg("-i")
           .arg("-m")
           .arg(filename)
           .output() {
    } else {
        println!("tidy-html not found!");
    }
}
<commit_msg>avoid pointles format!'s<commit_after>use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{Local, TimeZone};
use chrono::Duration;

use nom::IResult::Done;
use nom;

/* For running git and html-tidy */
use std::process::Command;

use std::env;

/* For from::utf8 */
use std::str;

pub fn get_seconds() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs()
}

pub fn ts_to_date(timestamp: u64) -> String {
    Local
        .timestamp(timestamp as i64, 0)
        .format("%Y-%m-%d, %H:%M")
        .to_string()
}

pub fn sec_to_hms_string(seconds: u64) -> String {
    let hours = seconds / 3600;
    let minutes = (seconds - hours * 3600) / 60;
    let seconds = seconds - minutes * 60 - hours * 3600;
    match (hours, minutes, seconds) {
        (0, 0, 1) => String::from("1 second"),
        (0, 0, s) => format!("{} seconds", s),
        (0, 1, _) => String::from("1 minute"),
        (0, m, _) => format!("{} minutes", m),
        /* Range matching: slightly dubious feature here */
        (1, 0...4, _)   => String::from("1 hour"),
        (h, 0...4, _)   => format!("{} hours", h),
        (h, 56...59, _) => format!("{} hours", h + 1),
        (h, m, _)       => format!("{} hours and {} minutes", h, m),
    }
}

/* For parsing time in HH:MM format. */
named!(duration_hhmm(&[u8]) -> Duration,
    do_parse!(
        hour: map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>
        tag!(":") >>
        min:  map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>
        /*tag!(":") >>
        sec: map_res!(map_res!(nom::digit, str::from_utf8), |s: &str| s.parse::<i64>()) >>*/
        (Duration::minutes(hour * 60 + min))
    )
);

pub fn parse_hhmm_to_seconds(timestr: &str) -> Option<u64> {
    match duration_hhmm(timestr.as_bytes()) {
        Done(_, out) => Some(out.num_seconds() as u64),
        _ => None,
    }
}

pub fn set_to_trk_dir() -> bool {
    let mut path = env::current_dir().unwrap();
    loop {
        path.push(".trk");
        if path.exists() {
            path.pop();
            env::set_current_dir(&path).is_ok();
            return true;
        } else {
            path.pop();
            if !path.pop() {
                println!("Fatal: not a .trk directory (or subdirectory of one).");
                return false;
            }
        }
    }
}

pub fn git_init_trk() -> bool {
    if !set_to_trk_dir() {
        println!("Could not initialise trk internal git repo!\
                 (couldn't find upper level .trk dir).");
        return false;
    }

    let mut path = env::current_dir().unwrap();
    path.push(".trk");
    if path.exists() {
        env::set_current_dir(&path).is_ok();
    } else {
        println!("Couldn't access .trk sub directory to initialise trk internal git repo.");
        return false;
    }
    let output = Command::new("git")
           .arg("init")
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git init!");
            return false;
        }
    }
    let output = Command::new("git")
           .arg("add")
           .arg("timesheet.json")
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git init!");
            return false;
        }
    }

    /* Reset current_dir to previous value */
    path.pop();
    env::set_current_dir(&path).is_ok();
    true
}

pub fn git_commit_trk(message: &str) -> bool {
    if !set_to_trk_dir() {
        println!("Could not commit to trk internal git repo!\
                 (couldn't find upper level .trk dir).");
        return false;
    }

    let mut p = env::current_dir().unwrap();
    p.push(".trk");
    if p.exists() {
        env::set_current_dir(&p).is_ok();
    } else {
        println!("Couldn't access .trk sub directory to initialise trk internal git repo.");
        return false;
    }
    let output = Command::new("git")
           .arg("commit")
           .arg("timesheet.json")
           .arg("-m")
           .arg(message)
           .output();
    match output {
        Ok(_) => {},
        Err(_) => {
            println!("Could not run git commit!");
            return false;
        }
    }

    /* Reset current_dir to previous value */
    p.pop();
    env::set_current_dir(&p).is_ok();
    true
}
pub fn git_author() -> Option<String> {
    if let Ok(output) = Command::new("git")
           .arg("config")
           .arg("user.name")
           .output() {
        if output.status.success() {
            let output = String::from_utf8_lossy(&output.stdout);
            /* Remove trailing newline character */
            let mut output = output.to_string();
            output
                .pop()
                .expect("Empty name in git config? Not even a newline?!?");
            Some(output)
        } else {
            let output = String::from_utf8_lossy(&output.stderr);
            println!("git config user.name failed. {}", output);
            None
        }
    } else {
        None
    }
}

pub fn git_commit_message(hash: &str) -> Option<String> {
    if let Ok(output) = Command::new("git")
           .arg("log")
           .arg("--format=%B")
           .arg("-n")
           .arg("1")
           .arg(hash)
           .output() {
        if output.status.success() {
            let output = String::from_utf8_lossy(&output.stdout);
            Some(output.to_string())
        } else {
            let output = String::from_utf8_lossy(&output.stderr);
            println!("git log --format=%B -n 1 <hash> failed. {}", output);
            None
        }
    } else {
        None
    }
}

pub fn format_file(filename: &str) {
    if let Ok(_) = Command::new("tidy")
           .arg("--tidy-mark")
           .arg("no")
           .arg("-i")
           .arg("-m")
           .arg(filename)
           .output() {
    } else {
        println!("tidy-html not found!");
    }
}
<|endoftext|>


<commit_before>use fnv::FnvHasher;
use std::collections;
use std::hash::BuildHasherDefault;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Range;
use std::ptr;
use std::slice;

pub type HashMap<K, V> = collections::HashMap<K, V, BuildHasherDefault<FnvHasher>>;
pub type HashSet<K> = collections::HashSet<K, BuildHasherDefault<FnvHasher>>;

pub fn new_map<K, V>() -> HashMap<K, V>
    where K: Eq + Hash
{
    HashMap::<K, V>::with_hasher(Default::default())
}

pub fn new_set<K>() -> HashSet<K>
    where K: Eq + Hash
{
    HashSet::<K>::with_hasher(Default::default())
}

pub fn ptr_eq<T>(x: &T, y: &T) -> bool {
    x as *const _ == y as *const _
}

// avoids dropping overhead

pub fn fast_clear<T: Copy>(vec: &mut Vec<T>) {
    unsafe {
        vec.set_len(0);
    }
}

pub fn fast_extend<T: Copy>(vec: &mut Vec<T>, other: &[T]) {
    vec.reserve(other.len());
    unsafe {
        let len = vec.len();
        ptr::copy_nonoverlapping(other.get_unchecked(0),
                                 vec.get_unchecked_mut(len),
                                 other.len());
        vec.set_len(len + other.len());
    }
}

pub fn copy_portion(vec: &mut Vec<u8>, from: Range<usize>) {
    let Range { start: copy_start, end: copy_end } = from.clone();
    &vec[from]; // for the bounds check
    unsafe {
        let copy_len = copy_end - copy_start;
        vec.reserve(copy_len);

        let old_len = vec.len();
        let copy_from = vec.as_ptr().offset(copy_start as isize);
        let copy_to = vec.as_mut_ptr().offset(old_len as isize);
        ptr::copy_nonoverlapping(copy_from, copy_to, copy_len);
        vec.set_len(old_len + copy_len);
    }
}

// rust already assumes you're on a twos-complement byte-addressed pure-endian machine...
// a chapter header is CRLF+ $ ( CRLF+ #*#...#*#, 79 total punctuation
// thus, it has #*#* or *#*# on any 32*19-bit boundary

fn aligned_part(buffer: &[u8]) -> (usize, &[u32]) {
    let mut sptr = buffer.as_ptr() as usize;
    let mut eptr = sptr + buffer.len();

    if sptr > !3 {
        // pointer into the last 32-bit word of the address space?  wat
        return (0, Default::default());
    }

    let offset = sptr.wrapping_neg() & 3;
    sptr += offset; // just checked this won't overflow
    eptr -= eptr & 3; // cannot overflow by construction

    unsafe { (offset, slice::from_raw_parts(sptr as *const u32, (eptr - sptr) / 4)) }
}

#[inline(never)]
pub fn find_chapter_header(mut buffer: &[u8]) -> Option<usize> {
    // returns something pointing at four consequtive puncts, guaranteed to
    // find if there is a run of 79
    fn hunt(buffer: &[u8]) -> Option<usize> {
        let (offset, aligned) = aligned_part(buffer);

        let mut pp = 0;
        while pp < aligned.len() {
            let word = aligned[pp];
            if word == 0x2a232a23 || word == 0x232a232a {
                return Some(offset + pp * 4);
            }
            pp += 19;
        }

        return None;
    }

    fn is_real(buffer: &[u8], mut midp: usize) -> Option<usize> {
        // backtrack to the beginning of the line
        while midp > 0 && (buffer[midp] == b'#' || buffer[midp] == b'*') {
            midp -= 1;
        }

        // make sure we reached a CRLF
        if buffer[midp] != b'\r' && buffer[midp] != b'\n' {
            return None;
        }
        // skip CRLF
        while midp > 0 && (buffer[midp] == b'\r' || buffer[midp] == b'\n') {
            midp -= 1;
        }
        // make sure we reached [CRLF] $(
        if midp >= 2 && buffer[midp] == b'(' && buffer[midp - 1] == b'$' &&
           (buffer[midp - 2] == b'\r' || buffer[midp - 2] == b'\n') {
            Some(midp - 1)
        } else {
            None
        }
    }

    let mut offset = 0;
    loop {
        match hunt(buffer) {
            None => return None,
            Some(mix) => {
                match is_real(buffer, mix) {
                    Some(chap) => return Some(chap + offset),
                    None => {
                        buffer = &buffer[mix + 1..];
                        offset += mix + 1;
                    }
                }
            }
        }
    }
}

#[derive(Eq,PartialEq,Copy,Clone)]
pub struct LongStr<'a>(pub &'a [u8]);

impl<'a> Hash for LongStr<'a> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.len().hash(state);
    }
}
<commit_msg>most memcpies we do are very short<commit_after>use fnv::FnvHasher;
use std::collections;
use std::hash::BuildHasherDefault;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Range;
use std::ptr;
use std::slice;

pub type HashMap<K, V> = collections::HashMap<K, V, BuildHasherDefault<FnvHasher>>;
pub type HashSet<K> = collections::HashSet<K, BuildHasherDefault<FnvHasher>>;

pub fn new_map<K, V>() -> HashMap<K, V>
    where K: Eq + Hash
{
    HashMap::<K, V>::with_hasher(Default::default())
}

pub fn new_set<K>() -> HashSet<K>
    where K: Eq + Hash
{
    HashSet::<K>::with_hasher(Default::default())
}

pub fn ptr_eq<T>(x: &T, y: &T) -> bool {
    x as *const _ == y as *const _
}

// avoids dropping overhead

pub fn fast_clear<T: Copy>(vec: &mut Vec<T>) {
    unsafe {
        vec.set_len(0);
    }
}

unsafe fn short_copy<T>(src: *const T, dst: *mut T, count: usize) {
    if count == 2 {
        ptr::write(dst as *mut [T; 2], ptr::read(src as *const [T; 2]));
    } else if count == 1 {
        ptr::write(dst, ptr::read(src));
    } else {
        ptr::copy_nonoverlapping(src, dst, count);
    }
}

pub fn fast_extend<T: Copy>(vec: &mut Vec<T>, other: &[T]) {
    vec.reserve(other.len());
    unsafe {
        let len = vec.len();
        short_copy(other.get_unchecked(0),
                                 vec.get_unchecked_mut(len),
                                 other.len());
        vec.set_len(len + other.len());
    }
}

pub fn copy_portion(vec: &mut Vec<u8>, from: Range<usize>) {
    let Range { start: copy_start, end: copy_end } = from.clone();
    &vec[from]; // for the bounds check
    unsafe {
        let copy_len = copy_end - copy_start;
        vec.reserve(copy_len);

        let old_len = vec.len();
        let copy_from = vec.as_ptr().offset(copy_start as isize);
        let copy_to = vec.as_mut_ptr().offset(old_len as isize);
        short_copy(copy_from, copy_to, copy_len);
        vec.set_len(old_len + copy_len);
    }
}

// rust already assumes you're on a twos-complement byte-addressed pure-endian machine...
// a chapter header is CRLF+ $ ( CRLF+ #*#...#*#, 79 total punctuation
// thus, it has #*#* or *#*# on any 32*19-bit boundary

fn aligned_part(buffer: &[u8]) -> (usize, &[u32]) {
    let mut sptr = buffer.as_ptr() as usize;
    let mut eptr = sptr + buffer.len();

    if sptr > !3 {
        // pointer into the last 32-bit word of the address space?  wat
        return (0, Default::default());
    }

    let offset = sptr.wrapping_neg() & 3;
    sptr += offset; // just checked this won't overflow
    eptr -= eptr & 3; // cannot overflow by construction

    unsafe { (offset, slice::from_raw_parts(sptr as *const u32, (eptr - sptr) / 4)) }
}

#[inline(never)]
pub fn find_chapter_header(mut buffer: &[u8]) -> Option<usize> {
    // returns something pointing at four consequtive puncts, guaranteed to
    // find if there is a run of 79
    fn hunt(buffer: &[u8]) -> Option<usize> {
        let (offset, aligned) = aligned_part(buffer);

        let mut pp = 0;
        while pp < aligned.len() {
            let word = aligned[pp];
            if word == 0x2a232a23 || word == 0x232a232a {
                return Some(offset + pp * 4);
            }
            pp += 19;
        }

        return None;
    }

    fn is_real(buffer: &[u8], mut midp: usize) -> Option<usize> {
        // backtrack to the beginning of the line
        while midp > 0 && (buffer[midp] == b'#' || buffer[midp] == b'*') {
            midp -= 1;
        }

        // make sure we reached a CRLF
        if buffer[midp] != b'\r' && buffer[midp] != b'\n' {
            return None;
        }
        // skip CRLF
        while midp > 0 && (buffer[midp] == b'\r' || buffer[midp] == b'\n') {
            midp -= 1;
        }
        // make sure we reached [CRLF] $(
        if midp >= 2 && buffer[midp] == b'(' && buffer[midp - 1] == b'$' &&
           (buffer[midp - 2] == b'\r' || buffer[midp - 2] == b'\n') {
            Some(midp - 1)
        } else {
            None
        }
    }

    let mut offset = 0;
    loop {
        match hunt(buffer) {
            None => return None,
            Some(mix) => {
                match is_real(buffer, mix) {
                    Some(chap) => return Some(chap + offset),
                    None => {
                        buffer = &buffer[mix + 1..];
                        offset += mix + 1;
                    }
                }
            }
        }
    }
}

#[derive(Eq,PartialEq,Copy,Clone)]
pub struct LongStr<'a>(pub &'a [u8]);

impl<'a> Hash for LongStr<'a> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.len().hash(state);
    }
}
<|endoftext|>


<commit_before>//! Miscellaneous utility macros and functions.

use docker_compose::v2 as dc;
use glob;
use retry::retry;
use std::env;
use std::error;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

/// We use the same `Error` type as `docker_compose` for simplicity.
pub type Error = dc::Error;

/// Create an error using a format string and arguments.
#[macro_export]
macro_rules! err {
    ($( $e:expr ),*) => ($crate::Error::from(format!($( $e ),*)));
}

pub trait ToStrOrErr {
    /// Convert to a Rust string as per `OsStr::to_str`, or return an
    /// error;
    fn to_str_or_err(&self) -> Result<&str, Error>;
}

impl ToStrOrErr for OsStr {
    fn to_str_or_err(&self) -> Result<&str, Error> {
        self.to_str().ok_or_else(|| {
            err!("the string {:?} contains non-Unicode characters",
                 self)
        })
    }
}

impl ToStrOrErr for Path {
    fn to_str_or_err(&self) -> Result<&str, Error> {
        self.to_str().ok_or_else(|| {
            err!("the path {} contains non-Unicode characters",
                 self.display())
        })
    }
}

/// Custom methods which we add to `Path` to support common operations.
pub trait ConductorPathExt: ToStrOrErr {
    /// Glob relative to this path.
    fn glob(&self, pattern: &str) -> Result<glob::Paths, Error>;

    /// Ensure the directory containing this path exists.  Returns the path
    /// itself so we can chain function calls, which might be too cute.
    /// (And copy it to a fully-owned `PathBuf` to avoid borrow-checker
    /// issues.)
    fn with_guaranteed_parent(&self) -> Result<PathBuf, Error>;

    /// Convert this path to a relative path, interpreting relative to the
    /// current working directory.
    fn to_absolute(&self) -> Result<PathBuf, Error>;
}

impl ConductorPathExt for Path {
    fn glob(&self, pattern: &str) -> Result<glob::Paths, Error> {
        // We always use the same options.
        let opts = glob::MatchOptions {
            case_sensitive: true,
            require_literal_separator: true,
            require_literal_leading_dot: true,
        };

        // Construct a full glob and run it.
        let pat = format!("{}/{}", try!(self.to_str_or_err()), pattern);
        Ok(try!(glob::glob_with(&pat, &opts)))
    }

    fn with_guaranteed_parent(&self) -> Result<PathBuf, Error> {
        let parent = try!(self.parent().ok_or_else(|| {
            err!("can't find parent path of {}", self.display())
        }));

        // Take an error message and elaborate a bit.  We use a trait
        // pointer here so we can use this for multiple error types,
        // because Rust closures don't seem to support type parameters.
        let wrap_err = |err: &error::Error| -> Error {
            err!("error creating parent directories for {}: {}",
                 parent.display(), err)
        };

        // On certain file systems, `create_dir_all` is not terribly thread
        // safe, and it may fail if another thread is trying to create the
        // same directories.  This seems to happen inside Docker containers
        // on Travis CI, for example.  For a possibly related issue, see
        // https://github.com/jpetazzo/dind/issues/73.  So we're going to
        // retry this function if it fails, in case it failed at some
        // intermediate step in the directory hierarchy.
        let retry_result = retry(5, 50, || {
            // The function to re-try.
            fs::create_dir_all(&parent)
        }, |result| {
            // Return true if we're done retrying.
            match result {
                &Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists =>
                    false,
                _ => true
            }
        });
        // Unwrap twice: Outer error is a possible retry failure, inner
        // error is a filesystem error.
        try!(try!(retry_result.map_err(|e| wrap_err(&e))).map_err(|e| wrap_err(&e)));
        Ok(self.to_owned())
    }

    fn to_absolute(&self) -> Result<PathBuf, Error> {
        let path = try!(env::current_dir()).join(self);
        assert!(path.is_absolute());
        Ok(path)
    }
}

#[test]
fn path_glob_uses_path_as_base() {
    let base = Path::new("examples/hello/pods/overrides");
    let paths: Vec<_> = base.glob("test/*.env").unwrap()
        .map(|p| p.unwrap().strip_prefix(base).unwrap().to_owned())
        .collect();
    assert_eq!(paths, vec!(Path::new("test/common.env")));
}
<commit_msg>Better explain create_dir_all workaround<commit_after>//! Miscellaneous utility macros and functions.

use docker_compose::v2 as dc;
use glob;
use retry::retry;
use std::env;
use std::error;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

/// We use the same `Error` type as `docker_compose` for simplicity.
pub type Error = dc::Error;

/// Create an error using a format string and arguments.
#[macro_export]
macro_rules! err {
    ($( $e:expr ),*) => ($crate::Error::from(format!($( $e ),*)));
}

pub trait ToStrOrErr {
    /// Convert to a Rust string as per `OsStr::to_str`, or return an
    /// error;
    fn to_str_or_err(&self) -> Result<&str, Error>;
}

impl ToStrOrErr for OsStr {
    fn to_str_or_err(&self) -> Result<&str, Error> {
        self.to_str().ok_or_else(|| {
            err!("the string {:?} contains non-Unicode characters",
                 self)
        })
    }
}

impl ToStrOrErr for Path {
    fn to_str_or_err(&self) -> Result<&str, Error> {
        self.to_str().ok_or_else(|| {
            err!("the path {} contains non-Unicode characters",
                 self.display())
        })
    }
}

/// Custom methods which we add to `Path` to support common operations.
pub trait ConductorPathExt: ToStrOrErr {
    /// Glob relative to this path.
    fn glob(&self, pattern: &str) -> Result<glob::Paths, Error>;

    /// Ensure the directory containing this path exists.  Returns the path
    /// itself so we can chain function calls, which might be too cute.
    /// (And copy it to a fully-owned `PathBuf` to avoid borrow-checker
    /// issues.)
    fn with_guaranteed_parent(&self) -> Result<PathBuf, Error>;

    /// Convert this path to a relative path, interpreting relative to the
    /// current working directory.
    fn to_absolute(&self) -> Result<PathBuf, Error>;
}

impl ConductorPathExt for Path {
    fn glob(&self, pattern: &str) -> Result<glob::Paths, Error> {
        // We always use the same options.
        let opts = glob::MatchOptions {
            case_sensitive: true,
            require_literal_separator: true,
            require_literal_leading_dot: true,
        };

        // Construct a full glob and run it.
        let pat = format!("{}/{}", try!(self.to_str_or_err()), pattern);
        Ok(try!(glob::glob_with(&pat, &opts)))
    }

    fn with_guaranteed_parent(&self) -> Result<PathBuf, Error> {
        let parent = try!(self.parent().ok_or_else(|| {
            err!("can't find parent path of {}", self.display())
        }));

        // Take an error message and elaborate a bit.  We use a trait
        // pointer here so we can use this for multiple error types,
        // because Rust closures don't seem to support type parameters.
        let wrap_err = |err: &error::Error| -> Error {
            err!("error creating parent directories for {}: {}",
                 parent.display(), err)
        };

        // On certain file systems, `create_dir_all` is not terribly thread
        // safe, and it may fail if another thread is trying to create the
        // same directories.  This seems to happen inside Docker containers
        // on Travis CI, for example.  For a possibly related issue, see
        // https://github.com/jpetazzo/dind/issues/73.  So we're going to
        // retry this function if it fails, because it will fail to
        // create directories below the one that already existed.
        let retry_result = retry(5, 50, || {
            // The function to re-try.
            fs::create_dir_all(&parent)
        }, |result| {
            // Return true if we're done retrying.
            match result {
                &Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists =>
                    false,
                _ => true
            }
        });
        // Unwrap twice: Outer error is a possible retry failure, inner
        // error is a filesystem error.
        try!(try!(retry_result.map_err(|e| wrap_err(&e))).map_err(|e| wrap_err(&e)));
        Ok(self.to_owned())
    }

    fn to_absolute(&self) -> Result<PathBuf, Error> {
        let path = try!(env::current_dir()).join(self);
        assert!(path.is_absolute());
        Ok(path)
    }
}

#[test]
fn path_glob_uses_path_as_base() {
    let base = Path::new("examples/hello/pods/overrides");
    let paths: Vec<_> = base.glob("test/*.env").unwrap()
        .map(|p| p.unwrap().strip_prefix(base).unwrap().to_owned())
        .collect();
    assert_eq!(paths, vec!(Path::new("test/common.env")));
}
<|endoftext|>