#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Position {
Start,
End,
Isolated,
}
#[derive(Debug, Clone, Copy)]
pub struct FillerRule {
pub word: &'static str,
pub positions: &'static [Position],
pub not_if_prev: &'static [&'static str],
pub not_if_next: &'static [&'static str],
pub compound_prev: &'static [&'static str],
pub compound_next: &'static [&'static str],
pub compound_requires_boundary_before: bool,
pub requires_prev: &'static [&'static str],
pub requires_after_comma: bool,
pub keep_terminal: bool,
}
pub const DEFAULT_FILLERS: &[FillerRule] = &[
FillerRule {
word: "tipo",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &["de", "que"],
compound_prev: &[],
compound_next: &["assim"],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "assim",
positions: &[Position::Isolated],
not_if_prev: &[
"mesmo", "mesma", "é", "e", "faz", "fazer", "fez", "fica", "ficar",
"dessa", "deste", "desta", "daquele", "daquela", "aquele", "aquela",
"aquilo", "bem",
],
not_if_next: &["como", "que"],
compound_prev: &["tipo"],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "né",
positions: &[Position::End],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &["não"],
compound_requires_boundary_before: true,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: true,
},
FillerRule {
word: "não",
positions: &[Position::End],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &["né"],
requires_after_comma: false,
keep_terminal: true,
},
FillerRule {
word: "então",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "sabe",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "entendeu",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "viu",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "tá",
positions: &[Position::Isolated],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: false,
keep_terminal: false,
},
FillerRule {
word: "olha",
positions: &[Position::Start],
not_if_prev: &[],
not_if_next: &[],
compound_prev: &[],
compound_next: &[],
compound_requires_boundary_before: false,
requires_prev: &[],
requires_after_comma: true,
keep_terminal: false,
},
];
pub const KNOWN_VARIANTS: &[(&str, &[&str])] = &[
("Sam Altman", &["sematlman", "semautman", "samautiman"]),
("Claude Code", &["cloud code", "cloude code", "claud code"]),
("GitHub", &["github", "git hub"]),
("ChatGPT", &["chatgpt", "chat gpt"]),
("OpenAI", &["openai", "open ia", "open ai"]),
("Anthropic", &["antropic", "antropico", "antrópico"]),
];
pub const FUNCTION_WORDS: &[&str] = &[
"a", "à", "ao", "aos", "as", "com", "da", "das", "de", "do", "dos", "e",
"é", "em", "entre", "eu", "já", "mas", "na", "nas", "no", "nos", "o",
"os", "ou", "para", "por", "que", "se", "sem", "sim", "te", "tu", "um",
"uma", "uns", "umas", "vou", "vamos", "não", "nao", "me", "lhe",
];
pub fn fold(s: &str) -> String {
s.to_lowercase()
}
pub fn fold_flat(s: &str) -> String {
s.to_lowercase().chars().filter(|c| !c.is_whitespace()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fold_handles_accents_and_multi_char_lowercase() {
assert_eq!(fold("JOSÉ SÃO PAULO"), "josé são paulo");
assert_eq!(fold("ẞ"), "ß");
assert_eq!(fold("İ"), "i\u{307}");
assert_ne!(fold("İ"), "i");
}
#[test]
fn fold_flat_joins_words() {
assert_eq!(fold_flat("Git Hub"), "github");
assert_eq!(fold_flat(" Sam\tAltman "), "samaltman");
}
#[test]
fn default_fillers_are_all_lowercase_single_words() {
for r in DEFAULT_FILLERS {
assert_eq!(r.word, r.word.to_lowercase(), "regra '{}' fora do padrão", r.word);
assert!(!r.word.contains(char::is_whitespace), "regra '{}' multi-palavra", r.word);
}
}
#[test]
fn every_filler_rule_has_a_home_in_the_table() {
let words: Vec<&str> = DEFAULT_FILLERS.iter().map(|r| r.word).collect();
for w in ["tipo", "assim", "né", "não", "então", "sabe", "entendeu", "viu", "tá", "olha"] {
assert!(words.contains(&w), "muleta '{}' ausente da tabela", w);
}
}
}