use crate::{FacetValue, GroupRank, Join, Nesting, Standing};
impl From<tagtree::Standing> for Standing {
fn from(standing: tagtree::Standing) -> Self {
match standing {
tagtree::Standing::Open => Self::Open,
tagtree::Standing::Taken => Self::Taken,
tagtree::Standing::Inherited => Self::Inherited,
tagtree::Standing::Pruned => Self::Pruned,
tagtree::Standing::PrunedAbove => Self::PrunedAbove,
}
}
}
#[must_use]
pub fn values<'a>(
rows: &[tagtree::OutlineNode<'a>],
selection: &tagtree::Selection,
counts: Option<&tagtree::Rollup>,
) -> Vec<FacetValue<'a>> {
rows.iter()
.map(|row| {
let value = FacetValue::new(row.path, row.label)
.standing(selection.standing(row.path).into())
.at(
Nesting::at(u8::try_from(row.depth.saturating_sub(1)).unwrap_or(u8::MAX)),
row.branching,
);
match counts {
Some(rollup) => value.counted(rollup.total(row.path)),
None => value,
}
})
.collect()
}
impl From<tagtree::Join> for Join {
fn from(join: tagtree::Join) -> Self {
match join {
tagtree::Join::Any => Self::Any,
tagtree::Join::All => Self::All,
}
}
}
#[must_use]
pub fn query_standing(query: &tagtree::TagQuery, path: &str) -> Standing {
if query.is_picked(path) {
return Standing::Taken;
}
if query.is_excluded(path) {
return Standing::Pruned;
}
for above in tagtree::ancestors(path).into_iter().rev() {
if query.is_excluded(above) {
return Standing::PrunedAbove;
}
if query.is_picked(above) {
return Standing::Inherited;
}
}
Standing::Open
}
#[must_use]
pub fn grouped<'a>(
rows: &[tagtree::OutlineNode<'a>],
query: &tagtree::TagQuery,
pickable: impl Fn(&str) -> bool,
open: impl Fn(&str) -> bool,
ranks: &[tagtree::GroupRank],
count: impl Fn(&str) -> Option<(u64, bool)>,
) -> Vec<FacetValue<'a>> {
let best = ranks
.iter()
.filter(|r| r.live && !r.in_use)
.map(|r| r.score)
.fold(0.0_f64, f64::max);
let start = ranks
.iter()
.find(|r| r.live && !r.in_use)
.map(|r| r.path.as_str());
rows.iter()
.map(|row| {
let path = row.path;
let mut value = FacetValue::new(path, row.label)
.standing(query_standing(query, path))
.at(
Nesting::at(u8::try_from(row.depth.saturating_sub(1)).unwrap_or(u8::MAX)),
row.branching,
);
if row.branching {
value = value.opened(open(path)).joined(query.join(path).into());
}
if pickable(path) {
if let Some((n, gain)) = count(path) {
value = value.counted(n);
if gain {
value = value.gained();
}
}
} else {
value = value.heading();
}
if let Some(r) = ranks.iter().find(|r| r.path == path) {
let rank = if r.in_use {
GroupRank::in_use()
} else if !r.live {
GroupRank::dead()
} else {
let cut = if best > 0.0 {
r.score / best * 100.0
} else {
0.0
};
GroupRank::cutting((cut.round() as u8).max(4))
};
value = value.ranked(if start == Some(path) {
rank.starting()
} else {
rank
});
}
value
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_tagtree_standing_maps_to_its_namesake() {
assert_eq!(Standing::from(tagtree::Standing::Open), Standing::Open);
assert_eq!(Standing::from(tagtree::Standing::Taken), Standing::Taken);
assert_eq!(
Standing::from(tagtree::Standing::Inherited),
Standing::Inherited
);
assert_eq!(Standing::from(tagtree::Standing::Pruned), Standing::Pruned);
assert_eq!(
Standing::from(tagtree::Standing::PrunedAbove),
Standing::PrunedAbove
);
}
#[test]
fn an_unmeasured_facet_carries_no_counts() {
let index = tagtree::TagIndex::new(vec!["a.b".into()]);
let outline = index.outline();
let facet = values(&outline, &tagtree::Selection::new(), None);
assert!(facet.iter().all(|v| v.count.is_none()));
assert!(facet[0].branching && !facet[1].branching);
assert_eq!(facet[0].label, "a");
assert_eq!(facet[1].label, "b");
}
#[test]
fn a_prune_reads_out_below_it() {
let index = tagtree::TagIndex::new(vec!["a.b.c".into()]);
let outline = index.outline();
let mut selection = tagtree::Selection::taking(["a"]);
selection.prune("a.b");
let facet = values(&outline, &selection, None);
let standings: Vec<Standing> = facet.iter().map(|v| v.standing).collect();
assert_eq!(
standings,
[Standing::Taken, Standing::Pruned, Standing::PrunedAbove]
);
}
#[test]
fn a_query_reads_nearest_decision_first() {
let mut query = tagtree::TagQuery::new();
query.pick("a.b");
query.exclude("a.b.c");
assert_eq!(query_standing(&query, "a.b"), Standing::Taken);
assert_eq!(query_standing(&query, "a.b.x"), Standing::Inherited);
assert_eq!(query_standing(&query, "a.b.c.d"), Standing::PrunedAbove);
assert_eq!(query_standing(&query, "a"), Standing::Open);
}
#[test]
fn ranks_scale_to_the_best_group_and_mark_where_to_start() {
let index =
tagtree::TagIndex::new(vec!["m.a".into(), "g.x".into(), "p.l".into(), "f.y".into()]);
let outline = index.outline();
let rank = |path: &str, in_use, live, score| tagtree::GroupRank {
path: path.into(),
in_use,
live,
score,
cut: 0.0,
coverage: 0.0,
choices: 1,
};
let ranks = [
rank("m", true, true, 1.0),
rank("g", false, true, 0.2),
rank("f", false, true, 0.1),
rank("p", false, false, 0.0),
];
let mut query = tagtree::TagQuery::new();
query.pick("m.a");
let facet = grouped(
&outline,
&query,
|p| p.contains('.'),
|_| false,
&ranks,
|_| None,
);
let by = |p: &str| {
facet
.iter()
.find(|v| v.value == p)
.and_then(|v| v.rank)
.unwrap()
};
assert!(by("m").in_use);
assert_eq!((by("g").cut, by("g").start), (100, true));
assert_eq!((by("f").cut, by("f").start), (50, false));
assert!(!by("p").live);
}
}