wsb 0.258.6441

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! The families a parity report compares.
//!
//! A family is a set of repositories that are meant to hold the same tests —
//! one library mirrored into several languages, each numbering its tests the
//! same way. Which repositories form one is a fact about the workspace, so the
//! workspace declares it, committed, in `<workspace root>/.wsb/test.json`:
//!
//! ```json
//! { "parity_families": { "lib": ["lib/lib-rs", "lib/lib-js", "lib/lib-py"] } }
//! ```
//!
//! Members are repository directories relative to the workspace root, as the
//! catalogue names repositories.

use std::collections::BTreeMap;

use crate::prelude::*;

/// Where the test tool's configuration lives, relative to the workspace root.
pub const FILE: &str = ".wsb/test.json";

#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct TestConfig {
    #[serde(default)]
    parity_families: BTreeMap<String, Vec<String>>,
}

/// Every family the workspace declares, by name.
pub fn declared(root: &Path) -> Result<BTreeMap<String, Vec<String>>> {
    let path = root.join(FILE);
    let text = match std::fs::read_to_string(&path) {
        Ok(text) => text,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(BTreeMap::new()),
        Err(error) => return Err(error).with_context(|| format!("cannot read {}", path.display())),
    };
    let config: TestConfig = serde_json::from_str(&text)
        .with_context(|| format!("{} is not a valid test configuration", path.display()))?;
    Ok(config.parity_families)
}

/// The members of one family, or a refusal naming the families there are.
pub fn members(root: &Path, family: &str) -> Result<Vec<String>> {
    let families = declared(root)?;
    match families.get(family) {
        Some(members) => Ok(members.clone()),
        None if families.is_empty() => bail!(
            "no parity families are declared: {} names none",
            root.join(FILE).display()
        ),
        None => bail!(
            "no family '{family}' is declared in {} (declared: {})",
            root.join(FILE).display(),
            families.keys().cloned().collect::<Vec<_>>().join(", ")
        ),
    }
}

#[cfg(test)]
#[path = "families_tests.rs"]
mod tests;