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 its [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 { /// Environment variables to fork with pub notes: Stanza, /// A command and its arguments (also see [Stanza]) pub stanza: Stanza, /// Denote the couplet status (for piping text in/out) /// /// * `0`: Not a couplet /// * `1`: Left side of a couplet /// * `2`: Right side of a couplet /// * `3`: Both sides of a couplet /// /// ``` /// ls | grep Ca | lolcat /// ^ ^ ^ /// | | 2: right side of a couplet /// | 3: both sides of a couplet /// 1: left side of a couplet /// ``` pub couplet: u8, /// A list of IO operations to be performed /// /// For instance, read into STDIN, write STDOUT to a file, etc. May /// be empty. Also see [Rune] for more details. pub io: Vec, /// Input paths /// /// A list of files to read into STDIN, may be empty. pub ip: Stanza, /// Output paths /// /// A list of files to send STDOUT to, may be empty. pub op: Stanza, /// Error paths /// /// A list of files to send STDERR to, may be empty. pub ep: Stanza, /// Internal poems (i.e. 'ls \`ls\`') /// /// A list of poems to evaluate before evaluating this one. pub poems: Vec, /// Interpret how to handle a poem after forking to its command /// /// * `None`: Indicates the end of a poem /// * `Couplet`: Pipe the output of this command into the next (`|`) /// * `Quiet`: Fork the called process into the background (`&`) /// * `And`: Run the next command, only if this one succeeds (`&&`) /// * `Continue`: Run the next command, regardless of whether or not /// this command succeeds (`;` or a newline in a file) /// /// Also see [Rune] for more details. pub meter: Rune, } /// Granularity for [spellcheck()][Verse::spellcheck] /// /// Indicates if a given [verb][Verse::verb] exists, and where. #[derive(Debug, Clone)] pub enum Spelling { /// The [verb][Verse::verb] is a full path, and that path exists FullPath, /// The [verb][Verse::verb] is on the `$PATH` OnPath(usize), /// The [verb][Verse::verb] is a built-in shell command 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. pub fn new() -> Self { Verse { notes: Stanza::new(), 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) -> Vec { match self.stanza.len() { 0 => vec![], 1 => vec![], _ => self.stanza[1..].to_vec(), } } /// Set the [Verse]'s command's environment /// /// Use self.notate to set inline environment variables pub fn notate(&self, command: &mut Command) { for var in self.notes.clone() { let split: Vec<&str> = var.split("=").collect(); if split.len() == 1 { command.env(split[0], ""); } else { command.env(split[0], split[1]); } } } /// 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::Notes) => self.notes.push(word.iter().collect()), 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][Verse::verb] exists /// /// First checks if the [verb][Verse::verb] is a built-in shell /// command. Then checks if the [verb][Verse::verb] is on the /// `$PATH`. If not, then the [verb][Verse::verb] is a either a full /// path, or it does not exist on the system. /// /// # Examples /// ``` /// let command_full_path = vec!["/bin/ls"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_on_path = vec!["cargo", "build", "--release"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_built_in = vec!["alias", "g=git"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_no_exist = vec!["thisdoesntexist"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let verse = Verse::new(); /// verse.stanza = Stanza::new(command_full_path); /// verse.spellcheck(&env.bins); // -> Some(Spelling::FullPath) /// /// let verse = Verse::new(); /// verse.stanza = Stanza::new(command_on_path); /// verse.spellcheck(&env.bins); // -> Some(Spelling::OnPath) /// /// let verse = Verse::new(); /// verse.stanza = Stanza::new(command_built_in); /// verse.spellcheck(&env.bins); // -> Some(Spelling::BuiltIn) /// /// let verse = Verse::new(); /// verse.stanza = Stanza::new(command_no_exist); /// verse.spellcheck(&env.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 [recite()][crate::poem::recite] function calls this /// [incant()][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()); self.notate(&mut command); incant!(self.verb(), command, out, pids, env, self) } } } }