1use anyhow::Result;
22use parking_lot::Mutex;
23use std::{
24 io::{sink, BufWriter, Write},
25 sync::{
26 mpsc::{self, Receiver, Sender, TryRecvError},
27 Arc,
28 },
29 thread,
30 time::Duration,
31};
32
33#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct Mantra {
37 pub syllables: Vec<String>,
39
40 pub repeats: Option<usize>,
42}
43
44impl Mantra {
45 fn recite<T>(&self, output: &mut BufWriter<T>, rate: Duration) -> Result<()>
47 where
48 T: Write,
49 {
50 let repeats = self.repeats.unwrap_or(1);
51 for _ in 0..repeats {
52 for syllable in &self.syllables {
53 output.write_all(syllable.as_bytes())?;
54 output.write_all("\n".as_bytes())?;
55 thread::sleep(rate);
56 }
57 }
58 Ok(())
59 }
60}
61
62#[derive(Clone, Debug, Default, Eq, PartialEq)]
64pub struct Options {
65 pub preparation: Option<String>,
69
70 pub preparation_repeats: Option<usize>,
73
74 pub mantras: Vec<Mantra>,
77
78 pub conclusion: Option<String>,
81
82 pub conclusion_repeats: Option<usize>,
85
86 pub repeats: Option<usize>,
89
90 pub rate_ns: u64,
93}
94
95impl Options {
96 fn should_repeat(&self, count: usize) -> bool {
98 match self.repeats {
99 Some(repeats) => count < repeats,
100 None => true,
101 }
102 }
103}
104
105pub struct MantraMiner {
107 options: Options,
109
110 count: Arc<Mutex<usize>>,
112
113 stop_channel: Option<Sender<()>>,
115}
116
117impl MantraMiner {
118 pub fn new(options: Options) -> MantraMiner {
120 MantraMiner {
121 options,
122 count: Arc::new(Mutex::new(0)),
123 stop_channel: None,
124 }
125 }
126
127 fn recite_string<T>(
129 input: &Option<String>,
130 output: &mut BufWriter<T>,
131 rate: Duration,
132 ) -> Result<()>
133 where
134 T: Write,
135 {
136 match input {
137 None => Ok(()),
138 Some(input) => {
139 for c in input.chars() {
140 let mut b = [0; 4];
141 output.write_all(c.encode_utf8(&mut b).as_bytes())?;
142 thread::sleep(rate);
143 }
144 Ok(())
145 }
146 }
147 }
148
149 fn run(options: Options, total_count: Arc<Mutex<usize>>, rx: Receiver<()>) -> Result<()> {
151 let mut run_count = 0;
152 let mut output = BufWriter::new(sink());
153 let rate = Duration::from_nanos(options.rate_ns);
154
155 while options.should_repeat(run_count) {
156 match rx.try_recv() {
157 Ok(_) | Err(TryRecvError::Disconnected) => {
158 break;
159 }
160 Err(TryRecvError::Empty) => {}
161 }
162
163 let preparation_repeats = options.preparation_repeats.unwrap_or(1);
164 for _ in 0..preparation_repeats {
165 Self::recite_string(&options.preparation, &mut output, rate)?;
166 }
167
168 for mantra in &options.mantras {
169 mantra.recite(&mut output, rate)?;
170 }
171
172 let conclusion_repeats = options.conclusion_repeats.unwrap_or(1);
173 for _ in 0..conclusion_repeats {
174 Self::recite_string(&options.conclusion, &mut output, rate)?;
175 }
176
177 *total_count.lock() += 1;
178 run_count += 1;
179 }
180 Ok(())
181 }
182
183 pub fn start(&mut self) -> Result<()> {
185 self.stop()?;
187
188 let cloned_options = self.options.clone();
189 let cloned_count = self.count.clone();
190 let (tx, rx) = mpsc::channel();
191 thread::spawn(move || {
192 let _ = MantraMiner::run(cloned_options, cloned_count, rx);
193 });
194 self.stop_channel = Some(tx);
195 Ok(())
196 }
197
198 pub fn stop(&mut self) -> Result<()> {
200 if let Some(tx) = self.stop_channel.take() {
201 let _ = tx.send(());
202 }
203 Ok(())
204 }
205
206 pub fn options(&self) -> Options {
208 self.options.clone()
209 }
210
211 pub fn count(&self) -> usize {
213 *self.count.lock()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use anyhow::Result;
220 use std::{
221 io::{BufWriter, Write},
222 thread,
223 time::Duration,
224 };
225
226 use crate::{Mantra, MantraMiner, Options};
227
228 const PREPARATION: &str = "I take refuge in the Three Jewels and arise bodhicitta.";
229 const DEDICATION: &str = "I dedicate the merit of this practice to all sentient beings.";
230
231 fn simple_mantra() -> Mantra {
232 Mantra {
233 syllables: vec![
234 "om".to_string(),
235 "ma".to_string(),
236 "ni".to_string(),
237 "pad".to_string(),
238 "me".to_string(),
239 "hum".to_string(),
240 ],
241 repeats: None,
242 }
243 }
244
245 fn repeated_mantra() -> Mantra {
246 Mantra {
247 syllables: vec!["hri".to_string()],
248 repeats: Some(108),
249 }
250 }
251
252 #[test]
253 fn should_repeat() {
254 let mut options = Options::default();
255
256 options.repeats = Some(10);
257 assert_eq!(options.should_repeat(5), true);
258 assert_eq!(options.should_repeat(10), false);
259 assert_eq!(options.should_repeat(50), false);
260
261 options.repeats = None;
262 assert_eq!(options.should_repeat(5), true);
263 assert_eq!(options.should_repeat(10), true);
264 assert_eq!(options.should_repeat(50), true);
265 }
266
267 #[test]
268 fn recite_string() -> Result<()> {
269 let rate = Duration::from_nanos(10);
270 let buffer = Vec::with_capacity(100);
271 let mut output = BufWriter::new(buffer);
272 MantraMiner::recite_string(&Some(PREPARATION.to_string()), &mut output, rate)?;
273 output.flush()?;
274 assert_eq!(output.get_ref(), PREPARATION.as_bytes());
275 Ok(())
276 }
277
278 #[test]
279 fn recite_mantra() -> Result<()> {
280 let mantra = simple_mantra();
281 let rate = Duration::from_nanos(10);
282 let buffer = Vec::with_capacity(100);
283 let mut output = BufWriter::new(buffer);
284 mantra.recite(&mut output, rate)?;
285 output.flush()?;
286 assert_eq!(output.get_ref(), "om\nma\nni\npad\nme\nhum\n".as_bytes());
287 Ok(())
288 }
289
290 #[test]
291 fn set_repeats() -> Result<()> {
292 let options = Options {
293 preparation: None,
294 preparation_repeats: None,
295 mantras: vec![simple_mantra()],
296 conclusion: None,
297 conclusion_repeats: None,
298 rate_ns: 1000,
299 repeats: Some(10),
300 };
301 let mut miner = MantraMiner::new(options);
302 miner.start()?;
303 thread::sleep(Duration::from_millis(10));
304 miner.stop()?;
305 assert_eq!(miner.count(), 10);
306 Ok(())
307 }
308
309 #[test]
310 fn indefinite_repeats() -> Result<()> {
311 let options = Options {
312 preparation: None,
313 preparation_repeats: None,
314 mantras: vec![simple_mantra()],
315 conclusion: None,
316 conclusion_repeats: None,
317 rate_ns: 1000,
318 repeats: None,
319 };
320 let mut miner = MantraMiner::new(options);
321 miner.start()?;
322 thread::sleep(Duration::from_millis(10));
323 miner.stop()?;
324 assert!(miner.count() > 10);
325 Ok(())
326 }
327
328 #[test]
329 fn with_preparation_and_conclusion() -> Result<()> {
330 let options = Options {
331 preparation: Some(PREPARATION.to_string()),
332 preparation_repeats: None,
333 mantras: vec![simple_mantra()],
334 conclusion: Some(DEDICATION.to_string()),
335 conclusion_repeats: None,
336 rate_ns: 1000,
337 repeats: Some(3),
338 };
339 let mut miner = MantraMiner::new(options);
340 miner.start()?;
341 thread::sleep(Duration::from_millis(100));
342 miner.stop()?;
343 assert_eq!(miner.count(), 3);
344 Ok(())
345 }
346
347 #[test]
348 fn with_repeated_preparation_and_conclusion() -> Result<()> {
349 let options = Options {
350 preparation: Some(PREPARATION.to_string()),
351 preparation_repeats: Some(3),
352 mantras: vec![simple_mantra()],
353 conclusion: Some(DEDICATION.to_string()),
354 conclusion_repeats: Some(3),
355 rate_ns: 1000,
356 repeats: Some(3),
357 };
358 let mut miner = MantraMiner::new(options);
359 miner.start()?;
360 thread::sleep(Duration::from_millis(100));
361 miner.stop()?;
362 assert_eq!(miner.count(), 3);
363 Ok(())
364 }
365
366 #[test]
367 fn using_repeated_mantra() -> Result<()> {
368 let options = Options {
369 preparation: Some(PREPARATION.to_string()),
370 preparation_repeats: None,
371 mantras: vec![repeated_mantra()],
372 conclusion: Some(DEDICATION.to_string()),
373 conclusion_repeats: None,
374 rate_ns: 1000,
375 repeats: Some(3),
376 };
377 let mut miner = MantraMiner::new(options);
378 miner.start()?;
379 thread::sleep(Duration::from_millis(100));
380 miner.stop()?;
381 assert_eq!(miner.count(), 3);
382 Ok(())
383 }
384
385 #[test]
386 fn options() {
387 let options = Options {
388 preparation: None,
389 preparation_repeats: None,
390 mantras: vec![repeated_mantra()],
391 conclusion: None,
392 conclusion_repeats: None,
393 rate_ns: 1000,
394 repeats: Some(3),
395 };
396 let options_clone = options.clone();
397 let miner = MantraMiner::new(options);
398 assert_eq!(miner.options(), options_clone);
399 }
400}