use regex::Regex;
use crate::types::Pos;
use crate::generator::types::{PayloadTok, GenerationMode};
use crate::grammar::{Grammar, DialectConfig};
pub fn payload_fits(tok: &PayloadTok, slot: Pos) -> bool {
tok.allowed.contains(&slot)
}
pub fn get_grammar(mode: GenerationMode, language: &str) -> Grammar {
let dialect = match mode {
GenerationMode::Subject => "subject",
GenerationMode::Body => "body",
GenerationMode::PayloadOnly => "payload_only",
};
Grammar::from_language_dialect(language, dialect)
.expect(&format!("Failed to load {} grammar for language {}", dialect, language))
}
pub fn mode_to_dialect(mode: GenerationMode) -> &'static str {
match mode {
GenerationMode::Subject => "subject",
GenerationMode::Body => "body",
GenerationMode::PayloadOnly => "payload_only",
}
}
pub fn get_dialect_config(mode: GenerationMode, language: &str) -> DialectConfig {
let dialect = mode_to_dialect(mode);
DialectConfig::from_language_dialect(language, dialect)
.expect(&format!("Failed to load {} dialect config for language {}", dialect, language))
}
pub fn start_nonterminal_for_pos(pos: Pos) -> &'static str {
match pos {
Pos::N | Pos::V | Pos::Adj | Pos::Adv | Pos::Prep | Pos::Det |
Pos::Modal | Pos::Aux | Pos::Cop | Pos::To | Pos::Conj | Pos::Dot |
Pos::Prefix | Pos::Pron => "S",
}
}
pub fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
}
}
pub fn normalize_token_for_bip39(s: &str) -> String {
let mut result = s.to_string();
result = Regex::new(r"\x1b\[[0-9;]*m")
.unwrap()
.replace_all(&result, "")
.to_string();
result = result.replace('|', "");
result = result.trim()
.trim_matches(|c: char| !c.is_ascii_alphanumeric())
.to_lowercase();
result
}
pub fn starts_with_vowel_sound(word: &str) -> bool {
let normalized = normalize_token_for_bip39(word);
if normalized.is_empty() {
return false;
}
let first_char = normalized.chars().next().unwrap();
matches!(first_char, 'a' | 'e' | 'i' | 'o' | 'u')
}
pub fn is_bare_verb_form(word: &str) -> bool {
let w = normalize_token_for_bip39(word).to_lowercase();
if w.is_empty() {
return false;
}
if w.ends_with("ing") || w.ends_with("ed") || w.ends_with("s") {
return false;
}
true
}
pub fn is_likely_transitive_verb(word: &str) -> bool {
let w = normalize_token_for_bip39(word).to_lowercase();
matches!(
w.as_str(),
"add"
| "bring"
| "build"
| "call"
| "change"
| "check"
| "close"
| "create"
| "deliver"
| "find"
| "fix"
| "get"
| "give"
| "help"
| "hold"
| "keep"
| "leave"
| "make"
| "need"
| "open"
| "put"
| "read"
| "save"
| "see"
| "send"
| "set"
| "show"
| "take"
| "tell"
| "use"
| "verify"
| "write"
)
}
pub fn pluralize_cover_noun(s: &str) -> String {
if s.is_empty() {
return String::new();
}
if s.ends_with('s') {
return s.to_string();
}
if s.ends_with("ch") || s.ends_with("sh") || s.ends_with('x') || s.ends_with('z') {
return format!("{s}es");
}
if s.ends_with('y') && s.len() >= 2 {
let prev = s.as_bytes()[s.len() - 2] as char;
if !matches!(prev, 'a' | 'e' | 'i' | 'o' | 'u') {
return format!("{}ies", &s[..s.len() - 1]);
}
}
format!("{s}s")
}
pub fn wrap_payload_with_bars(word_with_punct: &str) -> String {
let mut core = word_with_punct.to_string();
let mut suffix = String::new();
while let Some(last) = core.chars().last() {
if last.is_ascii_alphabetic() {
break;
}
core.pop();
suffix.insert(0, last);
}
format!("|{core}|{suffix}")
}
pub fn wrap_payload_with_color(word_with_punct: &str, color_code: u8) -> String {
let mut core = word_with_punct.to_string();
let mut suffix = String::new();
while let Some(last) = core.chars().last() {
if last.is_ascii_alphabetic() {
break;
}
core.pop();
suffix.insert(0, last);
}
format!("\x1b[{}m{core}\x1b[0m{suffix}", color_code)
}
pub fn word_wrap(text: &str, width: usize) -> String {
text.split('\n')
.map(|line| wrap_single_line(line, width))
.collect::<Vec<String>>()
.join("\n")
}
fn wrap_single_line(line: &str, width: usize) -> String {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.len() <= width {
return trimmed.to_string();
}
let mut result = Vec::new();
let mut current_line = String::new();
let sentences: Vec<&str> = trimmed.split_inclusive('.').collect();
for sentence in sentences {
let trimmed_sentence = sentence.trim();
if trimmed_sentence.is_empty() {
continue;
}
if !current_line.is_empty() {
let test_line = format!("{} {}", current_line, trimmed_sentence);
if test_line.len() > width {
result.push(current_line.clone());
current_line = trimmed_sentence.to_string();
} else {
current_line = test_line;
}
} else {
current_line = trimmed_sentence.to_string();
}
if current_line.len() > width {
let words: Vec<String> = current_line.split_whitespace().map(|s| s.to_string()).collect();
current_line.clear();
for word in words {
if current_line.is_empty() {
current_line = word;
} else {
let test_line = format!("{} {}", current_line, word);
if test_line.len() > width {
result.push(current_line.clone());
current_line = word;
} else {
current_line = test_line;
}
}
}
}
}
if !current_line.is_empty() {
result.push(current_line);
}
result.join("\n")
}