A pit full of rusty nails
at main 190 lines 5.1 kB view raw
1use std::sync::Arc; 2 3use axum::extract::MatchedPath; 4use bytes::{Bytes, BytesMut}; 5use nailconfig::NailConfig; 6use nailkov::NailKov; 7use nailrng::FastRng; 8use rand::{RngExt, Rng, distr::Alphanumeric, seq::IndexedRandom}; 9 10/// Provides either the minimum configured size, or a randomised value between 11/// the minimum and maximum configured sizes if a maximum is available. 12#[inline] 13fn get_desired_size(config: &NailConfig, rng: &mut impl Rng) -> usize { 14 match ( 15 config.generator.min_paragraph_size, 16 config.generator.max_paragraph_size, 17 ) { 18 (min, None) => min, 19 (min, Some(max)) => rng.random_range(min..=max), 20 } 21} 22 23/// Generates text from the markov chain, using the tokens it outputs to pull 24/// interned text from the interner. 25#[inline] 26pub fn text_generator<'a>( 27 chain: &'a NailKov, 28 size: usize, 29 rng: &'a mut impl Rng, 30) -> impl Iterator<Item = &'a u8> + 'a { 31 chain 32 .generate_tokens(rng) 33 .take(size) 34 // SAFETY: The id comes from the same interner that allocated it 35 .flat_map(|token| token.as_bytes()) 36 .skip_while(|&text| !text.is_ascii_alphabetic()) 37} 38 39#[inline] 40pub fn static_title<'a>(text: &'a str) -> impl Iterator<Item = &'a u8> + 'a { 41 text.lines() 42 .map(str::trim) 43 .next() 44 .into_iter() 45 .flat_map(str::as_bytes) 46} 47 48#[inline] 49pub fn static_content<'a>(text: &'a str) -> impl Iterator<Item = &'a u8> + 'a { 50 text.lines() 51 .skip(1) 52 .filter_map(|line| { 53 let trimmed = line.trim(); 54 55 if line.is_empty() { 56 None 57 } else { 58 Some(trimmed.as_bytes()) 59 } 60 }) 61 .flat_map(|bytes| b"<p>".iter().chain(bytes).chain(b"</p>\n")) 62} 63 64pub async fn initial_content( 65 buf_mut: BytesMut, 66 chain: Arc<NailKov>, 67 config: Arc<NailConfig>, 68 mut rng: FastRng, 69) -> Bytes { 70 // Randomise how many initial paragraphs we want 71 let max_paras: u32 = rng.random_range(1..=3); 72 73 (0..max_paras) 74 .fold(buf_mut, |mut acc, _| { 75 acc.extend(paragraph( 76 &chain, 77 get_desired_size(&config, &mut rng), 78 &mut rng, 79 )); 80 81 acc 82 }) 83 .freeze() 84} 85 86pub async fn main_content( 87 mut buffer: BytesMut, 88 chain: Arc<NailKov>, 89 config: Arc<NailConfig>, 90 mut rng: FastRng, 91) -> Bytes { 92 buffer.reserve(config.generator.chunk_size * 2); 93 94 loop { 95 buffer.extend(header(&chain, config.generator.header_size, &mut rng)); 96 97 // Randomise how many paragraphs we want per section 98 let paragraphs = rng.random_range(1..=4); 99 100 (0..paragraphs).for_each(|_| { 101 buffer.extend(paragraph( 102 &chain, 103 get_desired_size(&config, &mut rng), 104 &mut rng, 105 )); 106 }); 107 108 // We can generate more before handing it off to be streamed to the client, 109 // A bit more latency, but much more throughput, and friendlier to being compressed. 110 if buffer.len() >= config.generator.chunk_size { 111 return buffer.freeze(); 112 } 113 114 // Yield to the runtime to allow other tasks a chance to run before we generate 115 // another chunk of data 116 futures_lite::future::yield_now().await; 117 } 118} 119 120#[inline] 121pub fn extra(buf_mut: &mut BytesMut, config: &NailConfig, rng: &mut FastRng) -> usize { 122 let mut written = 0; 123 124 if let Some(prompt) = match config.generator.prompts.len() { 125 0 => None, 126 1 => config.generator.prompts.first(), 127 _ => config.generator.prompts.choose(rng), 128 } { 129 buf_mut.extend(b"<p>".iter().chain(prompt.as_bytes()).chain(b"</p>")); 130 131 written += prompt.len(); 132 } 133 134 written 135} 136 137pub async fn footer( 138 mut buf_mut: BytesMut, 139 chain: Arc<NailKov>, 140 path: MatchedPath, 141 config: Arc<NailConfig>, 142 mut rng: FastRng, 143) -> Bytes { 144 let path = path.as_str(); 145 146 let route = path.strip_suffix("/{*generated}").unwrap_or(path); 147 148 let total_links = rng.random_range(1..=config.generator.max_pit_links); 149 150 buf_mut.extend_from_slice(b"<nav style=\"visibility: hidden;\"><ul>"); 151 152 for _ in 1..=total_links { 153 buf_mut.extend(b"<li><a href=\"".iter().chain(route.as_bytes()).chain(b"/")); 154 buf_mut.extend((&mut rng).sample_iter(Alphanumeric).take(16)); 155 buf_mut.extend( 156 b"\">" 157 .iter() 158 .chain(text_generator(&chain, 8, &mut rng)) 159 .chain(b"</a></li>\n"), 160 ); 161 } 162 163 buf_mut.extend_from_slice(b"</ul></nav>"); 164 165 buf_mut.freeze() 166} 167 168#[inline] 169fn paragraph<'a>( 170 chain: &'a NailKov, 171 size: usize, 172 rng: &'a mut impl Rng, 173) -> impl Iterator<Item = &'a u8> + 'a { 174 b"<p>" 175 .iter() 176 .chain(text_generator(chain, size, rng)) 177 .chain(b"</p>\n") 178} 179 180#[inline] 181fn header<'a>( 182 chain: &'a NailKov, 183 size: usize, 184 rng: &'a mut impl Rng, 185) -> impl Iterator<Item = &'a u8> + 'a { 186 b"\n<h2>" 187 .iter() 188 .chain(text_generator(chain, size, rng)) 189 .chain(b"</h2>\n") 190}