cargo-member 0.2.1

Cargo subcommand for managing workspace members.
Documentation
#![warn(rust_2018_idioms)]

use cargo_metadata::{Metadata, MetadataCommand};
use difference::assert_diff;
use duct::cmd;
use std::{
    env, fs, io,
    path::Path,
    str::{self, Utf8Error},
};
use tempdir::TempDir;
use termcolor::NoColor;

#[test]
fn normal() -> anyhow::Result<()> {
    let tempdir = TempDir::new("cargo-member-test-exclude-normal")?;

    fs::write(tempdir.path().join("Cargo.toml"), ORIGINAL)?;
    cargo_new(&tempdir.path().join("a"))?;
    cargo_new(&tempdir.path().join("b"))?;
    cargo_new(&tempdir.path().join("c"))?;
    let metadata = cargo_metadata(&tempdir.path().join("Cargo.toml"), &[])?;

    let mut stderr = vec![];

    cargo_member::Exclude::from_metadata(&metadata, &[tempdir.path().join("b")], &["c"])
        .dry_run(false)
        .stderr(NoColor::new(&mut stderr))
        .exec()?;

    assert_manifest(&tempdir.path().join("Cargo.toml"), EXPECTED_MANIFEST)?;
    assert_stderr(&stderr, EXPECTED_STDERR)?;
    cargo_metadata(&tempdir.path().join("Cargo.toml"), &["--locked"])?;
    return Ok(());

    static ORIGINAL: &str = r#"[workspace]
members = ["a", "b", "c"]
exclude = []
"#;

    static EXPECTED_MANIFEST: &str = r#"[workspace]
members = ["a"]
exclude = ["b", "c"]
"#;

    static EXPECTED_STDERR: &str = r#"    Removing "b" from `workspace.members`
      Adding "b" to `workspace.exclude`
    Removing "c" from `workspace.members`
      Adding "c" to `workspace.exclude`
"#;
}

#[test]
fn dry_run() -> anyhow::Result<()> {
    let tempdir = TempDir::new("cargo-member-test-exclude-dry-run")?;

    fs::write(tempdir.path().join("Cargo.toml"), MANIFEST)?;
    cargo_new(&tempdir.path().join("a"))?;
    cargo_new(&tempdir.path().join("b"))?;
    cargo_new(&tempdir.path().join("c"))?;
    let metadata = cargo_metadata(&tempdir.path().join("Cargo.toml"), &[])?;

    let mut stderr = vec![];

    cargo_member::Exclude::from_metadata(&metadata, &[tempdir.path().join("b")], &["c"])
        .dry_run(true)
        .stderr(NoColor::new(&mut stderr))
        .exec()?;

    assert_manifest(&tempdir.path().join("Cargo.toml"), MANIFEST)?;
    assert_stderr(&stderr, EXPECTED_STDERR)?;
    cargo_metadata(&tempdir.path().join("Cargo.toml"), &["--locked"])?;
    return Ok(());

    static MANIFEST: &str = r#"[workspace]
members = ["a", "b", "c"]
exclude = []
"#;

    static EXPECTED_STDERR: &str = r#"    Removing "b" from `workspace.members`
      Adding "b" to `workspace.exclude`
    Removing "c" from `workspace.members`
      Adding "c" to `workspace.exclude`
warning: `workspace` unchanged
warning: not modifying the manifest due to dry run
"#;
}

fn cargo_new(path: &Path) -> io::Result<()> {
    let cargo_exe = env::var("CARGO").unwrap();
    cmd!(cargo_exe, "new", "-q", "--vcs", "none", path).run()?;
    Ok(())
}

fn assert_manifest(manifest_path: &Path, expected: &str) -> io::Result<()> {
    let modified = fs::read_to_string(manifest_path)?;
    assert_diff!(expected, &modified, "\n", 0);
    Ok(())
}

fn assert_stderr(stderr: &[u8], expected: &str) -> std::result::Result<(), Utf8Error> {
    assert_diff!(expected, str::from_utf8(stderr)?, "\n", 0);
    Ok(())
}

fn cargo_metadata(manifest_path: &Path, opts: &[&str]) -> cargo_metadata::Result<Metadata> {
    let opts = opts
        .iter()
        .copied()
        .map(ToOwned::to_owned)
        .collect::<Vec<_>>();

    MetadataCommand::new()
        .manifest_path(manifest_path)
        .other_options(opts.iter().map(ToOwned::to_owned).collect::<Vec<_>>())
        .exec()
}