use objects::object::{
Annotation, AnnotationScope, AnnotationStatus, ContextSuggestionTier, ContextTarget,
};
pub fn annotation_status_label(status: AnnotationStatus) -> &'static str {
match status {
AnnotationStatus::Active => "active",
AnnotationStatus::Superseded => "superseded",
}
}
pub fn suggestion_tier_token(tier: &ContextSuggestionTier) -> &'static str {
match tier {
ContextSuggestionTier::Medium => "medium",
ContextSuggestionTier::High => "high",
}
}
pub fn suggestion_tier_human_label(tier: &ContextSuggestionTier) -> &'static str {
match tier {
ContextSuggestionTier::Medium => "may benefit",
ContextSuggestionTier::High => "recommended",
}
}
pub fn annotation_passes_filters(
annotation: &Annotation,
scope_filter: Option<&AnnotationScope>,
tag_filter: Option<&str>,
include_superseded: bool,
) -> bool {
if !include_superseded && annotation.status == AnnotationStatus::Superseded {
return false;
}
if let Some(scope) = scope_filter
&& !annotation.scope.matches(scope)
{
return false;
}
if let Some(tag) = tag_filter {
let Some(current) = annotation.current_revision() else {
return false;
};
if !current.tags.iter().any(|candidate| candidate == tag) {
return false;
}
}
true
}
pub fn filter_annotations<'a>(
annotations: &'a [Annotation],
scope_filter: Option<&AnnotationScope>,
tag_filter: Option<&str>,
include_superseded: bool,
) -> Vec<&'a Annotation> {
annotations
.iter()
.filter(|annotation| {
annotation_passes_filters(annotation, scope_filter, tag_filter, include_superseded)
})
.collect()
}
pub fn count_active_annotations(annotations: &[Annotation]) -> usize {
annotations
.iter()
.filter(|annotation| annotation.status == AnnotationStatus::Active)
.count()
}
pub fn context_target_kind_and_label(target: &ContextTarget) -> (&'static str, String) {
match target {
ContextTarget::File { path } => ("file", path.clone()),
ContextTarget::State { state_id } => ("state", state_id.to_string_full()),
}
}
pub fn audit_target_key(target: &ContextTarget) -> String {
match target {
ContextTarget::File { path } => path.clone(),
ContextTarget::State { state_id } => state_id.to_string_full(),
}
}
pub fn audit_staleness_key(target: &ContextTarget, annotation: &Annotation) -> String {
match target {
ContextTarget::File { path } => format!("{path}:{}", annotation.scope),
ContextTarget::State { state_id } => {
format!(
"state:{}:{}",
state_id.to_string_full(),
annotation.annotation_id
)
}
}
}
pub fn audit_duplicate_count(signature_counts: impl IntoIterator<Item = u32>) -> u32 {
signature_counts
.into_iter()
.filter(|count| *count > 1)
.count() as u32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextContentPlanError {
Required,
}
impl ContextContentPlanError {
pub fn kind(self) -> &'static str {
match self {
Self::Required => "context_content_required",
}
}
}
pub fn plan_annotation_content_source(
has_message: bool,
has_file: bool,
) -> Result<(), ContextContentPlanError> {
if has_message || has_file {
Ok(())
} else {
Err(ContextContentPlanError::Required)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextRmPlanError {
ScopeRequired,
}
impl ContextRmPlanError {
pub fn kind(self) -> &'static str {
match self {
Self::ScopeRequired => "context_remove_scope_required",
}
}
}
pub fn plan_context_rm(all: bool, scope_present: bool) -> Result<(), ContextRmPlanError> {
if !all && !scope_present {
Err(ContextRmPlanError::ScopeRequired)
} else {
Ok(())
}
}
pub fn supersede_reuses_original_target(path: Option<&str>, state: Option<&str>) -> bool {
path.is_none() && state.is_none()
}
pub fn supersede_reuses_original_scope(scope: Option<&str>) -> bool {
scope.is_none()
}
pub fn next_annotation_tags(current: &[String], override_tags: Vec<String>) -> Vec<String> {
if override_tags.is_empty() {
current.to_vec()
} else {
override_tags
}
}
#[cfg(test)]
mod tests {
use objects::object::{Annotation, AnnotationKind, AnnotationScope, AnnotationStatus};
use super::*;
fn sample_annotation(
scope: AnnotationScope,
tags: Vec<String>,
status: AnnotationStatus,
) -> Annotation {
let mut annotation = Annotation::new(
scope,
AnnotationKind::Rationale,
"body".into(),
tags,
"Test <t@example.com>".into(),
0,
None,
None,
);
annotation.status = status;
annotation
}
#[test]
fn status_and_tier_labels() {
assert_eq!(annotation_status_label(AnnotationStatus::Active), "active");
assert_eq!(
annotation_status_label(AnnotationStatus::Superseded),
"superseded"
);
assert_eq!(
suggestion_tier_token(&ContextSuggestionTier::Medium),
"medium"
);
assert_eq!(suggestion_tier_token(&ContextSuggestionTier::High), "high");
assert_eq!(
suggestion_tier_human_label(&ContextSuggestionTier::Medium),
"may benefit"
);
assert_eq!(
suggestion_tier_human_label(&ContextSuggestionTier::High),
"recommended"
);
}
#[test]
fn list_filters_status_scope_and_tag() {
let active = sample_annotation(
AnnotationScope::File,
vec!["a".into()],
AnnotationStatus::Active,
);
let superseded = sample_annotation(
AnnotationScope::File,
vec!["a".into()],
AnnotationStatus::Superseded,
);
let tagged = sample_annotation(
AnnotationScope::Lines(1, 2),
vec!["hot".into()],
AnnotationStatus::Active,
);
assert!(annotation_passes_filters(&active, None, None, false));
assert!(!annotation_passes_filters(&superseded, None, None, false));
assert!(annotation_passes_filters(&superseded, None, None, true));
assert!(!annotation_passes_filters(
&active,
Some(&AnnotationScope::Lines(1, 2)),
None,
false
));
assert!(annotation_passes_filters(
&tagged,
Some(&AnnotationScope::Lines(1, 2)),
Some("hot"),
false
));
assert!(!annotation_passes_filters(
&tagged,
None,
Some("cold"),
false
));
let pool = [active.clone(), superseded, tagged];
let filtered = filter_annotations(&pool, None, Some("a"), false);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].annotation_id, active.annotation_id);
assert_eq!(count_active_annotations(&[active.clone(), active]), 2);
}
#[test]
fn content_and_rm_plans() {
assert!(plan_annotation_content_source(true, false).is_ok());
assert!(plan_annotation_content_source(false, true).is_ok());
assert_eq!(
plan_annotation_content_source(false, false),
Err(ContextContentPlanError::Required)
);
assert_eq!(
ContextContentPlanError::Required.kind(),
"context_content_required"
);
assert!(plan_context_rm(true, false).is_ok());
assert!(plan_context_rm(false, true).is_ok());
assert_eq!(
plan_context_rm(false, false),
Err(ContextRmPlanError::ScopeRequired)
);
assert_eq!(
ContextRmPlanError::ScopeRequired.kind(),
"context_remove_scope_required"
);
}
#[test]
fn supersede_and_edit_rules() {
assert!(supersede_reuses_original_target(None, None));
assert!(!supersede_reuses_original_target(Some("p"), None));
assert!(!supersede_reuses_original_target(None, Some("s")));
assert!(supersede_reuses_original_scope(None));
assert!(!supersede_reuses_original_scope(Some("file")));
assert_eq!(
next_annotation_tags(&["keep".into()], vec![]),
vec!["keep".to_string()]
);
assert_eq!(
next_annotation_tags(&["keep".into()], vec!["new".into()]),
vec!["new".to_string()]
);
}
#[test]
fn audit_duplicate_count_and_keys() {
assert_eq!(audit_duplicate_count([1, 1, 2, 3]), 2);
assert_eq!(audit_duplicate_count(std::iter::empty::<u32>()), 0);
let file = ContextTarget::file("src/a.rs").expect("file target");
let ann = sample_annotation(AnnotationScope::File, vec![], AnnotationStatus::Active);
assert_eq!(audit_target_key(&file), "src/a.rs");
assert_eq!(
audit_staleness_key(&file, &ann),
format!("src/a.rs:{}", ann.scope)
);
let (kind, label) = context_target_kind_and_label(&file);
assert_eq!(kind, "file");
assert_eq!(label, "src/a.rs");
}
}