use super::RewriteProfile; pub(super) fn rewrite_terms(content: &str, profile: RewriteProfile) -> String { let mut rewritten = replace_case_insensitive_with_boundaries(content, profile.doc_file_name, "AGENTS.md"); for from in profile.term_variants { rewritten = replace_case_insensitive_with_boundaries(&rewritten, from, "Codex"); } for from in profile.case_sensitive_term_variants { rewritten = replace_with_boundaries(&rewritten, from, "Codex"); } rewritten } fn replace_with_boundaries(input: &str, needle: &str, replacement: &str) -> String { if needle.is_empty() { return input.to_string(); } replace_with_boundaries_impl(input, needle, replacement, input) } fn replace_case_insensitive_with_boundaries( input: &str, needle: &str, replacement: &str, ) -> String { let needle_lower = needle.to_ascii_lowercase(); if needle_lower.is_empty() { return input.to_string(); } let haystack_lower = input.to_ascii_lowercase(); replace_with_boundaries_impl(input, &needle_lower, replacement, &haystack_lower) } fn replace_with_boundaries_impl( input: &str, needle: &str, replacement: &str, searchable_input: &str, ) -> String { let bytes = input.as_bytes(); let mut output = String::with_capacity(input.len()); let mut last_emitted = 0usize; let mut search_start = 0usize; while let Some(relative_pos) = searchable_input[search_start..].find(needle) { let start = search_start + relative_pos; let end = start + needle.len(); let boundary_before = start == 0 || !is_word_byte(bytes[start - 1]); let boundary_after = end == bytes.len() || !is_word_byte(bytes[end]); if boundary_before && boundary_after { output.push_str(&input[last_emitted..start]); output.push_str(replacement); last_emitted = end; } search_start = start + 1; } if last_emitted == 0 { return input.to_string(); } output.push_str(&input[last_emitted..]); output } pub(super) fn yaml_string(value: &str) -> String { format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\"")) } pub(super) fn slugify_name(value: &str) -> String { let mut slug = String::new(); let mut last_was_dash = false; for ch in value.chars() { if ch.is_ascii_alphanumeric() { slug.push(ch.to_ascii_lowercase()); last_was_dash = false; } else if !last_was_dash { slug.push('-'); last_was_dash = true; } } let slug = slug.trim_matches('-').to_string(); if slug.is_empty() { "migrated".to_string() } else { slug } } fn is_word_byte(byte: u8) -> bool { byte.is_ascii_alphanumeric() || byte == b'_' }