mandate 0.1.0

Convert Markdown or YAML manuals into roff manpages
Documentation
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::time::{SystemTime, UNIX_EPOCH};

fn temp_dir() -> PathBuf {
    let mut path = std::env::temp_dir();
    let stamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    path.push(format!("mandate-test-{}-{}", std::process::id(), stamp));
    fs::create_dir_all(&path).expect("create temp dir");
    path
}

fn mandate_bin() -> PathBuf {
    if let Some(path) = option_env!("CARGO_BIN_EXE_mandate") {
        return PathBuf::from(path);
    }
    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    path.push("target");
    path.push("debug");
    if cfg!(windows) {
        path.push("mandate.exe");
    } else {
        path.push("mandate");
    }
    path
}

fn th_fields(roff: &str) -> Vec<String> {
    roff.lines()
        .find(|line| line.starts_with(".TH "))
        .map(|line| {
            line.split('"')
                .skip(1)
                .step_by(2)
                .map(str::to_string)
                .collect::<Vec<_>>()
        })
        .unwrap_or_default()
}

#[test]
fn cli_file_input_writes_output() {
    let dir = temp_dir();
    let input = dir.join("input.md");
    let output = dir.join("out.1");

    fs::write(&input, "# mandate(1) -- Example\n\nParagraph.\n").expect("write input");

    let status = Command::new(mandate_bin())
        .args([
            "-i",
            input.to_str().unwrap(),
            "-p",
            "mandate",
            "-s",
            "1",
            "-t",
            "Test",
            "-o",
            output.to_str().unwrap(),
        ])
        .status()
        .expect("run mandate");

    assert!(status.success());
    let roff = fs::read_to_string(output).expect("read output");
    let fields = th_fields(&roff);
    assert!(fields.len() >= 5);
    assert_eq!(fields[0], "mandate");
    assert_eq!(fields[1], "1");
    assert!(!fields[2].is_empty());
    assert_eq!(fields[4], "Test");
    assert!(roff.contains(".SH \"NAME\""));
}

#[test]
fn cli_stdin_input_writes_output() {
    let dir = temp_dir();
    let output = dir.join("out-stdin.1");

    let mut cmd = Command::new(mandate_bin());
    let mut child = cmd
        .args([
            "-i",
            "-",
            "-p",
            "mandate",
            "-s",
            "1",
            "-t",
            "Test",
            "-o",
            output.to_str().unwrap(),
        ])
        .stdin(Stdio::piped())
        .spawn()
        .expect("spawn mandate");

    {
        let stdin = child.stdin.as_mut().expect("stdin");
        stdin
            .write_all(b"# mandate(1) -- Example\n\nParagraph.\n")
            .expect("write stdin");
    }

    let status = child.wait().expect("wait");
    assert!(status.success());
    let roff = fs::read_to_string(output).expect("read output");
    let fields = th_fields(&roff);
    assert!(fields.len() >= 5);
    assert_eq!(fields[0], "mandate");
    assert_eq!(fields[1], "1");
    assert!(!fields[2].is_empty());
    assert_eq!(fields[4], "Test");
}