mod ps; use super::Poem; use crate::compose::Environment; use crate::path; use crate::poem::anthology; use crate::poem::elements::rune::Rune; use crate::poem::read::Readable; use std::env; use std::{ io, sync::{Arc, Mutex}, }; pub trait Reciteable { fn recite(&self, env: &mut Environment, stdout: Option) -> Result; } impl Reciteable for Poem { fn recite(&self, env: &mut Environment, stdout: Option) -> Result { // Should we print to stdout or always capture it let stdout = stdout.unwrap_or(true); // Variable for storing the output of a piped verse let mut out: String = String::new(); // Keep track of pids for background processes let mut pids: Arc>> = Arc::new(Mutex::new(Vec::new())); // Check for aliases let mut vv = Poem::new(); for verse in self.iter() { let alias = match env.aliases.get(&verse.verb()) { Some(alias) => alias, None => { vv.push(verse.clone()); continue; } } .to_string(); let mut poem = Poem::read(alias).unwrap(); let len = poem.len(); for (i, new_verse) in poem.iter_mut().enumerate() { if verse.clause().is_some() && i + 1 == len { new_verse.stanza.append(&mut verse.clause().unwrap()); } vv.push(new_verse.clone()); } } // Loop through each verse in the poem for verse in vv.iter() { // Verse may need to be mutable let mut verse = verse.clone(); // Check for environment variables for word in verse.stanza.iter_mut() { while word.contains("$") { let mut name = vec![]; let mut idx = word.chars().position(|c| c == '$').unwrap() + 1; let bytes = word.as_bytes(); while idx < word.len() { let c = bytes[idx] as char; if !c.is_alphanumeric() { break; } name.push(c); idx += 1; } let name: String = format!("${}", name.iter().collect::()); let envar = name[1..].to_string(); let envar = match env::var(envar) { Ok(envar) => envar.to_string(), Err(_) => String::new(), }; *word = word.replace(name.as_str(), envar.as_str()); } // For any words in single quotes strings that began with a '$', // need to replace the ASCII SO placeholder with the '$' char once // more *word = word.replace('\x0e', "$"); } // Check if verse is a builtin let index = anthology::lookup(&verse.verb()); // Run interal poems let v = verse.clone(); let mut new_stanza = None; if verse.poems() { // Collect all the words that have vertical tabs let mut wordp_indicies = vec![]; let wordps = verse .stanza .iter_mut() .enumerate() .filter(|(_, w)| w.contains("\x0b")) .map(|(i, w)| { wordp_indicies.push(i + 1); w }) .collect::>(); // Loop through each word and replace with the output of the poem let mut poems = verse.poems.iter(); let mut j = 0; for wordp in wordps { let times = wordp .chars() .filter(|c| c == &'\x0b') .collect::() .len(); for _ in 0..times { let poem = match poems.next() { Some(poem) => poem, None => break, // TODO: Return an error }; let out = poem.recite(env, Some(false))?; if out.contains("\n") && index.is_none() { let mut out = out.split("\n"); let next = out.next().unwrap_or("").trim(); *wordp = wordp.replacen("\x0b", next, 1).to_string(); let (_, right) = v.stanza.split_at(wordp_indicies[j]); let mut left = vec![]; let mut right = right.to_vec(); loop { let next = match out.next() { Some(next) => next, None => break, } .to_string(); left.push(next); } left.append(&mut right); new_stanza = Some(left.clone()); } else { *wordp = wordp.replacen("\x0b", out.as_str(), 1).to_string(); } *wordp = wordp.replacen("\x0b", out.as_str(), 1).to_string(); } j += 1; } } match new_stanza { Some(stanza) => { let mut stanza = stanza.clone(); verse.stanza.append(&mut stanza); } None => {} }; // Incant the verse if it's a built-in let status = if index.is_some() { anthology::incant(&verse, &mut out, index.unwrap(), env) } else { // Checking for environment variables and running internal // poems may mean that the verb is empty now, so check it once // more // If it is empty, just continue to the next verse if !verse.verb().is_empty() { // Check if the verb exists on the PATH // If it doesn't exist, try refreshing the binary cache, and check // again // If it still doesn't exist, print an error if !verse.spellcheck(&env.bins) { env.bins = path::refresh(); if !verse.spellcheck(&env.bins) { eprintln!("dwvsh: {}: command not found", verse.verb()); if verse.meter != Rune::And { continue; } } } } else { continue; } // Incant the verse, based on its meter if stdout { match verse.io { Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, _ => match verse.meter { Rune::None => Rune::incant_none(&verse, &mut out)?, Rune::Couplet => Rune::incant_couplet(&verse, &mut out)?, Rune::Quiet => Rune::incant_quiet(&verse, &mut out, &mut pids)?, Rune::And => Rune::incant_and(&verse, &mut out)?, Rune::Continue => Rune::incant_continue(&verse, &mut out)?, _ => unreachable!(), }, } } else { match verse.io { Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, _ => Rune::incant_couplet(&verse, &mut out)?, } } }; // Break from the loop if the meter is not [Rune::Continue], and // if the status is not 0 // For [Rune::Quiet], [Rune::incant_quiet] will always return 0 if verse.meter != Rune::Continue && status != 0 { break; } } // If we've successfully exited the loop, then all verses were properly // recited Ok(out.trim().to_string()) } }