use super::rune::Rune; use super::stanza::Stanza; use super::word::Word; use crate::poem::anthology; use crate::poem::anthology::Anthology; use crate::poem::Poem; mod logic; use crate::compose::Environment; use crate::incant; use libc::{waitpid, WNOHANG}; use std::fmt::Debug; use std::fs::OpenOptions; use std::io::{self, Read, Write}; use std::os::unix::process::CommandExt; use std::path::Path; use std::process::{Child, Command, Output, Stdio}; use std::sync::{Arc, Mutex}; /// A [Stanza] and it's [meter](Rune) /// /// In addition to a [Stanza] and a [meter](Rune), this also holds a [bool] /// value called `couplet`, indicating that it needs to accept input on `STDIN` /// from the previous [Verse]. #[derive(Debug, Clone)] pub struct Verse { pub stanza: Stanza, pub couplet: u8, pub io: Vec, pub ip: Stanza, pub op: Stanza, pub ep: Stanza, pub poems: Vec, pub meter: Rune, } #[derive(Debug, Clone)] pub enum Spelling { FullPath, OnPath(usize), BuiltIn(usize), } pub trait Forkable { fn fork(&mut self, env: &mut Environment) -> Result; } impl Forkable for Command { fn fork(&mut self, _env: &mut Environment) -> Result { self.spawn() } } impl Forkable for Anthology { fn fork(&mut self, env: &mut Environment) -> Result { self.spawn(env) } } impl Verse { /// Create a new [Verse] /// /// Returns a new [Verse], with an empty [Stanza], a meter of [Rune::None], /// and `couplet` set to 0. /// /// # Fields /// stanza - The command (a `verb()` and a `clause()`) /// couplet - Indicates couplet status /// 0: Not a couplet (`cat Cargo.toml`) /// 1: Left side of a couplet (`cat Cargo.toml | ...`) /// 2: Right side of a couplet (`... | lolcat`) /// 3: Sandwiched between couplets (`... | grep Ca | ...`) /// io - A list of IO operations ([Rune::Read], [Rune::Write], etc.) /// ip - A list of filenames for reading into STDIN when: /// [Rune::Read] /// is specified /// op - A list of filenames for redirecting STDOUT to when: /// [Rune::Write], /// [Rune::WriteAll], /// [Rune::Addendum], /// and/or [Rune::AddendumAll] /// is specified /// ep - A list of filenames for redirecting STDERR to when: /// [Rune::Write2], /// [Rune::WriteAll], /// [Rune::Addendum2], /// and/or [Rune::AddendumAll] /// is specified /// poems - Internal commands to run before the [Verse] is recited /// ([Rune::Poem]). /// meter - Determines how the verse is recited in relation to the other /// verses in the [Poem]. /// [Rune::None] -> Run the command and print the output /// [Rune::Couplet] -> Pipe the output of this verse into the next /// [Rune::Quiet] -> Run in the background /// [Rune::And] -> Run the next verse only if this verse succeeds /// [Rune::Continue] -> Run the next verse, regardless of whether /// or not this verse succeeds pub fn new() -> Self { Verse { stanza: Stanza::new(), couplet: 0, io: Vec::new(), ip: Stanza::new(), op: Stanza::new(), ep: Stanza::new(), poems: Vec::new(), meter: Rune::None, } } /// Get the [Verse]'s verb /// /// Return the program to be forked pub fn verb(&self) -> String { self.stanza[0].clone() } /// Get the [Verse]'s clause /// /// Return program arguments, if they exist pub fn clause(&self) -> Option> { match self.stanza.len() { 0 => None, 1 => None, _ => Some(self.stanza[1..].to_vec()), } } /// Alias to [Verse].stanza.push() pub fn push(&mut self, word: String) { self.stanza.push(word); } /// Alias to [Verse].stanza.is_empty() pub fn is_empty(&self) -> bool { self.stanza.is_empty() } /// Alias to [Verse].stanza.clear() pub fn clear(&mut self) { self.stanza.clear(); self.io.clear(); self.poems.clear(); } /// Check if the [Verse] contains any internal poems pub fn poems(&self) -> bool { if self.poems.len() > 0 { return true; } false } /// Push a word to the [Verse]'s [Stanza] /// /// Push a word to the [Verse]'s [Stanza], or one of its IO channels. If /// `word` is empty, this function will simply return without performing /// any operations. A channel of [None] will push to the [Verse]'s /// [Stanza], while IO [Rune]s will determine which IO channel a word will /// get pushed onto. /// /// # Arguments /// word - The word to push onto the [Stanza]/channel /// channel - Specifiy which channel to use /// /// # Examples /// ``` /// word.push("c"); /// word.push("a"); /// word.push("t"); /// verse.add(&mut word, None); // Pushes onto [Stanza] /// verse.add(&mut word, Some(Rune::Write)); // Pushes onto [op] /// verse.add(&mut word, Some(Rune::WriteAll)); // Pushes onto [op], [ep] /// ``` pub fn add(&mut self, word: &mut Word, channel: Option) { // Do nothing if the stack is empty if word.is_empty() { return; } // Push the word match channel { Some(Rune::Read) => self.ip.push(word.iter().collect()), Some(Rune::Write) | Some(Rune::Addendum) => self.op.push(word.iter().collect()), Some(Rune::Write2) | Some(Rune::Addendum2) => self.ep.push(word.iter().collect()), Some(Rune::WriteAll) | Some(Rune::AddendumAll) => { self.op.push(word.iter().collect()); self.ep.push(word.iter().collect()); } Some(_) | None => self.push(word.iter().collect()), } // Clear the stack word.clear(); } /// Check if the `verb()` exists in the `$PATH` /// /// First checks if the `verb()` is a relative or full path. If it is, /// check whether or not it exists. If it does exist, return true, /// otherwise seeif the `verb()` is cached in our list of binaries. Search is /// done in $PATH order. /// /// # Examples /// ``` /// let bins = vec!["cargo", "ruby", "cat"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_success = vec!["cargo", "build", "--release"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_fail = vec!["make", "-j8"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let stanza_success = Stanza::new(command_success); /// let stanza_fail = Stanza::new(command_fail); /// /// stanza_success.spellcheck(bins) // -> Some(i) /// stanza_fail.spellcheck(bins) // -> None /// ``` pub fn spellcheck(&self, bins: &Vec) -> Option { // An empty verb (i.e. the empty string) cannot be a program, so // return false // Thanks to the parsing in Poem::read, however, it's // unlikely for this to happen if self.verb().is_empty() { return None; } // Check for built-ins let i = anthology::lookup(self.verb().as_str()); match i { Some(i) => return Some(Spelling::BuiltIn(i)), None => {} }; // Only search the $PATH if a full or relative path was not given, or // if the path given does not exist if !Path::new(self.verb().as_str()).exists() { // Try to find a binary in our path with the same name as the verb // Searches in $PATH order for (i, path) in bins.iter().enumerate() { if path.split('/').last().unwrap() == self.verb() { return Some(Spelling::OnPath(i)); } } // If it was not found, return None return None; } // Return the length of bins if the full path or relative path exists Some(Spelling::FullPath) } /// Run a command /// /// The [Poem]::recite() function calls this [Verse::incant] function for /// each verse it contains. This function handles the actual setup and /// spawning (forking) of a new process specified in the [Verse]. It will /// also run IO operations for the verse, and setup appropriate coupling, /// as per the [Verse]'s own details, contained throughout its fields. pub fn incant( &mut self, out: &mut Vec, pids: &mut Arc>>, env: &mut Environment, spell: Option, ) -> Result { // Read files into 'out' if Rune::Read is present in the verse's IO if self.io.contains(&Rune::Read) { // Enable piping on stdin self.couplet += 2; // Read all files specified after '<' into 'out', since there may // also be piped output from the last command for path in self.ip.iter() { let mut file = OpenOptions::new().read(true).open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; out.append(&mut contents.as_bytes().to_vec()); } } // Build and run the command match spell { Some(Spelling::BuiltIn(i)) => { let mut command = Anthology::new(i); incant!(self.verb(), command, out, pids, env, self) } _ => { let mut command = Command::new(self.verb()); incant!(self.verb(), command, out, pids, env, self) } } } }