cargo-x 0.3.7

A very simple third-party cargo subcommand to execute a custom command.
Documentation
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::time::{SystemTime, UNIX_EPOCH};

struct TempDir {
    path: PathBuf,
}

impl TempDir {
    fn new(label: &str) -> Self {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let path = env::temp_dir()
            .join(format!("cargo-x-it-{}-{label}-{nanos}", std::process::id()));
        fs::create_dir_all(&path).unwrap();
        Self { path }
    }

    fn path(&self) -> &Path {
        &self.path
    }
}

impl Drop for TempDir {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.path);
    }
}

struct Fixture {
    home: TempDir,
    project: TempDir,
}

impl Fixture {
    fn new(label: &str) -> Self {
        let home = TempDir::new(&format!("{label}-home"));
        let project = TempDir::new(&format!("{label}-project"));
        fs::create_dir_all(project.path().join("src")).unwrap();
        fs::write(
            project.path().join("Cargo.toml"),
            "[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
        )
        .unwrap();
        fs::write(project.path().join("src/lib.rs"), "").unwrap();

        Self { home, project }
    }

    fn write_home_config(&self, contents: &str) {
        fs::write(self.home.path().join(".x.toml"), contents).unwrap();
    }

    fn write_project_config(&self, contents: &str) {
        fs::write(self.project.path().join("x.toml"), contents).unwrap();
    }

    fn write_manifest(&self, metadata: &str) {
        fs::write(
            self.project.path().join("Cargo.toml"),
            format!(
                "[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n{metadata}"
            ),
        )
        .unwrap();
    }

    fn cargo_x(&self, args: &[&str]) -> Output {
        self.run(env!("CARGO_BIN_EXE_cargo-x"), args)
    }

    fn x(&self, args: &[&str]) -> Output {
        self.run(env!("CARGO_BIN_EXE_x"), args)
    }

    fn run(&self, bin: &str, args: &[&str]) -> Output {
        let mut cmd = Command::new(bin);
        cmd.args(args)
            .current_dir(self.project.path())
            .env("HOME", self.home.path())
            .env("USERPROFILE", self.home.path());

        cmd.output().unwrap()
    }
}

#[cfg(unix)]
fn print_command(message: &str) -> String {
    format!("printf '{message}\\n'")
}

#[cfg(windows)]
fn print_command(message: &str) -> String {
    format!("echo {message}")
}

#[cfg(unix)]
fn exit_command(code: i32) -> String {
    format!("exit {code}")
}

#[cfg(windows)]
fn exit_command(code: i32) -> String {
    format!("exit /B {code}")
}

fn toml_string(value: &str) -> String {
    format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\""))
}

fn command_entry(name: &str, command: &str) -> String {
    format!("{name} = {}\n", toml_string(command))
}

fn stdout(output: &Output) -> String {
    String::from_utf8_lossy(&output.stdout).trim().to_owned()
}

fn stderr(output: &Output) -> String {
    String::from_utf8_lossy(&output.stderr).trim().to_owned()
}

#[test]
fn cargo_x_runs_command_from_project_config() {
    let fixture = Fixture::new("project-config");
    fixture.write_project_config(&command_entry(
        "say",
        &print_command("from-project"),
    ));

    let output = fixture.cargo_x(&["say"]);

    assert!(output.status.success(), "stderr: {}", stderr(&output));
    assert_eq!(stdout(&output), "from-project");
}

#[test]
fn cargo_subcommand_form_skips_the_extra_x_arg() {
    let fixture = Fixture::new("cargo-subcommand");
    fixture.write_project_config(&command_entry(
        "say",
        &print_command("from-cargo-x"),
    ));

    let output = fixture.cargo_x(&["x", "say"]);

    assert!(output.status.success(), "stderr: {}", stderr(&output));
    assert_eq!(stdout(&output), "from-cargo-x");
}

#[test]
fn x_binary_runs_command_from_project_config() {
    let fixture = Fixture::new("x-binary");
    fixture
        .write_project_config(&command_entry("say", &print_command("from-x")));

    let output = fixture.x(&["say"]);

    assert!(output.status.success(), "stderr: {}", stderr(&output));
    assert_eq!(stdout(&output), "from-x");
}

#[test]
fn manifest_metadata_overrides_project_and_home_config() {
    let fixture = Fixture::new("precedence");
    fixture.write_home_config(&command_entry(
        "shared",
        &print_command("from-home"),
    ));
    fixture.write_project_config(&command_entry(
        "shared",
        &print_command("from-project"),
    ));
    fixture.write_manifest(&format!(
        "[package.metadata.x]\n{}",
        command_entry("shared", &print_command("from-manifest"))
    ));

    let output = fixture.cargo_x(&["shared"]);

    assert!(output.status.success(), "stderr: {}", stderr(&output));
    assert_eq!(stdout(&output), "from-manifest");
}

#[test]
fn missing_command_fails_with_error_message() {
    let fixture = Fixture::new("missing-command");
    fixture.write_project_config(&command_entry(
        "say",
        &print_command("available"),
    ));

    let output = fixture.cargo_x(&["missing"]);

    assert_eq!(output.status.code(), Some(1));
    assert!(stderr(&output).contains("no such command <missing>"));
}

#[test]
fn configured_command_exit_status_is_propagated() {
    let fixture = Fixture::new("exit-status");
    fixture.write_project_config(&command_entry("fail", &exit_command(7)));

    let output = fixture.cargo_x(&["fail"]);

    assert_eq!(output.status.code(), Some(7));
}