address: a one-word repo name matches as the repo spells it, a split phrase in any case; a self-overlapping name is found; stagger test helpers search a bounded range
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
be9f55ce3d
commit
0d2f4ec216
3 changed files with 59 additions and 25 deletions
|
|
@ -1,8 +1,8 @@
|
|||
//! Whether a chat delivery is for a Mind: the one rule the heartbeat's off-beat wake and beat score and the Persona's `to_you` and
|
||||
//! stagger rank all call. A delivery addresses a Mind when its `m.mentions` holds her Matrix id (a tag), or its text holds one of her
|
||||
//! names as a whole word. Her names are her own (handle, public name; case does not matter) and her projects': the names her
|
||||
//! stewardship records list, matched exactly as written, or, when a record lists none, the repo's name and its split form, matched in
|
||||
//! any case. A deterministic identity match on names, not a reading of meaning.
|
||||
//! stewardship records list, matched exactly as written, or, when a record lists none, the repo's name matched exactly as the repo
|
||||
//! spells it and, for a multi-word name, its split form matched in any case. A deterministic identity match on names, not a reading of meaning.
|
||||
|
||||
use eureka_pipeline::{OrgRepo, Short};
|
||||
|
||||
|
|
@ -52,8 +52,9 @@ pub(crate) fn addresses(who: &Addressee, delivery: &ChatDelivery) -> bool {
|
|||
}
|
||||
|
||||
/// The chat names of one stewarded repo. Listed names are exactly those, each matched exactly as the steward wrote it (capitalization
|
||||
/// tells a project from a common word). With none listed: the repo's name after the org, and that name split at `-`, `_`, `.` and where a
|
||||
/// lower-case letter or digit meets a capital, when the split reads differently; both match in any case.
|
||||
/// tells a project from a common word). With none listed: the repo's name after the org, matched exactly as the repo spells it
|
||||
/// (`Thing`, not `thing`), and that name split at `-`, `_`, `.` and where a lower-case letter or digit meets a capital, when the split
|
||||
/// reads differently, matched in any case (a split phrase is not a common word).
|
||||
pub(crate) fn project_names(repo: &OrgRepo, listed: &[Short]) -> Vec<Name> {
|
||||
if !listed.is_empty() {
|
||||
return listed.iter().map(|name| Name { text: name.0.clone(), exact: true }).collect();
|
||||
|
|
@ -72,9 +73,9 @@ pub(crate) fn project_names(repo: &OrgRepo, listed: &[Short]) -> Vec<Name> {
|
|||
}
|
||||
previous = Some(ch);
|
||||
}
|
||||
let (whole, split) = (whole.to_lowercase(), split.to_lowercase());
|
||||
let mut names = vec![Name { text: whole.clone(), exact: false }];
|
||||
if split != whole {
|
||||
let split = split.to_lowercase();
|
||||
let mut names = vec![Name { text: whole.to_string(), exact: true }];
|
||||
if split != whole.to_lowercase() {
|
||||
names.push(Name { text: split, exact: false });
|
||||
}
|
||||
names
|
||||
|
|
@ -87,11 +88,18 @@ pub(crate) fn names_whole_word(text: &str, name: &str, exact: bool) -> bool {
|
|||
if name.is_empty() {
|
||||
return false;
|
||||
}
|
||||
text.match_indices(&name).any(|(start, _)| {
|
||||
let mut from = 0;
|
||||
while let Some(found) = text[from..].find(&name) {
|
||||
let start = from + found;
|
||||
let before = text[..start].chars().next_back();
|
||||
let after = text[start + name.len()..].chars().next();
|
||||
!before.is_some_and(char::is_alphanumeric) && !after.is_some_and(char::is_alphanumeric)
|
||||
})
|
||||
if !before.is_some_and(char::is_alphanumeric) && !after.is_some_and(char::is_alphanumeric) {
|
||||
return true;
|
||||
}
|
||||
// Step one character, not one name: matches may overlap ("ab ab" in "xab ab ab").
|
||||
from = start + text[start..].chars().next().map_or(1, char::len_utf8);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! The one addressing rule: a tag, or a name as a whole word; a listed name exactly as written, a derived or a Mind's own in any case.
|
||||
//! The one addressing rule: a tag, or a name as a whole word; a listed name exactly as written, a derived one-word repo name as the repo spells it, a split phrase or a Mind's own in any case.
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -42,28 +42,42 @@ fn a_tag_her_name_or_one_of_her_project_names_as_a_whole_word_addresses_her_and_
|
|||
#[test]
|
||||
fn project_names_derives_the_repo_name_and_its_split_form_and_listed_names_replace_them() {
|
||||
let derived = |repo: &str| project_names(&OrgRepo(repo.into()), &[]).into_iter().map(|name| (name.text, name.exact)).collect::<Vec<_>>();
|
||||
let folded = |texts: &[&str]| texts.iter().map(|text| ((*text).to_string(), false)).collect::<Vec<_>>();
|
||||
assert_eq!(derived("GameCult/BadDaySimulator"), folded(&["baddaysimulator", "bad day simulator"]));
|
||||
assert_eq!(derived("GameCult/moonlight-ios"), folded(&["moonlight-ios", "moonlight ios"]));
|
||||
assert_eq!(derived("GameCult/Huginn"), folded(&["huginn"]));
|
||||
assert_eq!(derived("GameCult/snake_case.rs"), folded(&["snake_case.rs", "snake case rs"]));
|
||||
assert_eq!(derived("GameCult/Vili2D"), folded(&["vili2d", "vili2 d"]), "a digit before a capital splits");
|
||||
assert_eq!(derived("GameCult/Vili2Dev"), folded(&["vili2dev", "vili2 dev"]));
|
||||
let spelled = |whole: &str, split: &[&str]| {
|
||||
let mut names = vec![(whole.to_string(), true)];
|
||||
names.extend(split.iter().map(|text| ((*text).to_string(), false)));
|
||||
names
|
||||
};
|
||||
assert_eq!(derived("GameCult/BadDaySimulator"), spelled("BadDaySimulator", &["bad day simulator"]));
|
||||
assert_eq!(derived("GameCult/moonlight-ios"), spelled("moonlight-ios", &["moonlight ios"]));
|
||||
assert_eq!(derived("GameCult/Huginn"), spelled("Huginn", &[]));
|
||||
assert_eq!(derived("GameCult/Thing"), spelled("Thing", &[]));
|
||||
assert_eq!(derived("GameCult/snake_case.rs"), spelled("snake_case.rs", &["snake case rs"]));
|
||||
assert_eq!(derived("GameCult/Vili2D"), spelled("Vili2D", &["vili2 d"]), "a digit before a capital splits");
|
||||
assert_eq!(derived("GameCult/Vili2Dev"), spelled("Vili2Dev", &["vili2 dev"]));
|
||||
let listed = project_names(&OrgRepo("GameCult/BadDaySimulator".into()), &names(&["BDS", "Bad Day Simulator"]));
|
||||
assert_eq!(listed.into_iter().map(|name| (name.text, name.exact)).collect::<Vec<_>>(), [("BDS".to_string(), true), ("Bad Day Simulator".to_string(), true)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_listed_name_matches_exactly_as_written_and_a_derived_name_in_any_case() {
|
||||
fn a_listed_name_matches_exactly_as_written_a_derived_one_word_name_as_spelled_and_a_split_phrase_in_any_case() {
|
||||
let libby = Addressee::new("@libby:chat", ["Libby"]).with_projects(project_names(&OrgRepo("GameCult/Thing".into()), &names(&["Thing"])));
|
||||
assert!(addresses(&libby, &said("Thing is down", &[])));
|
||||
assert!(!addresses(&libby, &said("one thing at a time", &[])));
|
||||
assert!(!addresses(&libby, &said("THING", &[])));
|
||||
let derived_thing = steward("GameCult/Thing", &[]);
|
||||
assert!(addresses(&derived_thing, &said("Thing is down", &[])));
|
||||
for text in ["one thing at a time", "THING", "a thing"] {
|
||||
assert!(!addresses(&derived_thing, &said(text, &[])), "{text}");
|
||||
}
|
||||
let nibu = steward("GameCult/BadDaySimulator", &[]);
|
||||
for text in ["BADDAYSIMULATOR is red", "Bad Day Simulator is red", "bad day simulator is red", "is BadDaySimulator red"] {
|
||||
for text in ["BadDaySimulator is red", "BAD DAY SIMULATOR is red", "Bad Day Simulator is red", "bad day simulator is red"] {
|
||||
assert!(addresses(&nibu, &said(text, &[])), "{text}");
|
||||
}
|
||||
assert!(!addresses(&nibu, &said("a bad day, simulated", &[])));
|
||||
for text in ["baddaysimulator is red", "BADDAYSIMULATOR is red", "a bad day, simulated"] {
|
||||
assert!(!addresses(&nibu, &said(text, &[])), "{text}");
|
||||
}
|
||||
let hyphen = steward("GameCult/moonlight-ios", &[]);
|
||||
assert!(addresses(&hyphen, &said("moonlight-ios", &[])) && addresses(&hyphen, &said("Moonlight iOS", &[])) && !addresses(&hyphen, &said("Moonlight-iOS", &[])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -75,8 +89,17 @@ fn one_name_addresses_every_mind_that_lists_it_each_on_her_own_record() {
|
|||
assert!(addresses(&lore, &both));
|
||||
let two_stewards = [steward("GameCult/Thing", &["Thing"]), Addressee::new("@libby:chat", ["Libby"]).with_projects(project_names(&OrgRepo("GameCult/Thing".into()), &[]))];
|
||||
assert!(two_stewards.iter().all(|who| addresses(who, &said("Thing is broken", &[]))), "a project with two stewards addresses each");
|
||||
assert!(!addresses(&two_stewards[0], &said("a thing", &[])), "each by her own record: listed exact, derived in any case");
|
||||
assert!(addresses(&two_stewards[1], &said("a thing", &[])));
|
||||
assert!(!addresses(&two_stewards[0], &said("a thing", &[])), "listed Thing is exact");
|
||||
assert!(!addresses(&two_stewards[1], &said("a thing", &[])), "derived Thing is exact as the repo spells it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_name_that_overlaps_itself_is_found_where_a_whole_word_hit_follows_a_failed_one() {
|
||||
assert!(names_whole_word("xab ab ab", "ab ab", false));
|
||||
assert!(names_whole_word("xab ab ab", "AB AB", false));
|
||||
assert!(names_whole_word("xab ab ab", "ab ab", true));
|
||||
assert!(!names_whole_word("xab ab abx", "ab ab", true));
|
||||
assert!(names_whole_word("é ab", "ab", true), "multibyte text steps by whole characters");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ fn config() -> Config {
|
|||
/// ranks first among the Minds. The tests that are about the stagger name their events.
|
||||
fn delivery(seq: u64, room: &str, sender: &str, text: &str) -> ChatDelivery {
|
||||
let ranked_first = |event_id: &str| stagger_ms(&config(), &own_addressee(&config()), &ChatDelivery { event_id: event_id.into(), ..delivery_of(seq, room, sender, text, event_id) }) == 0;
|
||||
let event_id = (0..).map(|n| if n == 0 { format!("$e{seq}") } else { format!("$e{seq}-{n}") }).find(|id| ranked_first(id)).unwrap();
|
||||
let event_id = (0..1000).map(|n| if n == 0 { format!("$e{seq}") } else { format!("$e{seq}-{n}") }).find(|id| ranked_first(id)).expect("among 1000 events one ranks her first: the stagger ranks nobody first");
|
||||
delivery_of(seq, room, sender, text, &event_id)
|
||||
}
|
||||
|
||||
|
|
@ -6296,7 +6296,10 @@ fn three_minds(own: &str) -> Config {
|
|||
|
||||
/// An unaddressed swarm message whose event ranks `eureka` at `rank` of three.
|
||||
fn unaddressed_at_rank(seq: u64, rank: u64, text: &str) -> ChatDelivery {
|
||||
(0..).map(|n| delivery_of(seq, "swarm", "@op:chat", text, &format!("$r{seq}-{n}"))).find(|candidate| stagger_ms(&three_minds("@eureka:chat"), &own_addressee(&three_minds("@eureka:chat")), candidate) == rank * STAGGER_STEP_S * 1000).unwrap()
|
||||
(0..1000)
|
||||
.map(|n| delivery_of(seq, "swarm", "@op:chat", text, &format!("$r{seq}-{n}")))
|
||||
.find(|candidate| stagger_ms(&three_minds("@eureka:chat"), &own_addressee(&three_minds("@eureka:chat")), candidate) == rank * STAGGER_STEP_S * 1000)
|
||||
.unwrap_or_else(|| panic!("among 1000 events one ranks her at {rank}: the stagger never does"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue