<commit_before>extern crate pkg_config;

use std::process::Command;
use std::fs;
use std::path::Path;
use std::env;
use std::path::PathBuf;

fn main() {
    let wcd = env::current_dir().unwrap();
    let build = PathBuf::from(&wcd.join("ext/libqmlrswrapper/build"));

    let _ = fs::create_dir_all(&build);

    let mut myargs = vec![".."] ;

    /*
     * Support Qt installed via the Ports system on BSD-like systems.
     *
     * The native libs are in `/usr/local/lib`, which is not linked against by default.
     * This means that either the user or every package has to add this if they want to link
     * against something that is not part of the core distribution in `/usr/lib`.
     *
     * See https://wiki.freebsd.org/WarnerLosh/UsrLocal for the line of reasoning & how this will
     * change in the future.
     */
    if cfg!(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd",
                target_os = "dragonfly", target_os = "bitrig")) {
        println!("cargo:rustc-link-search=native=/usr/local/lib");
    }

    /*
     * Prameters for supporting QT on OS X installed via homebres
     *
     * Because QT5 conflicts with QT4 the homebrew package manager won't link
     * the QT5 package into the default search paths for libraries, to deal
     * with this we need to give pkg-config and cmake a nudge in the right
     * direction.
     */
    if cfg!(target_os = "macos") {
        // Point at homebrew's QT5 install location, this will likely fail if
        // another package manager was used.
        let qt5_lib_path = Path::new("/usr/local/opt/qt5/lib");

        if Path::exists(qt5_lib_path) {
            // First nudge cmake in the direction of the .cmake files added by
            // homebrew. This clobbers the existing value if present, it's
            // unlikely to be present though.
            env::set_var("CMAKE_PREFIX_PATH", qt5_lib_path.join("cmake"));

            // Nudge pkg-config in the direction of the brewed QT to ensure the
            // correct compiler flags get found for the project.
            env::set_var("PKG_CONFIG_PATH", qt5_lib_path.join("pkgconfig"));
        } else {
            panic!("The QT5 was not found at the expected location ({}) please install it via homebrew.", qt5_lib_path.display());
        }
    }

    let is_msys = env::var("MSYSTEM").is_ok() ;
    if cfg!(windows) && is_msys {
        myargs.push("-GMSYS Makefiles") ;
    }

    let cmake_output = Command::new("cmake").args(&myargs).current_dir(&build).output().unwrap_or_else(|e| {
        panic!("Failed to run cmake: {}", e);
    });
    let cmake_stderr = String::from_utf8(cmake_output.stderr).unwrap();
    if !cmake_stderr.is_empty() {
        panic!("cmake produced stderr: {}", cmake_stderr);
    }

    Command::new("make").current_dir(&build).output().unwrap_or_else(|e| {
        panic!("Failed to run make: {}", e);
    });

    if cfg!(windows) && is_msys {
        println!("cargo:rustc-link-search=native={}\\system32",env::var("WINDIR").unwrap());
    }
    println!("cargo:rustc-link-lib=static=qmlrswrapper");
    println!("cargo:rustc-link-lib=dylib=stdc++");
    println!("cargo:rustc-link-search=native={}",build.display());
    pkg_config::find_library("Qt5Core Qt5Gui Qt5Qml Qt5Quick").unwrap();
}
<commit_msg>Determine Qt5 path from QTDIR env vars, if set, on OS X<commit_after>extern crate pkg_config;

use std::process::Command;
use std::fs;
use std::path::Path;
use std::env;
use std::env::consts;
use std::path::PathBuf;

fn main() {
    let wcd = env::current_dir().unwrap();
    let build = PathBuf::from(&wcd.join("ext/libqmlrswrapper/build"));

    let _ = fs::create_dir_all(&build);

    let mut myargs = vec![".."] ;

    /*
     * Support Qt installed via the Ports system on BSD-like systems.
     *
     * The native libs are in `/usr/local/lib`, which is not linked against by default.
     * This means that either the user or every package has to add this if they want to link
     * against something that is not part of the core distribution in `/usr/lib`.
     *
     * See https://wiki.freebsd.org/WarnerLosh/UsrLocal for the line of reasoning & how this will
     * change in the future.
     */
    if cfg!(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd",
                target_os = "dragonfly", target_os = "bitrig")) {
        println!("cargo:rustc-link-search=native=/usr/local/lib");
    }

    /*
     * Parameters for supporting QT on OS X
     *
     * Because QT5 conflicts with QT4 the homebrew package manager won't link
     * the QT5 package into the default search paths for libraries, to deal
     * with this we need to give pkg-config and cmake a nudge in the right
     * direction.
     */
    if cfg!(target_os = "macos") {
        // We use the QTDIR or QTDIR64 env variables to find the location of
        // Qt5. If these are not set, we use the default homebrew install
        // location.
        let qtdir_variable = match consts::ARCH {
            "x86_64" => "QTDIR64",
            _ => "QTDIR",
        };
        let mut qt5_lib_path = PathBuf::new();
        qt5_lib_path.push(env::var(qtdir_variable).unwrap_or(String::from("/usr/local/opt/qt5")));
        qt5_lib_path.push(Path::new("lib"));

        if qt5_lib_path.exists() {
            // First nudge cmake in the direction of the .cmake files added by
            // homebrew. This clobbers the existing value if present, it's
            // unlikely to be present though.
            env::set_var("CMAKE_PREFIX_PATH", qt5_lib_path.join("cmake"));

            // Nudge pkg-config in the direction of the brewed QT to ensure the
            // correct compiler flags get found for the project.
            env::set_var("PKG_CONFIG_PATH", qt5_lib_path.join("pkgconfig"));
        } else {
            panic!("QT5 was not found at the expected location ({}) please install it via homebrew, or set the {} env variable.",
                qt5_lib_path.display(), qtdir_variable);
        }
    }

    let is_msys = env::var("MSYSTEM").is_ok() ;
    if cfg!(windows) && is_msys {
        myargs.push("-GMSYS Makefiles") ;
    }

    let cmake_output = Command::new("cmake").args(&myargs).current_dir(&build).output().unwrap_or_else(|e| {
        panic!("Failed to run cmake: {}", e);
    });
    let cmake_stderr = String::from_utf8(cmake_output.stderr).unwrap();
    if !cmake_stderr.is_empty() {
        panic!("cmake produced stderr: {}", cmake_stderr);
    }

    Command::new("make").current_dir(&build).output().unwrap_or_else(|e| {
        panic!("Failed to run make: {}", e);
    });

    if cfg!(windows) && is_msys {
        println!("cargo:rustc-link-search=native={}\\system32",env::var("WINDIR").unwrap());
    }
    println!("cargo:rustc-link-lib=static=qmlrswrapper");
    println!("cargo:rustc-link-lib=dylib=stdc++");
    println!("cargo:rustc-link-search=native={}",build.display());
    pkg_config::find_library("Qt5Core Qt5Gui Qt5Qml Qt5Quick").unwrap();
}
<|endoftext|>


<commit_before>extern crate pkg_config;
use std::process::Command;
use std::path::PathBuf;
use std::env;
use std::fs;

macro_rules! err_exists {
    ($expr:expr, $msg:expr) => {
        match $expr {
            Ok(val) => val,
            Err(err) => {
                match err.kind() {
                    ::std::io::ErrorKind::AlreadyExists => (),
                    _ => panic!(format!("{}: {}", $msg, err)),
                }
            }
        }
    }
}

fn lib_available(name: &str) -> bool {
    match pkg_config::find_library(name) {
        Ok(_) => true,
        Err(_) => {
            let res = Command::new("ldconfig").arg("--print-cache").output().unwrap();
            if res.status.success() {
                String::from_utf8(res.stdout).unwrap().contains(name)
            } else {
                false
            }
        }
    }
}

fn windows(_: String) {
    // TODO: Use precompiled binary!
    unimplemented!();
}

fn osx(_: String) {
    // TODO: I down't own any apple hardware to test this.
    unimplemented!();
}

fn linux(target: String) {
    if !lib_available("soundio") {
        build(target);
    } else {
        // TODO: remove else branch
        build(target);
    }
}

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("windows") {
        windows(target)
    } else if target.contains("apple") {
        osx(target)
    } else {
        // assume the rest is linux
        linux(target)
    }
}

const LIBSOUNDIO_TAR: &'static str = "http://libsound.io/release/libsoundio-1.1.0.tar.gz";
const LIBSOUNDIO_WIN: &'static str = "http://libsound.io/release/libsoundio-1.1.0.zip";

fn build(target: String) {
    let host = env::var("HOST").unwrap();
    let dst_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let lib_dir = dst_dir.join("lib");
    let include_dir = dst_dir.join("include");
    err_exists!(fs::create_dir(&lib_dir), &lib_dir.display());
    err_exists!(fs::create_dir(&include_dir), &include_dir.display());

    // set cargo flags
    println!("cargo:rustc-link-search=native={}", &lib_dir.display()); // -L
    println!("cargo:include={}", &include_dir.display());
    println!("cargo:root={}", &dst_dir.display());

    // download and extract libsoundio source
    Command::new("curl")
        .current_dir(&dst_dir)
        .args(&["--location", "--remote-name"])
        .arg(LIBSOUNDIO_TAR)
        .output()
        .unwrap();
    Command::new("tar")
        .current_dir(&dst_dir)
        .arg("-xvzf")
        .arg("libsoundio-1.1.0.tar.gz")
        .output()
        .unwrap();

    // create build dir
    let soundio_root = dst_dir.join("libsoundio-1.1.0");
    let build_dir = soundio_root.join("build");
    err_exists!(fs::create_dir(&build_dir), &build_dir.display());

    // run cmake
    Command::new("cmake")
        .current_dir(&build_dir)
        .arg("-DCMAKE_BUILD_TYPE=Debug")
        .arg("-DCMAKE_INSTALL_LIBDIR:PATH=lib")
        .arg(format!("-DCMAKE_INSTALL_PREFIX:PATH={}", &dst_dir.display()))
        .arg("-DBUILD_EXAMPLE_PROGRAMS:BOOL=OFF")
        .arg("-DBUILD_TESTS:BOOL=OFF")
        .arg("-DBUILD_STATIC_LIBS:BOOL=OFF")
        .arg("-DBUILD_SHARED_LIBS:BOOL=ON")
        .arg("-DENABLE_JACK:BOOL=OFF")
        .arg("-DENABLE_PULSEAUDIO:BOOL=OFF")
        .arg("-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON")
        .arg("..")
        .output()
        .unwrap();

    // make install
    Command::new("make")
        .current_dir(&build_dir)
        .arg("install")
        .output()
        .unwrap();

    // remove builddir
    fs::remove_dir_all(build_dir).unwrap();
}
<commit_msg>Add `sio!` macro and `sio_url` function<commit_after>extern crate pkg_config;
use std::process::Command;
use std::path::PathBuf;
use std::env;
use std::fs;

macro_rules! sio {
    ($expr:expr) => { format!("libsoundio-{}", $expr) }
}

macro_rules! err_exists {
    ($expr:expr, $msg:expr) => {
        match $expr {
            Ok(val) => val,
            Err(err) => {
                match err.kind() {
                    ::std::io::ErrorKind::AlreadyExists => (),
                    _ => panic!(format!("{}: {}", $msg, err)),
                }
            }
        }
    }
}

fn lib_available(name: &str) -> bool {
    match pkg_config::find_library(name) {
        Ok(_) => true,
        Err(_) => {
            let res = Command::new("ldconfig").arg("--print-cache").output().unwrap();
            if res.status.success() {
                String::from_utf8(res.stdout).unwrap().contains(name)
            } else {
                false
            }
        }
    }
}

fn windows(_: String) {
    // TODO: Use precompiled binary!
    unimplemented!();
}

fn osx(_: String) {
    // TODO: I down't own any apple hardware to test this.
    unimplemented!();
}

fn linux(target: String) {
    if !lib_available("soundio") {
        build(target);
    } else {
        // TODO: remove else branch
        build(target);
    }
}

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("windows") {
        windows(target)
    } else if target.contains("apple") {
        osx(target)
    } else {
        // assume the rest is linux
        linux(target)
    }
}

fn sio_url(ext: &'static str) -> String {
    format!("http://libsound.io/release/{}.{}", sio!("1.1.0"), ext)
}

fn build(target: String) {
    let host = env::var("HOST").unwrap();
    let dst_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let lib_dir = dst_dir.join("lib");
    let include_dir = dst_dir.join("include");
    err_exists!(fs::create_dir(&lib_dir), &lib_dir.display());
    err_exists!(fs::create_dir(&include_dir), &include_dir.display());

    // set cargo flags
    println!("cargo:rustc-link-search=native={}", &lib_dir.display()); // -L
    println!("cargo:include={}", &include_dir.display());
    println!("cargo:root={}", &dst_dir.display());

    // download and extract libsoundio source
    Command::new("curl")
        .current_dir(&dst_dir)
        .args(&["--location", "--remote-name"])
        .arg(sio_url("tar.gz"))
        .output()
        .unwrap();
    Command::new("tar")
        .current_dir(&dst_dir)
        .arg("-xvzf")
        .arg(format!("{}.{}", sio!("1.1.0"), "tar.gz"))
        .output()
        .unwrap();

    // create build dir
    let soundio_root = dst_dir.join(sio!("1.1.0"));
    let build_dir = soundio_root.join("build");
    err_exists!(fs::create_dir(&build_dir), &build_dir.display());

    // run cmake
    Command::new("cmake")
        .current_dir(&build_dir)
        .arg("-DCMAKE_BUILD_TYPE=Debug")
        .arg("-DCMAKE_INSTALL_LIBDIR:PATH=lib")
        .arg(format!("-DCMAKE_INSTALL_PREFIX:PATH={}", &dst_dir.display()))
        .arg("-DBUILD_EXAMPLE_PROGRAMS:BOOL=OFF")
        .arg("-DBUILD_TESTS:BOOL=OFF")
        .arg("-DBUILD_STATIC_LIBS:BOOL=OFF")
        .arg("-DBUILD_SHARED_LIBS:BOOL=ON")
        .arg("-DENABLE_JACK:BOOL=OFF")
        .arg("-DENABLE_PULSEAUDIO:BOOL=OFF")
        .arg("-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON")
        .arg("..")
        .output()
        .unwrap();

    // make install
    Command::new("make")
        .current_dir(&build_dir)
        .arg("install")
        .output()
        .unwrap();

    // remove builddir
    fs::remove_dir_all(build_dir).unwrap();
}
<|endoftext|>


<commit_before>extern crate gcc;

use std::fs;
use std::env;
use std::path::Path;

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let prefix = Path::new(".");

    fs::copy(prefix.join("src/arch/x86/linker.ld"),
             out_dir.clone() + "/linker.ld").unwrap();

    gcc::compile_library("libuniarch.a", &["src/arch/x86/boot.S"]);

    println!("cargo:rustc-link-search=native={}", out_dir);
}
<commit_msg>build.rs: Add auto detection of assembly sources<commit_after>extern crate gcc;

use std::fs;
use std::env;
use std::path::Path;

fn build_libuniarch(arch_path: &std::path::PathBuf) {
    let arch_content = fs::read_dir(arch_path).unwrap();
    let mut gcc_config = gcc::Config::new();

    for p in arch_content {
        let path = p.unwrap().path();
        let extension = path.extension().unwrap().to_str().unwrap();

        if extension == "S" {
            gcc_config.file(path.to_str().unwrap().clone());
        }
    }

    gcc_config.compile("libuniarch.a");
}

fn main() {
    let target = "x86";
    let out_dir = env::var("OUT_DIR").unwrap();
    let out_dir_path = Path::new(&out_dir[..]);
    let arch_path = Path::new("src/arch/").join(target);

    fs::copy(arch_path.join("linker.ld"),
             out_dir_path.join("linker.ld")).unwrap();

    build_libuniarch(&arch_path);
}
<|endoftext|>


<commit_before>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::Path;
use std::collections::HashMap;
use std::str::FromStr;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &String) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, HashMap<String, usize>>
}

impl TmuxSession {
    pub fn from_string(results: &String) -> TmuxSession {
        let window_name = Regex::new(r": (\w*)[$\*-]? \(").unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, HashMap<String, usize>> = HashMap::new();
        let mut h = HashMap::new();

        for line in window_lines {
            for cap in window_name.captures_iter(line) {
                let name = cap.at(1).unwrap();
                h.insert("Panes".to_string(), TmuxSession::count_panes(line));
                windows.insert(name.to_string(), h.clone());
            }
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows
        }
    }

    pub fn count_panes(line: &str) -> usize {
        let panes = Regex::new(r"\((\d*) panes\)").unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = cap.at(1).unwrap_or("0");
        }

        usize::from_str(num).unwrap()
    }
}

#[test]
fn count_panes_returns_two() {
  let num = TmuxSession::count_panes("1: ssh (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
  assert_eq!(num, 2)
}

#[test]
fn count_panes_returns_one() {
  let num = TmuxSession::count_panes("1: ssh (1 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
  assert_eq!(num, 1)
}

#[test]
fn count_with_active_previous_flag() {
  let num = TmuxSession::count_panes("1: ssh- (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
  assert_eq!(num, 2)
}

#[test]
fn count_with_active_dollar_sign_flag() {
  let num = TmuxSession::count_panes("1: ssh$ (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
  assert_eq!(num, 2)
}

#[test]
fn count_with_active_window_flag() {
  let num = TmuxSession::count_panes("1: ssh* (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
  assert_eq!(num, 2)
}
<commit_msg>More integration helper testing<commit_after>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::Path;
use std::collections::HashMap;
use std::str::FromStr;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &String) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, HashMap<String, usize>>
}

impl TmuxSession {
    pub fn from_string(results: &String) -> TmuxSession {
        let window_name = Regex::new(r":\s(\w*)[$\*-]?\s+\(").unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, HashMap<String, usize>> = HashMap::new();
        let mut h = HashMap::new();

        for line in window_lines {
            for cap in window_name.captures_iter(line) {
                let name = cap.at(1).unwrap();
                h.insert("Panes".to_string(), TmuxSession::count_panes(line));
                windows.insert(name.to_string(), h.clone());
            }
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows
        }
    }

    pub fn count_panes(line: &str) -> usize {
        let panes = Regex::new(r"\((\d*) panes\)").unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = cap.at(1).unwrap_or("0");
        }

        usize::from_str(num).unwrap()
    }
}

#[test]
fn count_panes_returns_two() {
    let num = TmuxSession::count_panes("1: ssh (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
    assert_eq!(num, 2)
}

#[test]
fn count_panes_returns_one() {
    let num = TmuxSession::count_panes("1: ssh (1 panes) [173x42] [layout b5bd,173x42,0,0,0] @0");
    assert_eq!(num, 1)
}

#[test]
fn parses_with_trailing_whitespace_after_window_name() {
    let config = "1: ssh  (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().get("Panes").unwrap().to_owned();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_previous_flag() {
    let config = "1: ssh- (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().get("Panes").unwrap().to_owned();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_dollar_sign_flag() {
    let config = "1: ssh$ (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().get("Panes").unwrap().to_owned();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_window_flag() {
    let config = "1: ssh* (2 panes) [173x42] [layout b5bd,173x42,0,0,0] @0\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().get("Panes").unwrap().to_owned();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn count_three_windows() {
    let config = "1: ssh  (1 panes) [173x42] [layout b5bd,173x42,0,0,0] @0
                  2: vim- (1 panes) [173x42] [layout b5be,173x42,0,0,1] @1
                  3: bash* (2 panes) [173x42] [layout b5bf,173x42,0,0,2] @2 (active)\n";
    let num = TmuxSession::from_string(&config.to_string()).num_of_windows;
    assert_eq!(num, 3)
}

#[test]
fn count_four_total_panes() {
    let config = "1: ssh  (1 panes) [173x42] [layout b5bd,173x42,0,0,0] @0
                  2: vim- (1 panes) [173x42] [layout b5be,173x42,0,0,1] @1
                  3: bash* (2 panes) [173x42] [layout b5bf,173x42,0,0,2] @2 (active)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let num = session.windows.get("ssh").unwrap().get("Panes").unwrap().to_owned();
    let num1 = session.windows.get("vim").unwrap().get("Panes").unwrap().to_owned();
    let num2 = session.windows.get("bash").unwrap().get("Panes").unwrap().to_owned();
    assert_eq!(num.to_owned(), 1);
    assert_eq!(num1.to_owned(), 1);
    assert_eq!(num2.to_owned(), 2)
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

pub type Tag = String;
pub type TagSlice<'a> = &'a str;

/// validator which can be used by clap to validate that a string is a valid tag
pub fn is_tag(s: String) -> Result<(), String> {
    use filters::filter::Filter;

    let is_lower      = |s: &String| s.chars().all(|c| c.is_lowercase());
    let no_whitespace = |s: &String| s.chars().all(|c| !c.is_whitespace());
    let is_alphanum   = |s: &String| s.chars().all(|c| c.is_alphanumeric());

    if is_lower.and(no_whitespace).and(is_alphanum).filter(&s) {
        Ok(())
    } else {
        Err(format!("The string '{}' is not a valid tag", s))
    }
}

<commit_msg>Add is_tag_str(&str) wrapper for is_tag(String)<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

pub type Tag = String;
pub type TagSlice<'a> = &'a str;

/// validator which can be used by clap to validate that a string is a valid tag
pub fn is_tag(s: String) -> Result<(), String> {
    is_tag_str(&s)
}

pub fn is_tag_str(s: &String) -> Result<(), String> {
    use filters::filter::Filter;

    let is_lower      = |s: &String| s.chars().all(|c| c.is_lowercase());
    let no_whitespace = |s: &String| s.chars().all(|c| !c.is_whitespace());
    let is_alphanum   = |s: &String| s.chars().all(|c| c.is_alphanumeric());

    if is_lower.and(no_whitespace).and(is_alphanum).filter(s) {
        Ok(())
    } else {
        Err(format!("The string '{}' is not a valid tag", s))
    }
}

<|endoftext|>


<commit_before>use std::error::Error;
use std::result::Result as RResult;

use toml::{Table, Value};

pub mod error {
    use std::fmt::{Debug, Display, Formatter};
    use std::fmt;
    use std::error::Error;
    use toml;

    #[derive(Clone)]
    pub enum ParserErrorKind {
        TOMLParserErrors,
        MissingMainSection,
        MissingVersionInfo,
    }

    pub struct ParserError {
        kind: ParserErrorKind,
        cause: Option<Box<Error>>,
    }

    impl ParserError {

        pub fn new(k: ParserErrorKind, cause: Option<Box<Error>>) -> ParserError {
            ParserError {
                kind: k,
                cause: cause,
            }
        }

    }

    impl Debug for ParserError {

        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            try!(write!(f, "{:?}", self.description()));
            Ok(())
        }

    }

    impl Display for ParserError {

        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            try!(write!(f, "{}", self.description()));
            Ok(())
        }

    }

    impl Error for ParserError {

        fn description(&self) -> &str {
            match self.kind {
                ParserErrorKind::TOMLParserErrors   => "Several TOML-Parser-Errors",
                ParserErrorKind::MissingMainSection => "Missing main section",
                ParserErrorKind::MissingVersionInfo => "Missing version information in main section",
            }
        }

        fn cause(&self) -> Option<&Error> {
            self.cause.as_ref().map(|e| &**e)
        }

    }

}

use self::error::ParserErrorKind;
use self::error::ParserError;

/**
 * EntryHeader
 *
 * This is basically a wrapper around toml::Table which provides convenience to the user of the
 * librray.
 */
#[derive(Debug, Clone)]
pub struct EntryHeader {
    toml: Table,
}

pub type Result<V> = RResult<V, error::ParserError>;

/**
 * Wrapper type around file header (TOML) object
 */
impl EntryHeader {

    /**
     * Get a new header object with a already-filled toml table
     */
    pub fn new(toml: Table) -> EntryHeader {
        EntryHeader {
            toml: toml,
        }
    }

    /**
     * Get the table which lives in the background
     */
    pub fn toml(&self) -> &Table {
        &self.toml
    }

    pub fn parse(s: &str) -> Result<EntryHeader> {
        use toml::Parser;

        let mut parser = Parser::new(s);
        parser.parse()
            .ok_or(ParserError::new(ParserErrorKind::TOMLParserErrors, None))
            .and_then(|t| verify_header_consistency(t))
            .map(|t| EntryHeader::new(t))
    }

}

fn verify_header_consistency(t: Table) -> Result<Table> {
    if !has_main_section(&t) {
        Err(ParserError::new(ParserErrorKind::MissingMainSection, None))
    } else if !has_imag_version_in_main_section(&t) {
        Err(ParserError::new(ParserErrorKind::MissingVersionInfo, None))
    } else {
        Ok(t)
    }
}

fn has_main_section(t: &Table) -> bool {
    t.contains_key("imag") &&
        match t.get("imag") {
            Some(&Value::Table(_)) => true,
            Some(_)                => false,
            None                   => false,
        }
}

fn has_imag_version_in_main_section(t: &Table) -> bool {
    use semver::Version;

    match t.get("imag").unwrap() {
        &Value::Table(ref sec) => {
            sec.get("version")
                .and_then(|v| {
                    match v {
                        &Value::String(ref s) => {
                            Some(Version::parse(&s[..]).is_ok())
                        },
                        _                 => Some(false),
                    }
                })
                .unwrap_or(false)
        }
        _                  => false,
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeMap;

    use toml::Value;

    #[test]
    fn test_imag_section() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Table(BTreeMap::new()));

        assert!(has_main_section(&map));
    }

    #[test]
    fn test_imag_invalid_section_type() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Boolean(false));

        assert!(!has_main_section(&map));
    }

    #[test]
    fn test_imag_abscent_main_section() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("not_imag".into(), Value::Boolean(false));

        assert!(!has_main_section(&map));
    }

    #[test]
    fn test_main_section_without_version() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Table(BTreeMap::new()));

        assert!(!has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_main_section_with_version() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        let mut sub = BTreeMap::new();
        sub.insert("version".into(), Value::String("0.0.0".into()));
        map.insert("imag".into(), Value::Table(sub));

        assert!(has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_main_section_with_version_in_wrong_type() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        let mut sub = BTreeMap::new();
        sub.insert("version".into(), Value::Boolean(false));
        map.insert("imag".into(), Value::Table(sub));

        assert!(!has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_verification_good() {
        use super::verify_header_consistency;

        let mut header = BTreeMap::new();
        let sub = {
            let mut sub = BTreeMap::new();
            sub.insert("version".into(), Value::String(String::from("0.0.0")));

            Value::Table(sub)
        };

        header.insert("imag".into(), sub);

        assert!(verify_header_consistency(header).is_ok());
    }

    #[test]
    fn test_verification_invalid_versionstring() {
        use super::verify_header_consistency;

        let mut header = BTreeMap::new();
        let sub = {
            let mut sub = BTreeMap::new();
            sub.insert("version".into(), Value::String(String::from("000")));

            Value::Table(sub)
        };

        header.insert("imag".into(), sub);

        assert!(!verify_header_consistency(header).is_ok());
    }

}

<commit_msg>Add test with current version<commit_after>use std::error::Error;
use std::result::Result as RResult;

use toml::{Table, Value};

pub mod error {
    use std::fmt::{Debug, Display, Formatter};
    use std::fmt;
    use std::error::Error;
    use toml;

    #[derive(Clone)]
    pub enum ParserErrorKind {
        TOMLParserErrors,
        MissingMainSection,
        MissingVersionInfo,
    }

    pub struct ParserError {
        kind: ParserErrorKind,
        cause: Option<Box<Error>>,
    }

    impl ParserError {

        pub fn new(k: ParserErrorKind, cause: Option<Box<Error>>) -> ParserError {
            ParserError {
                kind: k,
                cause: cause,
            }
        }

    }

    impl Debug for ParserError {

        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            try!(write!(f, "{:?}", self.description()));
            Ok(())
        }

    }

    impl Display for ParserError {

        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            try!(write!(f, "{}", self.description()));
            Ok(())
        }

    }

    impl Error for ParserError {

        fn description(&self) -> &str {
            match self.kind {
                ParserErrorKind::TOMLParserErrors   => "Several TOML-Parser-Errors",
                ParserErrorKind::MissingMainSection => "Missing main section",
                ParserErrorKind::MissingVersionInfo => "Missing version information in main section",
            }
        }

        fn cause(&self) -> Option<&Error> {
            self.cause.as_ref().map(|e| &**e)
        }

    }

}

use self::error::ParserErrorKind;
use self::error::ParserError;

/**
 * EntryHeader
 *
 * This is basically a wrapper around toml::Table which provides convenience to the user of the
 * librray.
 */
#[derive(Debug, Clone)]
pub struct EntryHeader {
    toml: Table,
}

pub type Result<V> = RResult<V, error::ParserError>;

/**
 * Wrapper type around file header (TOML) object
 */
impl EntryHeader {

    /**
     * Get a new header object with a already-filled toml table
     */
    pub fn new(toml: Table) -> EntryHeader {
        EntryHeader {
            toml: toml,
        }
    }

    /**
     * Get the table which lives in the background
     */
    pub fn toml(&self) -> &Table {
        &self.toml
    }

    pub fn parse(s: &str) -> Result<EntryHeader> {
        use toml::Parser;

        let mut parser = Parser::new(s);
        parser.parse()
            .ok_or(ParserError::new(ParserErrorKind::TOMLParserErrors, None))
            .and_then(|t| verify_header_consistency(t))
            .map(|t| EntryHeader::new(t))
    }

}

fn verify_header_consistency(t: Table) -> Result<Table> {
    if !has_main_section(&t) {
        Err(ParserError::new(ParserErrorKind::MissingMainSection, None))
    } else if !has_imag_version_in_main_section(&t) {
        Err(ParserError::new(ParserErrorKind::MissingVersionInfo, None))
    } else {
        Ok(t)
    }
}

fn has_main_section(t: &Table) -> bool {
    t.contains_key("imag") &&
        match t.get("imag") {
            Some(&Value::Table(_)) => true,
            Some(_)                => false,
            None                   => false,
        }
}

fn has_imag_version_in_main_section(t: &Table) -> bool {
    use semver::Version;

    match t.get("imag").unwrap() {
        &Value::Table(ref sec) => {
            sec.get("version")
                .and_then(|v| {
                    match v {
                        &Value::String(ref s) => {
                            Some(Version::parse(&s[..]).is_ok())
                        },
                        _                 => Some(false),
                    }
                })
                .unwrap_or(false)
        }
        _                  => false,
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeMap;

    use toml::Value;

    #[test]
    fn test_imag_section() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Table(BTreeMap::new()));

        assert!(has_main_section(&map));
    }

    #[test]
    fn test_imag_invalid_section_type() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Boolean(false));

        assert!(!has_main_section(&map));
    }

    #[test]
    fn test_imag_abscent_main_section() {
        use super::has_main_section;

        let mut map = BTreeMap::new();
        map.insert("not_imag".into(), Value::Boolean(false));

        assert!(!has_main_section(&map));
    }

    #[test]
    fn test_main_section_without_version() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        map.insert("imag".into(), Value::Table(BTreeMap::new()));

        assert!(!has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_main_section_with_version() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        let mut sub = BTreeMap::new();
        sub.insert("version".into(), Value::String("0.0.0".into()));
        map.insert("imag".into(), Value::Table(sub));

        assert!(has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_main_section_with_version_in_wrong_type() {
        use super::has_imag_version_in_main_section;

        let mut map = BTreeMap::new();
        let mut sub = BTreeMap::new();
        sub.insert("version".into(), Value::Boolean(false));
        map.insert("imag".into(), Value::Table(sub));

        assert!(!has_imag_version_in_main_section(&map));
    }

    #[test]
    fn test_verification_good() {
        use super::verify_header_consistency;

        let mut header = BTreeMap::new();
        let sub = {
            let mut sub = BTreeMap::new();
            sub.insert("version".into(), Value::String(String::from("0.0.0")));

            Value::Table(sub)
        };

        header.insert("imag".into(), sub);

        assert!(verify_header_consistency(header).is_ok());
    }

    #[test]
    fn test_verification_invalid_versionstring() {
        use super::verify_header_consistency;

        let mut header = BTreeMap::new();
        let sub = {
            let mut sub = BTreeMap::new();
            sub.insert("version".into(), Value::String(String::from("000")));

            Value::Table(sub)
        };

        header.insert("imag".into(), sub);

        assert!(!verify_header_consistency(header).is_ok());
    }

    #[test]
    fn test_verification_current_version() {
        use version;

        use super::verify_header_consistency;

        let mut header = BTreeMap::new();
        let sub = {
            let mut sub = BTreeMap::new();
            sub.insert("version".into(), Value::String(version!()));

            Value::Table(sub)
        };

        header.insert("imag".into(), sub);

        assert!(verify_header_consistency(header).is_ok());
    }
}

<|endoftext|>


<commit_before>// Copyright © 2017-2018 Mozilla Foundation
//
// This program is made available under an ISC-style license.  See the
// accompanying file LICENSE for details.

#[macro_export]
macro_rules! cubeb_log_internal {
    ($level: expr, $msg: expr) => {
        #[allow(unused_unsafe)]
        unsafe {
            if $level <= $crate::ffi::g_cubeb_log_level.into() {
                cubeb_log_internal!(__INTERNAL__ $msg);
            }
        }
    };
    ($level: expr, $fmt: expr, $($arg: expr),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            if $level <= $crate::ffi::g_cubeb_log_level.into() {
                cubeb_log_internal!(__INTERNAL__ format!($fmt, $($arg),*));
            }
        }
    };
    (__INTERNAL__ $msg: expr) => {
        if let Some(log_callback) = $crate::ffi::g_cubeb_log_callback {
            use std::io::Write;

            let mut buf = [0 as u8; 1024];
            let filename = std::path::Path::new(file!())
                .file_name()
                .unwrap()
                .to_str()
                .unwrap();
            // 2 for ':', 1 for ' ', 1 for '\n', and 1 for converting `line!()` to number of digits
            let len = filename.len() + ((line!() as f32).log10().trunc() as usize) + $msg.len() + 5;
            debug_assert!(len < buf.len(), "log will be truncated");
            let _ = write!(&mut buf[..], "{}:{}: {}\n", filename, line!(), $msg);
            let last = std::cmp::min(len, buf.len() - 1);
            buf[last] = 0;
            let cstr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..last + 1]) };
            log_callback(cstr.as_ptr());
        }
    }
}

#[macro_export]
macro_rules! cubeb_logv {
    ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Verbose, $msg));
    ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Verbose, $fmt, $($arg),*));
}

#[macro_export]
macro_rules! cubeb_log {
    ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Normal, $msg));
    ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Normal, $fmt, $($arg),*));
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_normal_logging() {
        cubeb_log!("This is log at normal level");
        cubeb_log!("{} Formatted log", 1);
        cubeb_log!("{} Formatted {} log {}", 1, 2, 3);
    }

    #[test]
    fn test_verbose_logging() {
        cubeb_logv!("This is a log at verbose level");
        cubeb_logv!("{} Formatted log", 1);
        cubeb_logv!("{} Formatted {} log {}", 1, 2, 3);
    }
}
<commit_msg>Silence clippy range_plus_one<commit_after>// Copyright © 2017-2018 Mozilla Foundation
//
// This program is made available under an ISC-style license.  See the
// accompanying file LICENSE for details.

#[macro_export]
macro_rules! cubeb_log_internal {
    ($level: expr, $msg: expr) => {
        #[allow(unused_unsafe)]
        unsafe {
            if $level <= $crate::ffi::g_cubeb_log_level.into() {
                cubeb_log_internal!(__INTERNAL__ $msg);
            }
        }
    };
    ($level: expr, $fmt: expr, $($arg: expr),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            if $level <= $crate::ffi::g_cubeb_log_level.into() {
                cubeb_log_internal!(__INTERNAL__ format!($fmt, $($arg),*));
            }
        }
    };
    (__INTERNAL__ $msg: expr) => {
        if let Some(log_callback) = $crate::ffi::g_cubeb_log_callback {
            use std::io::Write;

            let mut buf = [0 as u8; 1024];
            let filename = std::path::Path::new(file!())
                .file_name()
                .unwrap()
                .to_str()
                .unwrap();
            // 2 for ':', 1 for ' ', 1 for '\n', and 1 for converting `line!()` to number of digits
            let len = filename.len() + ((line!() as f32).log10().trunc() as usize) + $msg.len() + 5;
            debug_assert!(len < buf.len(), "log will be truncated");
            let _ = write!(&mut buf[..], "{}:{}: {}\n", filename, line!(), $msg);
            let last = std::cmp::min(len, buf.len() - 1);
            buf[last] = 0;
            let cstr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..=last]) };
            log_callback(cstr.as_ptr());
        }
    }
}

#[macro_export]
macro_rules! cubeb_logv {
    ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Verbose, $msg));
    ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Verbose, $fmt, $($arg),*));
}

#[macro_export]
macro_rules! cubeb_log {
    ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Normal, $msg));
    ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Normal, $fmt, $($arg),*));
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_normal_logging() {
        cubeb_log!("This is log at normal level");
        cubeb_log!("{} Formatted log", 1);
        cubeb_log!("{} Formatted {} log {}", 1, 2, 3);
    }

    #[test]
    fn test_verbose_logging() {
        cubeb_logv!("This is a log at verbose level");
        cubeb_logv!("{} Formatted log", 1);
        cubeb_logv!("{} Formatted {} log {}", 1, 2, 3);
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::PerfLogger;
use lsp_types::{
    notification::{Exit, Notification},
    request::{Request, Shutdown},
};
use schema_documentation::SchemaDocumentation;

use crate::{lsp_runtime_error::LSPRuntimeResult, server::LSPState};

pub(crate) fn on_shutdown<
    TPerfLogger: PerfLogger + 'static,
    TSchemaDocumentation: SchemaDocumentation,
>(
    _state: &LSPState<TPerfLogger, TSchemaDocumentation>,
    _params: <Shutdown as Request>::Params,
) -> LSPRuntimeResult<<Shutdown as Request>::Result> {
    std::process::exit(0);
}

pub(crate) fn on_exit<
    TPerfLogger: PerfLogger + 'static,
    TSchemaDocumentation: SchemaDocumentation,
>(
    _state: &LSPState<TPerfLogger, TSchemaDocumentation>,
    _params: <Exit as Notification>::Params,
) -> LSPRuntimeResult<()> {
    std::process::exit(0);
}
<commit_msg>Use GlobalState trait in shutdown.rs<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use lsp_types::{
    notification::{Exit, Notification},
    request::{Request, Shutdown},
};

use crate::{lsp_runtime_error::LSPRuntimeResult, server::GlobalState};

pub(crate) fn on_shutdown(
    _state: &impl GlobalState,
    _params: <Shutdown as Request>::Params,
) -> LSPRuntimeResult<<Shutdown as Request>::Result> {
    std::process::exit(0);
}

pub(crate) fn on_exit(
    _state: &impl GlobalState,
    _params: <Exit as Notification>::Params,
) -> LSPRuntimeResult<()> {
    std::process::exit(0);
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::sys::ShutdownMethod;
use habitat_core::os::process::{
    handle_from_pid,
    windows_child::{ExitStatus, Handle},
    Pid,
};
use std::{collections::HashMap, io, mem, time::Duration as StdDuration};
use time::{Duration, SteadyTime};
use winapi::{
    shared::minwindef::{DWORD, LPDWORD, MAX_PATH},
    um::{
        handleapi::{self, INVALID_HANDLE_VALUE},
        processthreadsapi,
        tlhelp32::{self, LPPROCESSENTRY32W, PROCESSENTRY32W, TH32CS_SNAPPROCESS},
        wincon,
    },
};

const PROCESS_ACTIVE: u32 = 259;
type ProcessTable = HashMap<DWORD, Vec<DWORD>>;

/// Kill a service process
pub fn kill(pid: Pid) -> ShutdownMethod {
    match handle_from_pid(pid) {
        None => {
            // Assume it's already gone if we can't resolve a proper process handle
            ShutdownMethod::AlreadyExited
        }
        Some(handle_ptr) => {
            let mut process = Process::new(Handle::new(handle_ptr));
            process.kill()
        }
    }
}

///////////////////////////////////////////////////////////////////////
// Private Code

struct Process {
    handle: Handle,
    last_status: Option<ExitStatus>,
}

impl Process {
    fn new(handle: Handle) -> Self {
        Process {
            handle: handle,
            last_status: None,
        }
    }

    fn id(&self) -> u32 {
        unsafe { processthreadsapi::GetProcessId(self.handle.raw()) as u32 }
    }

    /// Attempt to gracefully terminate a process and then forcefully kill it after
    /// 8 seconds if it has not terminated.
    fn kill(&mut self) -> ShutdownMethod {
        if self.status().is_some() {
            return ShutdownMethod::AlreadyExited;
        }
        let ret = unsafe { wincon::GenerateConsoleCtrlEvent(1, self.id()) };
        if ret == 0 {
            debug!(
                "Failed to send ctrl-break to pid {}: {}",
                self.id(),
                io::Error::last_os_error()
            );
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if ret == 0 || SteadyTime::now() > stop_time {
                let proc_table = build_proc_table();
                terminate_process_descendants(&proc_table, self.id());
                return ShutdownMethod::Killed;
            }

            if self.status().is_some() {
                return ShutdownMethod::GracefulTermination;
            }
            thread::sleep(StdDuration::from_millis(5));
        }
    }

    fn status(&mut self) -> Option<ExitStatus> {
        if self.last_status.is_some() {
            return self.last_status;
        }
        match exit_code(&self.handle) {
            Some(PROCESS_ACTIVE) => None,
            Some(code) => {
                self.last_status = Some(ExitStatus::from(code));
                self.last_status
            }
            None => None,
        }
    }
}

fn build_proc_table() -> ProcessTable {
    let processes_snap_handle =
        unsafe { tlhelp32::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        error!(
            "Failed to call CreateToolhelp32Snapshot: {}",
            io::Error::last_os_error()
        );
        return ProcessTable::new();
    }
    let mut table = ProcessTable::new();
    let mut process_entry = PROCESSENTRY32W {
        dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };
    // Get the first process from the snapshot.
    match unsafe {
        tlhelp32::Process32FirstW(
            processes_snap_handle,
            &mut process_entry as LPPROCESSENTRY32W,
        )
    } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success: i32 = 1;
            // Loop through all processes until we find one where `szExeFile` == `name`.
            while process_success == 1 {
                let children = table
                    .entry(process_entry.th32ParentProcessID)
                    .or_insert(Vec::new());
                (*children).push(process_entry.th32ProcessID);
                process_success =
                    unsafe { tlhelp32::Process32NextW(processes_snap_handle, &mut process_entry) };
            }
            unsafe { handleapi::CloseHandle(processes_snap_handle) };
        }
        0 | _ => unsafe {
            handleapi::CloseHandle(processes_snap_handle);
        },
    }
    table
}

fn exit_code(handle: &Handle) -> Option<u32> {
    let mut exit_code: u32 = 0;
    unsafe {
        let ret = processthreadsapi::GetExitCodeProcess(handle.raw(), &mut exit_code as LPDWORD);
        if ret == 0 {
            error!(
                "Failed to retrieve Exit Code: {}",
                io::Error::last_os_error()
            );
            return None;
        }
    }
    Some(exit_code)
}

fn terminate_process_descendants(table: &ProcessTable, pid: DWORD) {
    if let Some(children) = table.get(&pid) {
        for child in children {
            terminate_process_descendants(table, child.clone());
        }
    }
    unsafe {
        match handle_from_pid(pid) {
            Some(h) => {
                if processthreadsapi::TerminateProcess(h, 1) == 0 {
                    error!(
                        "Failed to call TerminateProcess on pid {}: {}",
                        pid,
                        io::Error::last_os_error()
                    );
                }
            }
            None => {}
        }
    }
}
<commit_msg>[SHAME] Actually use std::thread on Windows<commit_after>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::sys::ShutdownMethod;
use habitat_core::os::process::{
    handle_from_pid,
    windows_child::{ExitStatus, Handle},
    Pid,
};
use std::{collections::HashMap, io, mem, thread, time::Duration as StdDuration};
use time::{Duration, SteadyTime};
use winapi::{
    shared::minwindef::{DWORD, LPDWORD, MAX_PATH},
    um::{
        handleapi::{self, INVALID_HANDLE_VALUE},
        processthreadsapi,
        tlhelp32::{self, LPPROCESSENTRY32W, PROCESSENTRY32W, TH32CS_SNAPPROCESS},
        wincon,
    },
};

const PROCESS_ACTIVE: u32 = 259;
type ProcessTable = HashMap<DWORD, Vec<DWORD>>;

/// Kill a service process
pub fn kill(pid: Pid) -> ShutdownMethod {
    match handle_from_pid(pid) {
        None => {
            // Assume it's already gone if we can't resolve a proper process handle
            ShutdownMethod::AlreadyExited
        }
        Some(handle_ptr) => {
            let mut process = Process::new(Handle::new(handle_ptr));
            process.kill()
        }
    }
}

///////////////////////////////////////////////////////////////////////
// Private Code

struct Process {
    handle: Handle,
    last_status: Option<ExitStatus>,
}

impl Process {
    fn new(handle: Handle) -> Self {
        Process {
            handle: handle,
            last_status: None,
        }
    }

    fn id(&self) -> u32 {
        unsafe { processthreadsapi::GetProcessId(self.handle.raw()) as u32 }
    }

    /// Attempt to gracefully terminate a process and then forcefully kill it after
    /// 8 seconds if it has not terminated.
    fn kill(&mut self) -> ShutdownMethod {
        if self.status().is_some() {
            return ShutdownMethod::AlreadyExited;
        }
        let ret = unsafe { wincon::GenerateConsoleCtrlEvent(1, self.id()) };
        if ret == 0 {
            debug!(
                "Failed to send ctrl-break to pid {}: {}",
                self.id(),
                io::Error::last_os_error()
            );
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if ret == 0 || SteadyTime::now() > stop_time {
                let proc_table = build_proc_table();
                terminate_process_descendants(&proc_table, self.id());
                return ShutdownMethod::Killed;
            }

            if self.status().is_some() {
                return ShutdownMethod::GracefulTermination;
            }
            thread::sleep(StdDuration::from_millis(5));
        }
    }

    fn status(&mut self) -> Option<ExitStatus> {
        if self.last_status.is_some() {
            return self.last_status;
        }
        match exit_code(&self.handle) {
            Some(PROCESS_ACTIVE) => None,
            Some(code) => {
                self.last_status = Some(ExitStatus::from(code));
                self.last_status
            }
            None => None,
        }
    }
}

fn build_proc_table() -> ProcessTable {
    let processes_snap_handle =
        unsafe { tlhelp32::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        error!(
            "Failed to call CreateToolhelp32Snapshot: {}",
            io::Error::last_os_error()
        );
        return ProcessTable::new();
    }
    let mut table = ProcessTable::new();
    let mut process_entry = PROCESSENTRY32W {
        dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };
    // Get the first process from the snapshot.
    match unsafe {
        tlhelp32::Process32FirstW(
            processes_snap_handle,
            &mut process_entry as LPPROCESSENTRY32W,
        )
    } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success: i32 = 1;
            // Loop through all processes until we find one where `szExeFile` == `name`.
            while process_success == 1 {
                let children = table
                    .entry(process_entry.th32ParentProcessID)
                    .or_insert(Vec::new());
                (*children).push(process_entry.th32ProcessID);
                process_success =
                    unsafe { tlhelp32::Process32NextW(processes_snap_handle, &mut process_entry) };
            }
            unsafe { handleapi::CloseHandle(processes_snap_handle) };
        }
        0 | _ => unsafe {
            handleapi::CloseHandle(processes_snap_handle);
        },
    }
    table
}

fn exit_code(handle: &Handle) -> Option<u32> {
    let mut exit_code: u32 = 0;
    unsafe {
        let ret = processthreadsapi::GetExitCodeProcess(handle.raw(), &mut exit_code as LPDWORD);
        if ret == 0 {
            error!(
                "Failed to retrieve Exit Code: {}",
                io::Error::last_os_error()
            );
            return None;
        }
    }
    Some(exit_code)
}

fn terminate_process_descendants(table: &ProcessTable, pid: DWORD) {
    if let Some(children) = table.get(&pid) {
        for child in children {
            terminate_process_descendants(table, child.clone());
        }
    }
    unsafe {
        match handle_from_pid(pid) {
            Some(h) => {
                if processthreadsapi::TerminateProcess(h, 1) == 0 {
                    error!(
                        "Failed to call TerminateProcess on pid {}: {}",
                        pid,
                        io::Error::last_os_error()
                    );
                }
            }
            None => {}
        }
    }
}
<|endoftext|>


<commit_before>use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Pat};
use syntax::T;

use crate::{AssistContext, AssistId, AssistKind, Assists};

/// Gets a list of binders in a pattern, and whether they are mut.
fn binders_in_pat(pat: &Pat) -> Option<Vec<(String, bool)>> {
    use Pat::*;
    match pat {
        IdentPat(p) => {
            let ident = p.name()?.text().to_string();
            let ismut = p.ref_token().is_none() && p.mut_token().is_some();
            let mut res = vec![(ident, ismut)];
            if let Some(inner) = p.pat() {
                res.extend(binders_in_pat(&inner)?);
            }
            Some(res)
        }
        BoxPat(p) => p.pat().and_then(|p| binders_in_pat(&p)),
        RestPat(_) | LiteralPat(_) | PathPat(_) | WildcardPat(_) | ConstBlockPat(_) => Some(vec![]),
        OrPat(p) => {
            let mut v = vec![];
            for p in p.pats() {
                v.extend(binders_in_pat(&p)?);
            }
            Some(v)
        }
        ParenPat(p) => p.pat().and_then(|p| binders_in_pat(&p)),
        RangePat(p) => {
            let mut start = if let Some(st) = p.start() { binders_in_pat(&st)? } else { vec![] };
            let end = if let Some(st) = p.end() { binders_in_pat(&st)? } else { vec![] };
            start.extend(end);
            Some(start)
        }
        RecordPat(p) => {
            let mut v = vec![];
            for f in p.record_pat_field_list()?.fields() {
                let pat = f.pat()?;
                v.extend(binders_in_pat(&pat)?);
            }
            Some(v)
        }
        RefPat(p) => p.pat().and_then(|p| binders_in_pat(&p)),
        SlicePat(p) => {
            let mut v = vec![];
            for p in p.pats() {
                v.extend(binders_in_pat(&p)?);
            }
            Some(v)
        }
        TuplePat(p) => {
            let mut v = vec![];
            for p in p.fields() {
                v.extend(binders_in_pat(&p)?);
            }
            Some(v)
        }
        TupleStructPat(p) => {
            let mut v = vec![];
            for p in p.fields() {
                v.extend(binders_in_pat(&p)?);
            }
            Some(v)
        }
        // don't support macro pat yet
        MacroPat(_) => None,
    }
}

fn binders_to_str(binders: &[(String, bool)], addmut: bool) -> String {
    let vars = binders
        .iter()
        .map(
            |(ident, ismut)| {
                if *ismut && addmut {
                    format!("mut {}", ident)
                } else {
                    ident.to_string()
                }
            },
        )
        .collect::<Vec<_>>()
        .join(", ");
    if binders.is_empty() {
        String::from("{}")
    } else if binders.len() == 1 {
        vars
    } else {
        format!("({})", vars)
    }
}

// Assist: convert_let_else_to_match
//
// Converts let-else statement to let statement and match expression.
//
// ```
// fn main() {
//     let Ok(mut x) = f() else$0 { return };
// }
// ```
// ->
// ```
// fn main() {
//     let mut x = match f() {
//         Ok(x) => x,
//         _ => return,
//     };
// }
// ```
pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    // should focus on else token to trigger
    let else_token = ctx.find_token_syntax_at_offset(T![else])?;
    let let_stmt = LetStmt::cast(else_token.parent()?.parent()?)?;
    let let_else_block = let_stmt.let_else()?.block_expr()?;
    let let_init = let_stmt.initializer()?;
    if let_stmt.ty().is_some() {
        // don't support let with type annotation
        return None;
    }
    let pat = let_stmt.pat()?;
    let binders = binders_in_pat(&pat)?;

    let target = let_stmt.syntax().text_range();
    acc.add(
        AssistId("convert_let_else_to_match", AssistKind::RefactorRewrite),
        "Convert let-else to let and match",
        target,
        |edit| {
            let indent_level = let_stmt.indent_level().0 as usize;
            let indent = "    ".repeat(indent_level);
            let indent1 = "    ".repeat(indent_level + 1);

            let binders_str = binders_to_str(&binders, false);
            let binders_str_mut = binders_to_str(&binders, true);

            let init_expr = let_init.syntax().text();
            let mut pat_no_mut = pat.syntax().text().to_string();
            // remove the mut from the pattern
            for (b, ismut) in binders.iter() {
                if *ismut {
                    pat_no_mut = pat_no_mut.replace(&format!("mut {b}"), b);
                }
            }

            let only_expr = let_else_block.statements().next().is_none();
            let branch2 = match &let_else_block.tail_expr() {
                Some(tail) if only_expr => format!("{},", tail.syntax().text()),
                _ => let_else_block.syntax().text().to_string(),
            };
            let replace = if binders.is_empty() {
                format!(
                    "match {init_expr} {{
{indent1}{pat_no_mut} => {binders_str}
{indent1}_ => {branch2}
{indent}}}"
                )
            } else {
                format!(
                    "let {binders_str_mut} = match {init_expr} {{
{indent1}{pat_no_mut} => {binders_str},
{indent1}_ => {branch2}
{indent}}};"
                )
            };
            edit.replace(target, replace);
        },
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

    #[test]
    fn convert_let_else_to_match_no_type_let() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let 1: u32 = v.iter().sum() else$0 { return };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_on_else() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(x) = f() else {$0 return };
}
            "#,
        );
    }

    #[test]
    fn convert_let_else_to_match_no_macropat() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let m!() = g() else$0 { return };
}
            "#,
        );
    }

    #[test]
    fn convert_let_else_to_match_target() {
        check_assist_target(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(x) = f() else$0 { continue };
}",
            "let Ok(x) = f() else { continue };",
        );
    }

    #[test]
    fn convert_let_else_to_match_basic() {
        check_assist(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(x) = f() else$0 { continue };
}",
            r"
fn main() {
    let x = match f() {
        Ok(x) => x,
        _ => continue,
    };
}",
        );
    }

    #[test]
    fn convert_let_else_to_match_mut() {
        check_assist(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(mut x) = f() el$0se { continue };
}",
            r"
fn main() {
    let mut x = match f() {
        Ok(x) => x,
        _ => continue,
    };
}",
        );
    }

    #[test]
    fn convert_let_else_to_match_multi_binders() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
}"#,
            r#"
fn main() {
    let (x, y) = match f() {
        ControlFlow::Break((x, "tag", y, ..)) => (x, y),
        _ => { g(); return }
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_slice() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [one, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (one, other) = match f() {
        [one, 1001, other] => (one, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_struct() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (it, other) = match f() {
        [Struct { inner: Some(it) }, 1001, other] => (it, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_struct_ident_pat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [Struct { inner }, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (inner, other) = match f() {
        [Struct { inner }, 1001, other] => (inner, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_no_binder() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let (8 | 9) = f() else$0 { panic!() };
}"#,
            r#"
fn main() {
    match f() {
        (8 | 9) => {}
        _ => panic!(),
    }
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_range() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let 1.. = f() e$0lse { return };
}"#,
            r#"
fn main() {
    match f() {
        1.. => {}
        _ => return,
    }
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_refpat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(&mut x) = f(&mut 0) else$0 { return };
}"#,
            r#"
fn main() {
    let x = match f(&mut 0) {
        Ok(&mut x) => x,
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_refmut() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(ref mut x) = f() else$0 { return };
}"#,
            r#"
fn main() {
    let x = match f() {
        Ok(ref mut x) => x,
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_atpat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let out @ Ok(ins) = f() else$0 { return };
}"#,
            r#"
fn main() {
    let (out, ins) = match f() {
        out @ Ok(ins) => (out, ins),
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_complex_init() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let v = vec![1, 2, 3];
    let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
}"#,
            r#"
fn main() {
    let v = vec![1, 2, 3];
    let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
        &[x, y, ..] => (x, y),
        _ => return,
    };
}"#,
        );
    }
}
<commit_msg>Avoid allocations<commit_after>use hir::Semantics;
use ide_db::RootDatabase;
use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Name, Pat};
use syntax::T;

use crate::{AssistContext, AssistId, AssistKind, Assists};

/// Gets a list of binders in a pattern, and whether they are mut.
fn binders_in_pat(
    acc: &mut Vec<(Name, bool)>,
    pat: &Pat,
    sem: &Semantics<RootDatabase>,
) -> Option<()> {
    use Pat::*;
    match pat {
        IdentPat(p) => {
            let ident = p.name()?;
            let ismut = p.ref_token().is_none() && p.mut_token().is_some();
            acc.push((ident, ismut));
            if let Some(inner) = p.pat() {
                binders_in_pat(acc, &inner, sem)?;
            }
            Some(())
        }
        BoxPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
        RestPat(_) | LiteralPat(_) | PathPat(_) | WildcardPat(_) | ConstBlockPat(_) => Some(()),
        OrPat(p) => {
            for p in p.pats() {
                binders_in_pat(acc, &p, sem)?;
            }
            Some(())
        }
        ParenPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
        RangePat(p) => {
            if let Some(st) = p.start() {
                binders_in_pat(acc, &st, sem)?
            }
            if let Some(ed) = p.end() {
                binders_in_pat(acc, &ed, sem)?
            }
            Some(())
        }
        RecordPat(p) => {
            for f in p.record_pat_field_list()?.fields() {
                let pat = f.pat()?;
                binders_in_pat(acc, &pat, sem)?;
            }
            Some(())
        }
        RefPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
        SlicePat(p) => {
            for p in p.pats() {
                binders_in_pat(acc, &p, sem)?;
            }
            Some(())
        }
        TuplePat(p) => {
            for p in p.fields() {
                binders_in_pat(acc, &p, sem)?;
            }
            Some(())
        }
        TupleStructPat(p) => {
            for p in p.fields() {
                binders_in_pat(acc, &p, sem)?;
            }
            Some(())
        }
        // don't support macro pat yet
        MacroPat(_) => None,
    }
}

fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
    let vars = binders
        .iter()
        .map(
            |(ident, ismut)| {
                if *ismut && addmut {
                    format!("mut {}", ident)
                } else {
                    ident.to_string()
                }
            },
        )
        .collect::<Vec<_>>()
        .join(", ");
    if binders.is_empty() {
        String::from("{}")
    } else if binders.len() == 1 {
        vars
    } else {
        format!("({})", vars)
    }
}

// Assist: convert_let_else_to_match
//
// Converts let-else statement to let statement and match expression.
//
// ```
// fn main() {
//     let Ok(mut x) = f() else$0 { return };
// }
// ```
// ->
// ```
// fn main() {
//     let mut x = match f() {
//         Ok(x) => x,
//         _ => return,
//     };
// }
// ```
pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    // should focus on else token to trigger
    let else_token = ctx.find_token_syntax_at_offset(T![else])?;
    let let_stmt = LetStmt::cast(else_token.parent()?.parent()?)?;
    let let_else_block = let_stmt.let_else()?.block_expr()?;
    let let_init = let_stmt.initializer()?;
    if let_stmt.ty().is_some() {
        // don't support let with type annotation
        return None;
    }
    let pat = let_stmt.pat()?;
    let mut binders = Vec::new();
    binders_in_pat(&mut binders, &pat, &ctx.sema)?;

    let target = let_stmt.syntax().text_range();
    acc.add(
        AssistId("convert_let_else_to_match", AssistKind::RefactorRewrite),
        "Convert let-else to let and match",
        target,
        |edit| {
            let indent_level = let_stmt.indent_level().0 as usize;
            let indent = "    ".repeat(indent_level);
            let indent1 = "    ".repeat(indent_level + 1);

            let binders_str = binders_to_str(&binders, false);
            let binders_str_mut = binders_to_str(&binders, true);

            let init_expr = let_init.syntax().text();
            let mut pat_no_mut = pat.syntax().text().to_string();
            // remove the mut from the pattern
            for (b, ismut) in binders.iter() {
                if *ismut {
                    pat_no_mut = pat_no_mut.replace(&format!("mut {b}"), &b.to_string());
                }
            }

            let only_expr = let_else_block.statements().next().is_none();
            let branch2 = match &let_else_block.tail_expr() {
                Some(tail) if only_expr => format!("{},", tail.syntax().text()),
                _ => let_else_block.syntax().text().to_string(),
            };
            let replace = if binders.is_empty() {
                format!(
                    "match {init_expr} {{
{indent1}{pat_no_mut} => {binders_str}
{indent1}_ => {branch2}
{indent}}}"
                )
            } else {
                format!(
                    "let {binders_str_mut} = match {init_expr} {{
{indent1}{pat_no_mut} => {binders_str},
{indent1}_ => {branch2}
{indent}}};"
                )
            };
            edit.replace(target, replace);
        },
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

    #[test]
    fn convert_let_else_to_match_no_type_let() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let 1: u32 = v.iter().sum() else$0 { return };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_on_else() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(x) = f() else {$0 return };
}
            "#,
        );
    }

    #[test]
    fn convert_let_else_to_match_no_macropat() {
        check_assist_not_applicable(
            convert_let_else_to_match,
            r#"
fn main() {
    let m!() = g() else$0 { return };
}
            "#,
        );
    }

    #[test]
    fn convert_let_else_to_match_target() {
        check_assist_target(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(x) = f() else$0 { continue };
}",
            "let Ok(x) = f() else { continue };",
        );
    }

    #[test]
    fn convert_let_else_to_match_basic() {
        check_assist(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(x) = f() else$0 { continue };
}",
            r"
fn main() {
    let x = match f() {
        Ok(x) => x,
        _ => continue,
    };
}",
        );
    }

    #[test]
    fn convert_let_else_to_match_mut() {
        check_assist(
            convert_let_else_to_match,
            r"
fn main() {
    let Ok(mut x) = f() el$0se { continue };
}",
            r"
fn main() {
    let mut x = match f() {
        Ok(x) => x,
        _ => continue,
    };
}",
        );
    }

    #[test]
    fn convert_let_else_to_match_multi_binders() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
}"#,
            r#"
fn main() {
    let (x, y) = match f() {
        ControlFlow::Break((x, "tag", y, ..)) => (x, y),
        _ => { g(); return }
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_slice() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [one, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (one, other) = match f() {
        [one, 1001, other] => (one, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_struct() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (it, other) = match f() {
        [Struct { inner: Some(it) }, 1001, other] => (it, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_struct_ident_pat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let [Struct { inner }, 1001, other] = f() else$0 { break };
}"#,
            r#"
fn main() {
    let (inner, other) = match f() {
        [Struct { inner }, 1001, other] => (inner, other),
        _ => break,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_no_binder() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let (8 | 9) = f() else$0 { panic!() };
}"#,
            r#"
fn main() {
    match f() {
        (8 | 9) => {}
        _ => panic!(),
    }
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_range() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let 1.. = f() e$0lse { return };
}"#,
            r#"
fn main() {
    match f() {
        1.. => {}
        _ => return,
    }
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_refpat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(&mut x) = f(&mut 0) else$0 { return };
}"#,
            r#"
fn main() {
    let x = match f(&mut 0) {
        Ok(&mut x) => x,
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_refmut() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let Ok(ref mut x) = f() else$0 { return };
}"#,
            r#"
fn main() {
    let x = match f() {
        Ok(ref mut x) => x,
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_atpat() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let out @ Ok(ins) = f() else$0 { return };
}"#,
            r#"
fn main() {
    let (out, ins) = match f() {
        out @ Ok(ins) => (out, ins),
        _ => return,
    };
}"#,
        );
    }

    #[test]
    fn convert_let_else_to_match_complex_init() {
        check_assist(
            convert_let_else_to_match,
            r#"
fn main() {
    let v = vec![1, 2, 3];
    let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
}"#,
            r#"
fn main() {
    let v = vec![1, 2, 3];
    let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
        &[x, y, ..] => (x, y),
        _ => return,
    };
}"#,
        );
    }
}
<|endoftext|>