mod alias; mod cd; mod exit; mod export; mod source; mod which; use crate::compose::Environment; use std::io; use std::process::{Output, Stdio}; /// A static list of all the built-in commands static INDEX: [&str; 8] = [ "alias", "cd", "exit", "export", "source", "unalias", "unset", "which", ]; /// Lookup the index of a built-in command /// /// Looks up the index of a built-in command in [INDEX], accounting for /// aliases. /// /// # Aliases /// * quit -> exit /// * set -> export pub fn lookup(verb: &str) -> Option { let verb = match verb { "quit" => "exit", // Alias 'quit' to 'exit' "set" => "export", // Alias 'set' to 'export' _ => verb, }; INDEX.iter().position(|v| v.to_string() == verb) } /// Dummy interface for STDIN /// /// A helper struct for dealing with STDIN in built-in commands. /// Currently, there are no built-in commands that actually use this, /// but it needs to exist for the `incant!()` macro to function /// properly. See [Anthology] for more details. #[derive(Debug, PartialEq, Eq, Clone)] pub struct AnthologyStdin { /// Bytes data: Vec, } impl AnthologyStdin { /// Create a new instance of AnthologyStdin pub fn new() -> Self { AnthologyStdin { data: Vec::new() } } /// Return a mutable version of self pub fn as_mut(&mut self) -> Option<&mut Self> { Some(self) } /// Write bytes specified by data into STDIN pub fn write_all(&mut self, data: &[u8]) -> Result<(), io::Error> { self.data.append(&mut data.to_vec()); Ok(()) } } /// Interface for built-in commands /// /// The implementation of [Anthology] is designed to mimic /// [std::process::Command], in order to avoid code redundancy. The /// internal structure of [Anthology] is fairly irrelevant to this /// `impl`, with the exception of the `stdin`, and `output` fields. In /// addition to the documentation below, it may also be helpful to read /// through the `incant!()` macro, so see how processes are actually /// forked. #[derive(Debug, Clone)] pub struct Anthology { /// Name of the built-in command (see [lookup()] and/or [INDEX]) verb: String, /// Arguments to pass to the built-in command clause: Option>, /// Indicates STDIN should be read in (but no built-ins use STDIN) uin: bool, /// Indicates STDOUT should be captured uout: bool, /// Indicates STDERR should be captured uerr: bool, /// Compatability with [std::process::Command] pub stdin: AnthologyStdin, /// Stores the built-in output (return code, STDOUT, STDERR), also /// needed for compatability with [std::process::Command] output: Option, } impl Anthology { /// Create a new instance of a built-in command /// /// Sets up a built-in command with default values. /// /// # Examples /// ``` /// let mut command = Anthology::new("alias"); /// ``` pub fn new(i: usize) -> Self { Anthology { verb: INDEX[i].to_string(), clause: None, uin: false, uout: false, uerr: false, stdin: AnthologyStdin::new(), output: None, } } /// Setup arguments to the built-in command /// /// Sets the 'clause' field of the [Anthology] struct, which are arguments /// to be passed to the built-in command. pub fn args(&mut self, clause: Vec) { self.clause = match clause.is_empty() { true => None, false => Some(clause.clone()), }; } /// Read to STDIN /// /// None of the built-in commands will currently read from STDIN for any /// reason, so this is just a dummy function. pub fn stdin(&mut self, _stdin: Stdio) { self.uin = true; } /// Capture STDOUT pub fn stdout(&mut self, _stdout: Stdio) { self.uout = true; } /// Capture STDERR pub fn stderr(&mut self, _stderr: Stdio) { self.uerr = true; } /// Set the process group /// /// This is only needed if a process is forked into the background. /// Technically this is possible with the built-ins, but not really /// useful, so this function simply does nothing. As a result, /// built-ins that get forked into the background always return /// immediately. pub fn process_group(&mut self, _id: usize) {} /// Get the process id /// /// This is only needed if a process is forked into the background. /// Will always return `0` for built-in commands. Also see /// [process_group()][Anthology::process_group]. pub fn id(&mut self) -> i32 { 0 } /// Run a built-in command /// /// Runs a built-in command based on the `verb`, appropriately /// passing arguments and [Environment] fields as necessary. pub fn spawn(&mut self, env: &mut Environment) -> Result { // Incant the built-in and set the output self.output = Some(match self.verb.as_str() { "alias" => alias::incant(&self.clause, self.uout, &mut env.aliases), "cd" => cd::incant(&self.clause, self.uerr), "exit" => exit::incant(), "export" => export::incant(&self.clause, self.uout), "source" => source::incant(&self.clause, self.uout, self.uerr, env), "unalias" => alias::unincant(&self.clause, self.uerr, &mut env.aliases), "unset" => export::unincant(&self.clause, self.uerr), "which" => which::incant(&self.clause, self.uout, self.uerr, env), _ => unreachable!(), }); Ok(self.clone()) } /// Get the output of a built-in /// /// Return the [Output] of a built-in command. This function needed /// for compatibility with [std::process::Command]. pub fn wait_with_output(&self) -> Result { match &self.output { Some(output) => Ok(output.clone()), None => Err(io::Error::new(io::ErrorKind::Other, "not spawned")), } } }