#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GuidanceDefaults {
pub goal: Option<String>,
pub avoid: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MemGuidance {
pub goal_additions: Option<String>,
pub avoid_additions: Option<String>,
pub legacy_goal: Option<String>,
pub legacy_avoid: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedGuidance {
pub goal: Option<String>,
pub avoid: Option<String>,
}
fn present(s: Option<&str>) -> Option<&str> {
s.filter(|x| !x.is_empty())
}
pub fn merge_guidance_block(
default: Option<&str>,
additions: Option<&str>,
legacy: Option<&str>,
) -> Option<String> {
let default = present(default);
let additions = present(additions);
if let Some(legacy) = present(legacy) {
tracing::warn!(
"ingest guidance: mem carries a legacy writeGuidance literal; \
the schema's default_writing_guidance is ignored for this field \
(migrate the prose into the schema, or rename to *_additions)"
);
return Some(legacy.to_string());
}
match (default, additions) {
(None, None) => None,
(Some(default), None) => Some(default.to_string()),
(None, Some(additions)) => Some(additions.to_string()),
(Some(default), Some(additions)) => {
Some(format!("{}\n\n{}", default.trim_end(), additions))
}
}
}
pub fn resolve_writing_guidance(
defaults: &GuidanceDefaults,
mem: &MemGuidance,
) -> ResolvedGuidance {
ResolvedGuidance {
goal: merge_guidance_block(
defaults.goal.as_deref(),
mem.goal_additions.as_deref(),
mem.legacy_goal.as_deref(),
),
avoid: merge_guidance_block(
defaults.avoid.as_deref(),
mem.avoid_additions.as_deref(),
mem.legacy_avoid.as_deref(),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_combines_default_and_additions() {
assert_eq!(merge_guidance_block(None, None, None), None);
assert_eq!(merge_guidance_block(Some(""), Some(""), None), None);
assert_eq!(
merge_guidance_block(Some("D"), None, None),
Some("D".to_string())
);
assert_eq!(
merge_guidance_block(None, Some("A"), None),
Some("A".to_string())
);
assert_eq!(
merge_guidance_block(Some("D\n"), Some("A"), None),
Some("D\n\nA".to_string()),
"default's trailing newline is trimmed, then joined by a blank line"
);
}
#[test]
fn legacy_literal_wins() {
assert_eq!(
merge_guidance_block(
Some("schema default"),
Some("additions"),
Some("legacy prose")
),
Some("legacy prose".to_string())
);
}
#[test]
fn resolve_wires_goal_and_avoid() {
let defaults = GuidanceDefaults {
goal: Some("build coverage".to_string()),
avoid: None,
};
let mem = MemGuidance {
goal_additions: Some("prefer small entities".to_string()),
avoid_additions: None,
legacy_goal: None,
legacy_avoid: None,
};
let r = resolve_writing_guidance(&defaults, &mem);
assert_eq!(
r.goal.as_deref(),
Some("build coverage\n\nprefer small entities")
);
assert_eq!(r.avoid, None, "avoid absent everywhere is dropped");
}
}