xshell 0.1.3

Utilities for quick shell scripting in Rust
Documentation
use std::{ffi::OsStr, thread};

use xshell::{cmd, cwd, pushd, pushenv, read_file};

#[test]
fn smoke() {
    let output = cmd!("echo hello").read().unwrap();
    assert_eq!(output, "hello");
}

#[test]
fn multiline() {
    let output = cmd!(
        "
        echo hello
        "
    )
    .read()
    .unwrap();
    assert_eq!(output, "hello");
}

#[test]
fn interpolation() {
    let hello = "hello";
    let output = cmd!("echo {hello}").read().unwrap();
    assert_eq!(output, "hello");
}

#[test]
fn program_interpolation() {
    let echo = "echo";
    let output = cmd!("{echo} hello").read().unwrap();
    assert_eq!(output, "hello");
}

#[test]
fn interpolation_concatenation() {
    let hello = "hello";
    let world = "world";
    let output = cmd!("echo {hello}-{world}").read().unwrap();
    assert_eq!(output, "hello-world")
}

#[test]
fn interpolation_move() {
    let hello = "hello".to_string();
    let output1 = cmd!("echo {hello}").read().unwrap();
    let output2 = cmd!("echo {hello}").read().unwrap();
    assert_eq!(output1, output2)
}

#[test]
fn interpolation_spat() {
    let a = &["hello", "world"];
    let b: &[&OsStr] = &[];
    let c = &["!".to_string()];
    let output = cmd!("echo {a...} {b...} {c...}").read().unwrap();
    assert_eq!(output, "hello world !")
}

#[test]
fn exit_status() {
    let err = cmd!("false").read().unwrap_err();
    assert_eq!(err.to_string(), "command `false` failed, exit code: 1");
}

#[test]
fn unknown_command() {
    let err = cmd!("nope no way").read().unwrap_err();
    assert_eq!(err.to_string(), "command not found: `nope`");
}

#[test]
fn args_with_spaces() {
    let hello_world = "hello world";
    let cmd = cmd!("echo {hello_world} 'hello world' hello world");
    assert_eq!(cmd.to_string(), r#"echo "hello world" "hello world" hello world"#)
}

#[test]
fn escape() {
    let output = cmd!("echo \\hello\\ '\\world\\'").read().unwrap();
    assert_eq!(output, r#"\hello\ \world\"#)
}

#[test]
fn stdin_redirection() {
    let lines = "\
foo
baz
bar
";
    let output = cmd!("sort").stdin(lines).read().unwrap();
    assert_eq!(
        output,
        "\
bar
baz
foo"
    )
}

#[test]
fn test_pushd() {
    let d1 = cwd().unwrap();
    {
        let _p = pushd("xshell-macros").unwrap();
        let d2 = cwd().unwrap();
        assert_eq!(d2, d1.join("xshell-macros"));
        {
            let _p = pushd("src").unwrap();
            let d3 = cwd().unwrap();
            assert_eq!(d3, d1.join("xshell-macros/src"));
        }
        let d4 = cwd().unwrap();
        assert_eq!(d4, d1.join("xshell-macros"));
    }
    let d5 = cwd().unwrap();
    assert_eq!(d5, d1);
}

#[test]
fn pushd_parent_dir() {
    let current = cwd().unwrap();
    let dirname = current.file_name().unwrap();
    let _d = pushd("..").unwrap();
    let _d = pushd(dirname).unwrap();
    assert_eq!(cwd().unwrap(), current);
}

#[test]
fn test_pushd_lock() {
    let t1 = thread::spawn(|| {
        let _p = pushd("cbench").unwrap();
        sleep_ms(20);
    });
    sleep_ms(10);

    let t2 = thread::spawn(|| {
        let _p = pushd("cbench").unwrap();
        sleep_ms(30);
    });

    t1.join().unwrap();
    t2.join().unwrap();
}

const VAR: &str = "SPICA";

#[test]
fn test_pushenv() {
    let e1 = std::env::var_os(VAR);
    {
        let _e = pushenv(VAR, "1");
        let e2 = std::env::var_os(VAR);
        assert_eq!(e2, Some("1".into()));
        {
            let _e = pushenv(VAR, "2");
            let e3 = std::env::var_os(VAR);
            assert_eq!(e3, Some("2".into()));
        }
        let e4 = std::env::var_os(VAR);
        assert_eq!(e4, e2);
    }
    let e5 = std::env::var_os(VAR);
    assert_eq!(e5, e1);
}

#[test]
fn test_pushenv_lock() {
    let t1 = thread::spawn(|| {
        let _e = pushenv(VAR, "hello");
        sleep_ms(20);
    });
    sleep_ms(10);

    let t2 = thread::spawn(|| {
        let _e = pushenv(VAR, "world");
        sleep_ms(30);
    });

    t1.join().unwrap();
    t2.join().unwrap();
}

#[test]
fn versions_match() {
    let read_version = |path: &str| {
        let text = read_file(path).unwrap();
        text.lines().find(|it| it.starts_with("version =")).unwrap().trim().to_string()
    };

    let v1 = read_version("./Cargo.toml");
    let v2 = read_version("./xshell-macros/Cargo.toml");
    assert_eq!(v1, v2);

    let cargo_toml = read_file("./Cargo.toml").unwrap();
    let dep = format!("xshell-macros = {{ {}", v1);
    assert!(cargo_toml.contains(&dep));
}

#[test]
fn formatting() {
    cmd!("cargo fmt --all -- --check").run().unwrap()
}

fn sleep_ms(ms: u64) {
    thread::sleep(std::time::Duration::from_millis(ms))
}