Add more themes and rustfmt

This commit is contained in:
2026-02-16 22:12:29 +00:00
parent 6d6815af02
commit edd2f7e6b5
36 changed files with 854 additions and 329 deletions

View File

@@ -24,7 +24,13 @@ impl DiskCache {
fn sanitize_key(key: &str) -> String {
key.chars()
.map(|c| if c.is_alphanumeric() || c == '-' || c == '_' || c == '.' { c } else { '_' })
.map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else {
'_'
}
})
.collect()
}
}

View File

@@ -1,5 +1,5 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
/// Post-processing pass that capitalizes words in generated text.
/// Only capitalizes using letters from `unlocked_capitals`.
@@ -39,11 +39,16 @@ pub fn apply_capitalization(
// Capitalize word starts: boosted for focused key, ~12% for others
if ch.is_ascii_lowercase() && !at_sentence_start {
let is_word_start = i == 0 || text.as_bytes().get(i - 1).map(|&b| b as char) == Some(' ');
let is_word_start =
i == 0 || text.as_bytes().get(i - 1).map(|&b| b as char) == Some(' ');
if is_word_start {
let upper = ch.to_ascii_uppercase();
if unlocked_capitals.contains(&upper) {
let prob = if focused_upper == Some(upper) { 0.40 } else { 0.12 };
let prob = if focused_upper == Some(upper) {
0.40
} else {
0.12
};
if rng.gen_bool(prob) {
result.push(upper);
continue;

View File

@@ -1,5 +1,5 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
/// Post-processing pass that inserts code-like expressions into text.
/// Only uses symbols from `unlocked_symbols`.
@@ -51,35 +51,47 @@ fn generate_code_expr(
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} = val")));
if focused_symbol == Some('=') { focused_patterns.push(idx); }
if focused_symbol == Some('=') {
focused_patterns.push(idx);
}
}
if has('+') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} + num")));
if focused_symbol == Some('+') { focused_patterns.push(idx); }
if focused_symbol == Some('+') {
focused_patterns.push(idx);
}
}
if has('*') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} * cnt")));
if focused_symbol == Some('*') { focused_patterns.push(idx); }
if focused_symbol == Some('*') {
focused_patterns.push(idx);
}
}
if has('/') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} / max")));
if focused_symbol == Some('/') { focused_patterns.push(idx); }
if focused_symbol == Some('/') {
focused_patterns.push(idx);
}
}
if has('-') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} - one")));
if focused_symbol == Some('-') { focused_patterns.push(idx); }
if focused_symbol == Some('-') {
focused_patterns.push(idx);
}
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("-{w}")));
if focused_symbol == Some('-') { focused_patterns.push(idx); }
if focused_symbol == Some('-') {
focused_patterns.push(idx);
}
}
if has('=') && has('+') {
let w = word.to_string();
@@ -89,7 +101,9 @@ fn generate_code_expr(
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} -= one")));
if focused_symbol == Some('-') { focused_patterns.push(idx); }
if focused_symbol == Some('-') {
focused_patterns.push(idx);
}
}
if has('=') && has('=') {
let w = word.to_string();
@@ -101,19 +115,25 @@ fn generate_code_expr(
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{{ {w} }}")));
if matches!(focused_symbol, Some('{') | Some('}')) { focused_patterns.push(idx); }
if matches!(focused_symbol, Some('{') | Some('}')) {
focused_patterns.push(idx);
}
}
if has('[') && has(']') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w}[idx]")));
if matches!(focused_symbol, Some('[') | Some(']')) { focused_patterns.push(idx); }
if matches!(focused_symbol, Some('[') | Some(']')) {
focused_patterns.push(idx);
}
}
if has('<') && has('>') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("Vec<{w}>")));
if matches!(focused_symbol, Some('<') | Some('>')) { focused_patterns.push(idx); }
if matches!(focused_symbol, Some('<') | Some('>')) {
focused_patterns.push(idx);
}
}
// Logic patterns
@@ -121,19 +141,25 @@ fn generate_code_expr(
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("&{w}")));
if focused_symbol == Some('&') { focused_patterns.push(idx); }
if focused_symbol == Some('&') {
focused_patterns.push(idx);
}
}
if has('|') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w} | nil")));
if focused_symbol == Some('|') { focused_patterns.push(idx); }
if focused_symbol == Some('|') {
focused_patterns.push(idx);
}
}
if has('!') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("!{w}")));
if focused_symbol == Some('!') { focused_patterns.push(idx); }
if focused_symbol == Some('!') {
focused_patterns.push(idx);
}
}
// Special patterns
@@ -141,31 +167,41 @@ fn generate_code_expr(
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("@{w}")));
if focused_symbol == Some('@') { focused_patterns.push(idx); }
if focused_symbol == Some('@') {
focused_patterns.push(idx);
}
}
if has('#') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("#{w}")));
if focused_symbol == Some('#') { focused_patterns.push(idx); }
if focused_symbol == Some('#') {
focused_patterns.push(idx);
}
}
if has('_') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("{w}_val")));
if focused_symbol == Some('_') { focused_patterns.push(idx); }
if focused_symbol == Some('_') {
focused_patterns.push(idx);
}
}
if has('$') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("${w}")));
if focused_symbol == Some('$') { focused_patterns.push(idx); }
if focused_symbol == Some('$') {
focused_patterns.push(idx);
}
}
if has('\\') {
let w = word.to_string();
let idx = patterns.len();
patterns.push(Box::new(move |_| format!("\\{w}")));
if focused_symbol == Some('\\') { focused_patterns.push(idx); }
if focused_symbol == Some('\\') {
focused_patterns.push(idx);
}
}
if patterns.is_empty() {

View File

@@ -1,9 +1,9 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
use crate::engine::filter::CharFilter;
use crate::generator::cache::{DiskCache, fetch_url};
use crate::generator::TextGenerator;
use crate::generator::cache::{DiskCache, fetch_url};
pub struct CodeSyntaxGenerator {
rng: SmallRng,
@@ -49,9 +49,7 @@ impl CodeSyntaxGenerator {
"https://raw.githubusercontent.com/lodash/lodash/main/src/chunk.ts",
"https://raw.githubusercontent.com/expressjs/express/master/lib/router/index.js",
],
"go" => vec![
"https://raw.githubusercontent.com/golang/go/master/src/fmt/print.go",
],
"go" => vec!["https://raw.githubusercontent.com/golang/go/master/src/fmt/print.go"],
_ => vec![],
};

View File

@@ -23,11 +23,7 @@ impl Dictionary {
self.words.clone()
}
pub fn find_matching(
&self,
filter: &CharFilter,
focused: Option<char>,
) -> Vec<&str> {
pub fn find_matching(&self, filter: &CharFilter, focused: Option<char>) -> Vec<&str> {
let mut matching: Vec<&str> = self
.words
.iter()

View File

@@ -14,5 +14,5 @@ use crate::engine::filter::CharFilter;
pub trait TextGenerator {
fn generate(&mut self, filter: &CharFilter, focused: Option<char>, word_count: usize)
-> String;
-> String;
}

View File

@@ -1,5 +1,5 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
/// Post-processing pass that inserts number expressions into text.
/// Only uses digits from `unlocked_digits`.
@@ -127,6 +127,9 @@ mod tests {
let digits = ['1', '2', '3', '4', '5'];
let text = "a b c d e f g h i j k l m n o p q r s t";
let result = apply_numbers(text, &digits, false, None, &mut rng);
assert!(!result.contains('.'), "Should not contain dot when has_dot=false: {result}");
assert!(
!result.contains('.'),
"Should not contain dot when has_dot=false: {result}"
);
}
}

View File

@@ -1,9 +1,9 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
use crate::engine::filter::CharFilter;
use crate::generator::cache::{DiskCache, fetch_url};
use crate::generator::TextGenerator;
use crate::generator::cache::{DiskCache, fetch_url};
const PASSAGES: &[&str] = &[
// Classic literature & speeches
@@ -217,7 +217,9 @@ fn extract_paragraphs(text: &str) -> Vec<String> {
.collect::<Vec<_>>()
.join(" ")
.chars()
.filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace() || c.is_ascii_punctuation())
.filter(|c| {
c.is_ascii_alphanumeric() || c.is_ascii_whitespace() || c.is_ascii_punctuation()
})
.collect::<String>()
.to_lowercase();

View File

@@ -1,10 +1,10 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
use crate::engine::filter::CharFilter;
use crate::generator::TextGenerator;
use crate::generator::dictionary::Dictionary;
use crate::generator::transition_table::TransitionTable;
use crate::generator::TextGenerator;
const MIN_WORD_LEN: usize = 3;
const MAX_WORD_LEN: usize = 10;
@@ -149,7 +149,8 @@ impl PhoneticGenerator {
if space_weight > 0.0 {
let boost = 1.3f64.powi(word.len() as i32 - MIN_WORD_LEN as i32);
let total: f64 = probs.iter().map(|(_, w)| w).sum();
let space_prob = (space_weight * boost) / (total + space_weight * (boost - 1.0));
let space_prob =
(space_weight * boost) / (total + space_weight * (boost - 1.0));
if self.rng.gen_bool(space_prob.min(0.85)) {
break;
}
@@ -164,11 +165,8 @@ impl PhoneticGenerator {
// Get next character from transition table
if let Some(probs) = self.table.segment(&prefix) {
let non_space: Vec<(char, f64)> = probs
.iter()
.filter(|(ch, _)| *ch != ' ')
.copied()
.collect();
let non_space: Vec<(char, f64)> =
probs.iter().filter(|(ch, _)| *ch != ' ').copied().collect();
if let Some(next) = Self::pick_weighted_from(&mut self.rng, &non_space, filter) {
word.push(next);
} else {

View File

@@ -1,5 +1,5 @@
use rand::rngs::SmallRng;
use rand::Rng;
use rand::rngs::SmallRng;
/// Post-processing pass that inserts punctuation into generated text.
/// Only uses punctuation chars from `unlocked_punct`.
@@ -41,25 +41,41 @@ pub fn apply_punctuation(
let mut w = word.to_string();
// Contractions (~8% of words, boosted if apostrophe is focused)
let apostrophe_prob = if focused_punct == Some('\'') { 0.30 } else { 0.08 };
let apostrophe_prob = if focused_punct == Some('\'') {
0.30
} else {
0.08
};
if has_apostrophe && w.len() >= 3 && rng.gen_bool(apostrophe_prob) {
w = make_contraction(&w, rng);
}
// Compound words with dash (~5% of words, boosted if dash is focused)
let dash_prob = if focused_punct == Some('-') { 0.25 } else { 0.05 };
let dash_prob = if focused_punct == Some('-') {
0.25
} else {
0.05
};
if has_dash && i + 1 < words.len() && rng.gen_bool(dash_prob) {
w.push('-');
}
// Sentence ending punctuation
words_since_period += 1;
let end_sentence = words_since_period >= 8 && rng.gen_bool(0.15)
|| words_since_period >= 12;
let end_sentence =
words_since_period >= 8 && rng.gen_bool(0.15) || words_since_period >= 12;
if end_sentence && i < words.len() - 1 {
let q_prob = if focused_punct == Some('?') { 0.40 } else { 0.15 };
let excl_prob = if focused_punct == Some('!') { 0.40 } else { 0.10 };
let q_prob = if focused_punct == Some('?') {
0.40
} else {
0.15
};
let excl_prob = if focused_punct == Some('!') {
0.40
} else {
0.10
};
if has_question && rng.gen_bool(q_prob) {
w.push('?');
} else if has_exclaim && rng.gen_bool(excl_prob) {
@@ -72,34 +88,62 @@ pub fn apply_punctuation(
} else {
// Comma after clause (~every 4-6 words)
words_since_comma += 1;
let comma_prob = if focused_punct == Some(',') { 0.40 } else { 0.20 };
if has_comma && words_since_comma >= 4 && rng.gen_bool(comma_prob) && i < words.len() - 1 {
let comma_prob = if focused_punct == Some(',') {
0.40
} else {
0.20
};
if has_comma
&& words_since_comma >= 4
&& rng.gen_bool(comma_prob)
&& i < words.len() - 1
{
w.push(',');
words_since_comma = 0;
}
// Semicolon between clauses (rare, boosted if focused)
let semi_prob = if focused_punct == Some(';') { 0.25 } else { 0.05 };
if has_semicolon && words_since_comma >= 5 && rng.gen_bool(semi_prob) && i < words.len() - 1 {
let semi_prob = if focused_punct == Some(';') {
0.25
} else {
0.05
};
if has_semicolon
&& words_since_comma >= 5
&& rng.gen_bool(semi_prob)
&& i < words.len() - 1
{
w.push(';');
words_since_comma = 0;
}
// Colon before list-like content (rare, boosted if focused)
let colon_prob = if focused_punct == Some(':') { 0.20 } else { 0.03 };
let colon_prob = if focused_punct == Some(':') {
0.20
} else {
0.03
};
if has_colon && rng.gen_bool(colon_prob) && i < words.len() - 1 {
w.push(':');
}
}
// Quoted phrases (~5% chance to start a quote, boosted if focused)
let quote_prob = if focused_punct == Some('"') { 0.20 } else { 0.04 };
let quote_prob = if focused_punct == Some('"') {
0.20
} else {
0.04
};
if has_quote && rng.gen_bool(quote_prob) && i + 2 < words.len() {
w = format!("\"{w}");
}
// Parenthetical asides (rare, boosted if focused)
let paren_prob = if matches!(focused_punct, Some('(' | ')')) { 0.15 } else { 0.03 };
let paren_prob = if matches!(focused_punct, Some('(' | ')')) {
0.15
} else {
0.03
};
if has_open_paren && has_close_paren && rng.gen_bool(paren_prob) && i + 2 < words.len() {
w = format!("({w}");
}
@@ -122,9 +166,15 @@ pub fn apply_punctuation(
let mut open_parens = 0i32;
for w in &result {
for ch in w.chars() {
if ch == '"' { open_quotes += 1; }
if ch == '(' { open_parens += 1; }
if ch == ')' { open_parens -= 1; }
if ch == '"' {
open_quotes += 1;
}
if ch == '(' {
open_parens += 1;
}
if ch == ')' {
open_parens -= 1;
}
}
}
if let Some(last) = result.last_mut() {

View File

@@ -108,24 +108,94 @@ impl TransitionTable {
let mut table = Self::new(4);
let common_patterns: &[(&str, f64)] = &[
("the", 10.0), ("and", 8.0), ("ing", 7.0), ("tion", 6.0), ("ent", 5.0),
("ion", 5.0), ("her", 4.0), ("for", 4.0), ("are", 4.0), ("his", 4.0),
("hat", 3.0), ("tha", 3.0), ("ere", 3.0), ("ate", 3.0), ("ith", 3.0),
("ver", 3.0), ("all", 3.0), ("not", 3.0), ("ess", 3.0), ("est", 3.0),
("rea", 3.0), ("sta", 3.0), ("ted", 3.0), ("com", 3.0), ("con", 3.0),
("oun", 2.5), ("pro", 2.5), ("oth", 2.5), ("igh", 2.5), ("ore", 2.5),
("our", 2.5), ("ine", 2.5), ("ove", 2.5), ("ome", 2.5), ("use", 2.5),
("ble", 2.0), ("ful", 2.0), ("ous", 2.0), ("str", 2.0), ("tri", 2.0),
("ght", 2.0), ("whi", 2.0), ("who", 2.0), ("hen", 2.0), ("ter", 2.0),
("man", 2.0), ("men", 2.0), ("ner", 2.0), ("per", 2.0), ("pre", 2.0),
("ran", 2.0), ("lin", 2.0), ("kin", 2.0), ("din", 2.0), ("sin", 2.0),
("out", 2.0), ("ind", 2.0), ("ber", 2.0), ("der", 2.0),
("end", 2.0), ("hin", 2.0), ("old", 2.0), ("ear", 2.0), ("ain", 2.0),
("ant", 2.0), ("urn", 2.0), ("ell", 2.0), ("ill", 2.0), ("ade", 2.0),
("ong", 2.0), ("ung", 2.0), ("ast", 2.0), ("ist", 2.0),
("ust", 2.0), ("ost", 2.0), ("ard", 2.0), ("ord", 2.0), ("art", 2.0),
("ort", 2.0), ("ect", 2.0), ("act", 2.0), ("ack", 2.0), ("ick", 2.0),
("ock", 2.0), ("uck", 2.0), ("ash", 2.0), ("ish", 2.0), ("ush", 2.0),
("the", 10.0),
("and", 8.0),
("ing", 7.0),
("tion", 6.0),
("ent", 5.0),
("ion", 5.0),
("her", 4.0),
("for", 4.0),
("are", 4.0),
("his", 4.0),
("hat", 3.0),
("tha", 3.0),
("ere", 3.0),
("ate", 3.0),
("ith", 3.0),
("ver", 3.0),
("all", 3.0),
("not", 3.0),
("ess", 3.0),
("est", 3.0),
("rea", 3.0),
("sta", 3.0),
("ted", 3.0),
("com", 3.0),
("con", 3.0),
("oun", 2.5),
("pro", 2.5),
("oth", 2.5),
("igh", 2.5),
("ore", 2.5),
("our", 2.5),
("ine", 2.5),
("ove", 2.5),
("ome", 2.5),
("use", 2.5),
("ble", 2.0),
("ful", 2.0),
("ous", 2.0),
("str", 2.0),
("tri", 2.0),
("ght", 2.0),
("whi", 2.0),
("who", 2.0),
("hen", 2.0),
("ter", 2.0),
("man", 2.0),
("men", 2.0),
("ner", 2.0),
("per", 2.0),
("pre", 2.0),
("ran", 2.0),
("lin", 2.0),
("kin", 2.0),
("din", 2.0),
("sin", 2.0),
("out", 2.0),
("ind", 2.0),
("ber", 2.0),
("der", 2.0),
("end", 2.0),
("hin", 2.0),
("old", 2.0),
("ear", 2.0),
("ain", 2.0),
("ant", 2.0),
("urn", 2.0),
("ell", 2.0),
("ill", 2.0),
("ade", 2.0),
("ong", 2.0),
("ung", 2.0),
("ast", 2.0),
("ist", 2.0),
("ust", 2.0),
("ost", 2.0),
("ard", 2.0),
("ord", 2.0),
("art", 2.0),
("ort", 2.0),
("ect", 2.0),
("act", 2.0),
("ack", 2.0),
("ick", 2.0),
("ock", 2.0),
("uck", 2.0),
("ash", 2.0),
("ish", 2.0),
("ush", 2.0),
];
for &(pattern, weight) in common_patterns {
@@ -142,8 +212,8 @@ impl TransitionTable {
let vowels = ['a', 'e', 'i', 'o', 'u'];
let consonants = [
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v',
'w', 'x', 'y', 'z',
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w',
'x', 'y', 'z',
];
for &c in &consonants {