mxsh 0.2.0

Embeddable POSIX-style shell parser and runtime
Documentation
#![cfg(all(feature = "cli", feature = "test-support", feature = "unix-runtime"))]

mod support;

use std::fs;

use support::{run_shell, shell_quote, temp_path};

fn run_script(script: &str) -> std::process::Output {
    run_shell("mxsh", &["-c", script], "")
}

fn assert_success(output: &std::process::Output) {
    assert!(
        output.status.success(),
        "mxsh failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
        output.status.code(),
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr),
    );
}

#[test]
fn loop_input_redirection_feeds_condition_and_body() {
    let dir = temp_path("compound-redirection-input");
    fs::create_dir_all(&dir).expect("temp dir should be creatable");
    let input = dir.join("input");
    fs::write(&input, "a\nb\n").expect("input should be writable");

    let script = format!(
        "while IFS= read -r line; do printf '<%s>\\n' \"$line\"; done < {}",
        shell_quote(input.to_str().expect("temp path should be utf8")),
    );
    let output = run_script(&script);

    assert_success(&output);
    assert_eq!(String::from_utf8_lossy(&output.stdout), "<a>\n<b>\n");

    fs::remove_dir_all(&dir).expect("temp dir should be removable");
}

#[test]
fn compound_output_redirections_apply_to_whole_command_then_restore_stdout() {
    let dir = temp_path("compound-redirection-output");
    fs::create_dir_all(&dir).expect("temp dir should be creatable");
    let if_out = dir.join("if.out");
    let for_out = dir.join("for.out");
    let until_out = dir.join("until.out");
    let case_out = dir.join("case.out");

    let script = format!(
        "if printf cond; then printf body; fi > {}; \
         for x in a b; do printf '%s' \"$x\"; done > {}; \
         until false; do printf until; break; done > {}; \
         case x in x) printf case ;; esac > {}; \
         printf visible",
        shell_quote(if_out.to_str().expect("temp path should be utf8")),
        shell_quote(for_out.to_str().expect("temp path should be utf8")),
        shell_quote(until_out.to_str().expect("temp path should be utf8")),
        shell_quote(case_out.to_str().expect("temp path should be utf8")),
    );
    let output = run_script(&script);

    assert_success(&output);
    assert_eq!(String::from_utf8_lossy(&output.stdout), "visible");
    assert_eq!(fs::read_to_string(&if_out).expect("if output"), "condbody");
    assert_eq!(fs::read_to_string(&for_out).expect("for output"), "ab");
    assert_eq!(
        fs::read_to_string(&until_out).expect("until output"),
        "until"
    );
    assert_eq!(fs::read_to_string(&case_out).expect("case output"), "case");

    fs::remove_dir_all(&dir).expect("temp dir should be removable");
}