use super::Poem; use crate::compose::Environment; use crate::path; use crate::poem::anthology; use crate::poem::elements::rune::Rune; use crate::poem::elements::stanza::Stanza; use std::env; use std::{ io, sync::{Arc, Mutex}, }; pub trait Reciteable { fn recite(&self, env: &mut Environment) -> Result, io::Error>; } impl Reciteable for Poem { /// Recite a [Poem] /// /// Runs the commands specified in all verses for a given [Poem]. See /// [crate::poem::Verse::incant] for more details on how processes are /// forked. In addition to running each [crate::poem::Verse], /// [Poem::recite] also checks for the presence of environment variables, /// as well as sub-poems, before running the [crate::poem::Verse]'s /// [Stanza]. fn recite(&self, env: &mut Environment) -> Result, io::Error> { // Variable for storing the output of a piped verse let mut out: Vec = Vec::new(); // Keep track of pids for background processes let mut pids: Arc>> = Arc::new(Mutex::new(Vec::new())); // Loop through each verse in the poem for verse in self.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: Option = 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 mut out = poem.recite(env)?; match out.last() { Some(last) => { if *last == b'\n' { out.remove(out.len() - 1); } } None => {} } let out = String::from_utf8_lossy(&out); 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, 1).to_string(); } } j += 1; } } match new_stanza { Some(stanza) => { let mut stanza = stanza.clone(); verse.stanza.append(&mut stanza); } None => {} }; // Check if the verse's verb exists let mut spell = None; 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 spell = verse.spellcheck(&env.bins); if !spell.is_some() { env.bins = path::refresh(); spell = verse.spellcheck(&env.bins); if !spell.is_some() { eprintln!("dwvsh: {}: command not found", verse.verb()); if verse.meter != Rune::And { continue; } } } } // Incant the verse if it's a built-in let status = verse.incant(&mut out, &mut pids, env, spell)?; // 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) } }