use std::path::{Path, PathBuf};
use crate::core::DispatchMechanism;
use super::super::grouping::Group;
pub(super) struct EnvTarget {
pub root: PathBuf,
pub conditions: Vec<(&'static str, Option<String>)>,
pub eval_ids: Vec<String>,
}
pub(super) struct EnvLayoutInput<'a> {
pub iteration_dir: &'a Path,
pub mechanism: DispatchMechanism,
pub groups: &'a [Group],
pub cond_a: &'static str,
pub cond_b: &'static str,
pub skill_path_a: Option<&'a str>,
pub skill_path_b: Option<&'a str>,
}
pub(super) fn task_env_root(
iteration_dir: &Path,
mechanism: DispatchMechanism,
group_id: &str,
condition: &str,
) -> PathBuf {
match mechanism {
DispatchMechanism::InSession => iteration_dir.join("env"),
DispatchMechanism::Cli => iteration_dir.join(format!("env-{group_id}-{condition}")),
}
}
pub(super) fn env_targets(input: &EnvLayoutInput) -> Vec<EnvTarget> {
let conds: [(&'static str, Option<String>); 2] = [
(input.cond_a, input.skill_path_a.map(str::to_owned)),
(input.cond_b, input.skill_path_b.map(str::to_owned)),
];
match input.mechanism {
DispatchMechanism::InSession => {
let first = input
.groups
.first()
.expect("at least one group is always computed");
vec![EnvTarget {
root: task_env_root(
input.iteration_dir,
input.mechanism,
&first.id,
input.cond_a,
),
conditions: conds.to_vec(),
eval_ids: first.eval_ids.clone(),
}]
}
DispatchMechanism::Cli => input
.groups
.iter()
.flat_map(|g| {
conds
.clone()
.into_iter()
.map(move |(cond, skill)| EnvTarget {
root: task_env_root(input.iteration_dir, input.mechanism, &g.id, cond),
conditions: vec![(cond, skill)],
eval_ids: g.eval_ids.clone(),
})
})
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn groups() -> Vec<Group> {
vec![
Group {
id: "g1".into(),
eval_ids: vec!["e1".into()],
rationale: "default".into(),
},
Group {
id: "g2".into(),
eval_ids: vec!["e2".into()],
rationale: "fixture-conflict: e2 vs e1 at c.json".into(),
},
]
}
#[test]
fn insession_single_env_hosts_both_conditions_and_first_group() {
let iter = Path::new("/w/iteration-1");
let gs = groups();
let targets = env_targets(&EnvLayoutInput {
iteration_dir: iter,
mechanism: DispatchMechanism::InSession,
groups: &gs,
cond_a: "with_skill",
cond_b: "without_skill",
skill_path_a: Some("/s/SKILL.md"),
skill_path_b: None,
});
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].root, Path::new("/w/iteration-1/env"));
assert_eq!(
targets[0]
.conditions
.iter()
.map(|(c, _)| *c)
.collect::<Vec<_>>(),
vec!["with_skill", "without_skill"]
);
assert_eq!(targets[0].eval_ids, vec!["e1"]);
}
#[test]
fn cli_one_env_per_group_condition_with_only_that_conditions_skill() {
let iter = Path::new("/w/iteration-1");
let gs = groups();
let targets = env_targets(&EnvLayoutInput {
iteration_dir: iter,
mechanism: DispatchMechanism::Cli,
groups: &gs,
cond_a: "with_skill",
cond_b: "without_skill",
skill_path_a: Some("/s/SKILL.md"),
skill_path_b: None,
});
assert_eq!(targets.len(), 4, "2 groups × 2 conditions");
let roots: Vec<String> = targets
.iter()
.map(|t| t.root.to_string_lossy().into_owned())
.collect();
assert_eq!(
roots,
vec![
"/w/iteration-1/env-g1-with_skill",
"/w/iteration-1/env-g1-without_skill",
"/w/iteration-1/env-g2-with_skill",
"/w/iteration-1/env-g2-without_skill",
]
);
let with = &targets[0];
assert_eq!(
with.conditions,
vec![("with_skill", Some("/s/SKILL.md".to_string()))]
);
let without = &targets[1];
assert_eq!(without.conditions, vec![("without_skill", None)]);
assert_eq!(targets[0].eval_ids, vec!["e1"]);
assert_eq!(targets[2].eval_ids, vec!["e2"]);
}
#[test]
fn task_env_root_is_bare_env_for_insession_and_suffixed_for_cli() {
let iter = Path::new("/w/iteration-1");
assert_eq!(
task_env_root(iter, DispatchMechanism::InSession, "g2", "without_skill"),
Path::new("/w/iteration-1/env")
);
assert_eq!(
task_env_root(iter, DispatchMechanism::Cli, "g2", "without_skill"),
Path::new("/w/iteration-1/env-g2-without_skill")
);
}
}