BFGG4TBFOQVCRQ3JKNVRFAAUNJYZAJ7QWGA6VVTDYL3R53GZMSTAC use crate::commands::{cmd_with_output, piped_cmd, return_shellcommand, PipedShellCommand, Redirection, ShellCommand};use sflib::ensure_directory;use std::env::var as env_var;use std::path::PathBuf;#[cfg(feature = "readline")]use std::process::exit;#[cfg(feature = "readline")]use rustyline::{error::ReadlineError, Editor};#[cfg(not(feature = "readline"))]use std::io::Write;/// Holds all important informations for and about the shell.pub struct ShellState {pub args: Vec<String>,pub prompt: String,pub user: String,pub home: String,pub na: String,pub share_dir: String,pub cd_prev_dir: Option<PathBuf>,pub config_dir: String,pub config: String,pub history_file: String,pub edit_mode: String,pub bell_style: String,pub history_auto_add_lines: bool,pub history_size: usize,pub history_spaces_ignored: bool,}/// Gets the current time with the format specified if the `time` feature is enabled./// Otherwise it returns the format string back.fn get_time(format: &str) -> String {#[cfg(not(feature = "time"))]{format.to_string()}#[cfg(feature = "time")]{let date = chrono::Local::now();date.format(format).to_string()}}// Process the input to run the appropriate builtin or external command.pub fn process_input(shell_state: &mut ShellState, input: &str) {if input.is_empty() {return;}let command = ShellCommand::new(input);ShellCommand::run(shell_state, command);}#[cfg(feature = "readline")]pub fn run_loop(rl: &mut Editor<()>, mut shell_state: ShellState) {loop {let prompt = rl.readline(&ShellState::eval_prompt(&mut shell_state));match prompt {Ok(line) => {rl.add_history_entry(line.as_str());if line.starts_with("exit") {if line.contains(' ') {let input = line.split(' ').collect::<Vec<&str>>()[1];rl.save_history(&shell_state.history_file).unwrap();exit(input.parse::<i32>().unwrap_or(0));} else {rl.save_history(&shell_state.history_file).unwrap();exit(0);}}process_input(&mut shell_state, &line);}Err(ReadlineError::Interrupted) => {continue;}Err(ReadlineError::Eof) => {break;}Err(err) => {println!("Error: {:?}", err);break;}}}rl.save_history(&shell_state.history_file).unwrap();}#[cfg(not(feature = "readline"))]pub fn run_loop(mut shell_state: ShellState) {loop {let prompt = ShellState::eval_prompt(&mut shell_state);print!("{}", prompt);std::io::stdout().flush().unwrap();let input = crate::parse_input("interactive");process_input(&mut shell_state, &input);}}impl ShellState {/// Initalizes the shell state with all the informations needed.////// `cd_prev_dir` doesnt hold a value, because there is no previous dir yet.pub fn init() -> ShellState {let args = std::env::args().collect();let prompt = env_var("PROMPT").unwrap_or_else(|_| String::from("F<GREEN>B<BLACK>%{b}%{u}[crust]-[%{CL}]:%{re} "));let user_command = return_shellcommand(String::from("whoami"), Vec::new(), Redirection::NoOp);let user = env_var("USER").unwrap_or_else(|_| cmd_with_output(&user_command)).trim().to_string();let home = env_var("HOME").unwrap_or_else(|_| ["/home/", user.as_str()].concat());let na = String::from("no args");let share_dir = [&home, "/.local/share/crust"].concat();let cd_prev_dir = None;let config_dir = [&home, "/.config/crust/"].concat();let config = [&config_dir, "config"].concat();let history_file = [&share_dir, "/crust.history"].concat();let shell_state = ShellState {args,prompt,user,home,na,share_dir,cd_prev_dir,config_dir,config,history_file,edit_mode: String::from("emacs"),bell_style: String::from("nothing"),history_auto_add_lines: true,history_size: 500,history_spaces_ignored: true,};ensure_directory(&shell_state.share_dir, true).unwrap();ensure_directory(&shell_state.config_dir, true).unwrap();shell_state}pub fn eval_prompt(&mut self) -> String {let mut evaled_prompt = self.prompt.clone();let commands = crate::prompt::get_commands_from_input(&self.prompt);let mut command_output: String;for command in commands {if command.args.contains(&String::from("|")) {command_output = piped_cmd(&PipedShellCommand::from(&command));} else {command_output = cmd_with_output(&command);}evaled_prompt = evaled_prompt.replace(format!("%({})", command.to_string().trim()).as_str(),command_output.trim(),);}let files = crate::prompt::get_files_from_input(&evaled_prompt);for file in files {evaled_prompt = evaled_prompt.replace(format!("%[{}]", file.to_string().trim()).as_str(),crate::builtins::cat::cat(&[file]).trim(),);}// Parse the prompt and replace the colors with the escape sequences.evaled_prompt = crate::prompt::parse_prompt_effects(&evaled_prompt);let substitutions = vec!["%{CL}", "%{CS}", "%{D}", "%{H}", "%{T12}", "%{T24}", "%{U}", "\\n"];for to_subst in substitutions {let mut subst = String::new();match to_subst {"%{CL}" => subst = std::env::current_dir().unwrap().display().to_string(),"%{CS}" => {let cwd = std::env::current_dir().unwrap().display().to_string();subst = cwd.split("/").collect::<Vec<&str>>()[cwd.split("/").count() - 1].to_string();},"%{D}" => subst = get_time("%a %b %d"),"%{H}" => subst = self.home.clone(),"%{T12}" => subst = get_time("%I:%M %p").to_string(),"%{T24}" => subst = get_time("%H:%M").to_string(),"%{U}" => subst = self.user.clone(),"\\n" => subst = '\n'.to_string(), // Needed to support newlines in the prompt_ => (),}evaled_prompt = evaled_prompt.replace(to_subst, &subst);}evaled_prompt}}/// Tokenizes the input, returning a vector of every character in `input`.pub fn tokenize(input: &str) -> Vec<String> {input.chars().map(|t| t.to_string()).collect::<Vec<String>>()}fn push_to_vec(from_vec: &mut Vec<String>, to_vec: &mut Vec<String>) {let element = from_vec.concat();// Don't push to the vector if element is empty.if element.is_empty() {return;}to_vec.push(element);from_vec.clear();}/// Creates a lexified vector from a tokenized one./// Example, if the tokenized vec was:/// ```/// ["e", "c", "h", "o",/// " ",/// "\"", "a", "r", "g", " ", "1" "\"",/// " ",/// "\"", "a", "r", "g", " ", "2" "\""]/// ```/// It would return:/// `["echo", "arg 1", "arg 2"]`pub fn lex_tokenized_input(input: &str) -> Vec<String> {let tokenized_vec = tokenize(input);// This is the final vector that will be returned.let mut lexed_vec: Vec<String> = Vec::new();// This is a temporary vec that gets pushed to lexed_vec.let mut tmp_vec: Vec<String> = Vec::new();// Same as tmp_vec except this is for anything in quotes.let mut quoted_vec: Vec<String> = Vec::new();// These two bools are used for checking if the character is in quotes,// and if the quotes part of the match statement was ran.let mut quoted = false;let mut quotes_ran = false;for (idx, character) in tokenized_vec.iter().enumerate() {match character.as_str() {"\"" | "'" => {if quotes_ran {push_to_vec(&mut quoted_vec, &mut lexed_vec);quoted = false;quotes_ran = false;} else {quoted = true;quotes_ran = true;}}" " => {if quoted {quoted_vec.push(character.to_string());} else {push_to_vec(&mut tmp_vec, &mut lexed_vec);}}// Instead of explicitely checking for everything,// don't we just append any character that doesn't// require extra work, such as quotations._ => {if quoted {quoted_vec.push(character.to_string());} else {tmp_vec.push(character.to_string());// Needed to push the last element to lexed_vec.if idx == tokenized_vec.len() - 1 {push_to_vec(&mut tmp_vec, &mut lexed_vec);}}}}}lexed_vec}
use crate::commands::ShellCommand;use crate::shared_functions::tokenize;use std::fmt::{Formatter, Display};#[derive(Debug, Copy, Clone)]/// An enum for background colors.pub enum BgColor {Black = 40,Red = 41,Green = 42,Yellow = 43,Blue = 44,Magenta = 45,Cyan = 46,White = 47,}#[derive(Debug, Copy, Clone)]/// An enum for foreground colors.pub enum FgColor {Black = 30,Red = 31,Green = 32,Yellow = 33,Blue = 34,Magenta = 35,Cyan = 36,White = 37,}#[derive(Debug, Copy, Clone)]/// A unified enum for accessing both foreground and background colors.pub enum Color {Bg(BgColor),Fg(FgColor),}impl Display for BgColor {// Displays the full escape sequence.fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {write!(f, "\x1b[{}m", &self.to_u8())}}impl Display for FgColor {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {write!(f, "\x1b[{}m", &self.to_u8())}}impl BgColor {pub fn to_u8(self) -> u8 {match self {BgColor::Black => 40,BgColor::Red => 41,BgColor::Green => 42,BgColor::Yellow => 43,BgColor::Blue => 44,BgColor::Magenta => 45,BgColor::Cyan => 46,BgColor::White => 47,}}}impl FgColor {pub fn to_u8(self) -> u8 {match self {FgColor::Black => 30,FgColor::Red => 31,FgColor::Green => 32,FgColor::Yellow => 33,FgColor::Blue => 34,FgColor::Magenta => 35,FgColor::Cyan => 36,FgColor::White => 37,}}}/// An enum for the different types of font effects.pub enum FontEffects {ResetBackground = 49,ResetEverything = 0,ResetForeground = 39,Bold = 1,Italics = 3,Underline = 4,}impl Display for FontEffects {// Displays the full escape sequence.fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {write!(f, "\x1b[{}m", &self.to_u8())}}impl FontEffects {pub fn to_u8(self: &FontEffects) -> u8 {match self {FontEffects::ResetBackground => 49,FontEffects::ResetEverything => 0,FontEffects::ResetForeground => 39,FontEffects::Bold => 1,FontEffects::Italics => 3,FontEffects::Underline => 4,}}}#[derive(Debug, Clone)]pub struct EscapeSequence {escape_sequence: String,}impl Display for EscapeSequence {// Displays the full escape sequence.fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {write!(f, "{}", self.escape_sequence)}}impl EscapeSequence {pub fn builder() -> EsBuilder {EsBuilder::default()}}#[derive(Clone, Debug)]pub struct EsBuilder {escape_sequence: String,}impl Default for EsBuilder {fn default() -> EsBuilder {EsBuilder {escape_sequence: String::from("\x1b["),}}}impl EsBuilder {pub fn new() -> EsBuilder {EsBuilder {escape_sequence: String::from("\x1b["),}}pub fn append(&mut self, argument: u8) {self.escape_sequence.push_str(&argument.to_string());self.escape_sequence.push(';');}pub fn build(&mut self) -> EscapeSequence {self.escape_sequence.pop();self.escape_sequence.push('m');EscapeSequence {escape_sequence: self.escape_sequence.clone(),}}}pub fn get_files_from_input(input: &str) -> Vec<String> {let tokenized_vec = tokenize(input);let mut tmp_vec: String = String::new();let mut file_vec: Vec<String> = Vec::new();let mut file = false;let mut file_end = false;let mut tok_iter = tokenized_vec.iter().peekable();while tok_iter.peek() != None {let tok_iter_char = tok_iter.next().unwrap().as_str();if file_end {file_vec.push(tmp_vec.to_string());tmp_vec.clear();file = false;file_end = false;continue;}if tok_iter_char == "%" && tok_iter.peek().unwrap().as_str() == "[" {file = true;} else if file {if tok_iter_char == "[" {continue;} else if tok_iter_char != "]" {tmp_vec.push_str(tok_iter_char);} else if tok_iter_char == "]" {file_end = true;continue;}}} {if file_end {file_vec.push(tmp_vec.to_string());file_vec.clear();}}file_vec}pub fn get_commands_from_input(input: &str) -> Vec<ShellCommand> {let tokenized_vec = tokenize(input);let mut tmp_vec: String = String::new();let mut command_vec: Vec<ShellCommand> = Vec::new();let mut command = false;let mut command_end = false;let mut tok_iter = tokenized_vec.iter().peekable();while tok_iter.peek() != None {let tok_iter_char = tok_iter.next().unwrap().as_str();if command_end {command_vec.push(ShellCommand::new(tmp_vec.as_str()));tmp_vec.clear();command = false;command_end = false;continue;}if tok_iter_char == "%" && tok_iter.peek().unwrap().as_str() == "(" {command = true;} else if command {if tok_iter_char == "(" {continue;} else if tok_iter_char != ")" {tmp_vec.push_str(tok_iter_char);} else if tok_iter_char == ")" {command_end = true;continue;}}} {if command_end {command_vec.push(ShellCommand::new(tmp_vec.as_str()));tmp_vec.clear();}}command_vec}pub fn parse_prompt_effects(input: &str) -> String {let tokenized_vec = tokenize(input);let mut tok_iter = tokenized_vec.iter().peekable();let mut es_builder = EscapeSequence::builder();let mut es_seqs: Vec<(String, String)> = Vec::new();let mut fin_prompt = String::new();let mut tmp_string = String::new();let mut pos_option = false;let mut pos_bgcol = false;let mut bgcol = false;let mut fgcol = false;let mut pos_fgcol = false;let mut option = false;let mut option_fin = false;let mut es_fin = false;let mut not_pos_option;// Go through every character in the input, until the end is reached.while tok_iter.peek() != None {// Get the next char unwrapping is safe as we ensured that the next char is never None.let cur_char = tok_iter.next().unwrap().as_str();// Match for certain key chars like % for options, F for fgcolor, B for bgcolor.match cur_char {"%" => {not_pos_option = false;if !option_fin {option_fin = false;}pos_option = true;}"B" => {not_pos_option = false;option_fin = false;pos_bgcol = true;pos_fgcol = false;}"F" => {not_pos_option = false;option_fin = false;pos_bgcol = false;pos_fgcol = true;}_ => {// TODO(zeno): Push every char to the prompt and pop it, if it belongs to an option// if the char wasn't matched, it is not a possbiel option, but could be an// argument for an option, like a color or font effect// this is checked laternot_pos_option = true;// If the option is finished, the escape sequence is finished too, except the char// is matched by a possible identifier.if option_fin {es_fin = true;}}}// If an escape sequence has finished, build the sequence and push it in the prompt string.if es_fin {//if es_seqs.is_empty() {// continue;//}// Take the identifier and the sequence out of the vector.// There is an identifier, because we can't know if we meant fg/bg just from the color,// so I (zeno) introduced an indentifier which could be useful,// if one value can mean different things in different contexts.for (ty, seq) in &es_seqs {// Default arg, should be reset to 0.let mut arg = 0;// Actually check for the identifier to know what escape sequence we should use.match ty.as_str() {"O" => {arg = match seq.as_str() {"b" => FontEffects::Bold.to_u8(),"i" => FontEffects::Italics.to_u8(),"rb" => FontEffects::ResetBackground.to_u8(),"re" => FontEffects::ResetEverything.to_u8(),"rf" => FontEffects::ResetForeground.to_u8(),"u" => FontEffects::Underline.to_u8(),_ => 0,};}"B" => {let tmp_arg = match seq.as_str() {"BLACK" => Color::Bg(BgColor::Black),"RED" => Color::Bg(BgColor::Red),"GREEN" => Color::Bg(BgColor::Green),"YELLOW" => Color::Bg(BgColor::Yellow),"BLUE" => Color::Bg(BgColor::Blue),"MAGENTA" => Color::Bg(BgColor::Magenta),"CYAN" => Color::Bg(BgColor::Cyan),_ => Color::Bg(BgColor::White),};arg = match tmp_arg {Color::Fg(fg) => {fg.to_u8()},Color::Bg(bg) => {bg.to_u8()}};}"F" => {let tmp_arg = match seq.as_str() {"BLACK" => Color::Fg(FgColor::Black),"RED" => Color::Fg(FgColor::Red),"GREEN" => Color::Fg(FgColor::Green),"YELLOW" => Color::Fg(FgColor::Yellow),"BLUE" => Color::Fg(FgColor::Blue),"MAGENTA" => Color::Fg(FgColor::Magenta),"CYAN" => Color::Fg(FgColor::Cyan),_ => Color::Fg(FgColor::White),};arg = match tmp_arg {Color::Fg(fg) => {fg.to_u8()},Color::Bg(bg) => {bg.to_u8()}};}_ => (),}es_builder.append(arg);}// Push finished escape sequence to prompt string.fin_prompt.push_str(es_builder.build().escape_sequence.as_str());// Clear the EsBuilder, so the old escape sequence doenst get expended.es_builder = EsBuilder::new();// Clear the es_seqs vector from any escape sequences, because we begin a new set.es_seqs.clear();option_fin = false;es_fin = false;}// Check if prev determined possible option, color is actually an option, color// and set appropriate flags for checkingif pos_option && cur_char == "{" {option = true;option_fin = false;pos_option = false;continue;} else if pos_bgcol && cur_char == "<" {bgcol = true;option_fin = false;pos_bgcol= false;continue;} else if pos_fgcol && cur_char == "<" {fgcol = true;option_fin = false;pos_fgcol = false;continue;}// handle found optionif option {if cur_char != "}" {// if option doesn't end, push char to tmp_stringtmp_string.push_str(cur_char);} else if cur_char == "}" {// if option ends, push option to es_seqs vecoption_fin = true;match tmp_string.as_str() {"b" | "i" | "rb" | "re" | "rf" | "u" => es_seqs.push(("O".to_string(), tmp_string.clone())),_ => fin_prompt.push_str(format!("%{{{}}}", tmp_string).as_str()),}tmp_string.clear();option = false;}continue;// Handle foudn bgcol} else if bgcol {if cur_char != ">" {// if bgcol doesn't end, push char to tmp_stringtmp_string.push_str(cur_char);} else if cur_char == ">" {// if bgcol ends, push option to es_seqs vecoption_fin = true;es_seqs.push(("B".to_string(), tmp_string.clone()));tmp_string.clear();bgcol = false;}continue;// handle found fgcol} else if fgcol {if cur_char != ">" {// if fgcol doesn't end, push char to tmp_stringtmp_string.push_str(cur_char);} else if cur_char == ">" {// if fgcol ends, push option to es_seqs vecoption_fin = true;es_seqs.push(("F".to_string(), tmp_string.clone()));tmp_string.clear();fgcol = false;}continue;}// check if char belongs to an option, otherwise push it as is to prompt stringif not_pos_option && (!option || !bgcol || !fgcol) {fin_prompt.push_str(cur_char);}};fin_prompt}
mod builtins;mod commands;mod prompt;mod shared_functions;#[cfg(feature = "readline")]use rustyline::Editor;use shared_functions::{process_input, run_loop, ShellState};/// A function to parse input, used for the barebones prompt.pub fn parse_input(op: &str) -> String {if op == "interactive" {let mut input = String::new();std::io::stdin().read_line(&mut input).expect("failed to read user input");input.trim().to_string()} else {std::env::args().collect::<Vec<String>>().get(2).unwrap().replace('"', "").trim().to_string()}}/// A helper function to run a non-interactive command,/// it will automatically check if `-c` was passed as an arg/// and run commands non-interactively.pub fn non_interactive(shell_state: &mut ShellState) {if shell_state.args.get(1).unwrap_or(&shell_state.na) == "-c" {let input = parse_input("non-interactive");process_input(shell_state, &input);std::process::exit(0);}}fn main() {let mut shell_state = ShellState::init();// Default config:// ```// bell style="nothing"// edit mode="emacs"// history auto add lines=true// history file="`shell_state.history`"// history size=500// history spaces ignored=true// prompt="`shell_state.prompt`"// should be invalid="N/A"// ```let default_config = format!("bell style=\"{}\"\nedit mode=\"{}\"\nhistory auto add lines={}\nhistory file=\"{}\"\nhistory size={}\nprompt=\"{}\"\nshould be invalid=\"N/A\"",&shell_state.bell_style,&shell_state.edit_mode,&shell_state.history_auto_add_lines,&shell_state.history_file,&shell_state.history_size,&shell_state.prompt);#[cfg(feature = "readline")]let options = conf::get_options(shell_state.config.as_str(), &default_config);#[cfg(feature = "readline")]if let Ok(options) = options {for option in options {match option.0.as_str() {"bell style" => shell_state.bell_style = option.1,"edit mode" => shell_state.edit_mode = option.1,"history auto add lines" => shell_state.history_auto_add_lines = option.1.parse::<bool>().unwrap(),"history file" => shell_state.history_file = option.1,"history size" => shell_state.history_size = option.1.parse::<usize>().unwrap(),"history spaces ignored" => shell_state.history_spaces_ignored = option.1.parse::<bool>().unwrap(),"prompt" => shell_state.prompt = option.1,_ => println!("[WARNING]: '{}' is an invalid option, ignoring.", option.0)}}}#[cfg(feature = "readline")]let bell_style: rustyline::config::BellStyle = match shell_state.bell_style.as_str() {"nothing" => rustyline::config::BellStyle::None,"bell" => rustyline::config::BellStyle::Audible,"flashing" => rustyline::config::BellStyle::Visible,_ => rustyline::config::BellStyle::None};#[cfg(feature = "readline")]let edit_mode: rustyline::EditMode = match shell_state.edit_mode.as_str() {"emacs" => rustyline::EditMode::Emacs,"vi" => rustyline::EditMode::Vi,_ => rustyline::EditMode::Emacs};#[cfg(feature = "readline")]let config = rustyline::Config::builder().auto_add_history(shell_state.history_auto_add_lines).bell_style(bell_style).edit_mode(edit_mode).history_ignore_space(shell_state.history_spaces_ignored).max_history_size(shell_state.history_size).build();non_interactive(&mut shell_state);#[cfg(feature = "readline")]let mut rl = Editor::with_config(config);#[cfg(feature = "readline")]if rl.load_history(&shell_state.history_file).is_err() {println!("There was no previous history to load.");}#[cfg(not(feature = "readline"))]run_loop(shell_state);#[cfg(feature = "readline")]run_loop(&mut rl, shell_state);}
use crate::builtins::{calc::calc, cat::cat, cd::cd, echo::echo, help::help, ls::ls};use crate::shared_functions::lex_tokenized_input;use crate::ShellState;use sflib::ensure_directory;use std::io::{Read, Write};use std::path::Path;use std::process::{Command, Stdio};#[derive(Debug, Clone)]pub enum Redirection {Overwrite,Append,NoOp,}/// This struct is used to construct a shellcommand,/// be it a builtin or external command./// The `name` String holds the actual command name, like `echo` or `cargo`./// The `args` vector hold all arguments. This includes the pipe char,/// which is later used to detect and construct a pipe.#[derive(Debug, Clone)]pub struct ShellCommand {pub name: String,pub args: Vec<String>,pub redirection: Redirection,}pub fn return_shellcommand(name: String, args: Vec<String>, redirection: Redirection) -> ShellCommand {ShellCommand {name,args,redirection,}}/// Implement `Display` for `ShellCommand` which will in turn also implement `.to_string()`.impl std::fmt::Display for ShellCommand {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {write!(f, "{} {}", self.name, self.args.join(" "))}}impl ShellCommand {/// Constructs a new `ShellCommand` and returns it./// Takes the input given by the user, unprocessed.pub fn new(input: &str) -> ShellCommand {fn get_redirection_type(input: &str) -> Redirection {if input.contains(">>") {Redirection::Append} else if input.contains('>') {Redirection::Overwrite} else {Redirection::NoOp}}let lexed_vec = lex_tokenized_input(input);ShellCommand {name: lexed_vec[0].clone(),args: lexed_vec[1..].to_vec(),redirection: get_redirection_type(input),}}/// Takes a `ShellCommand`, figures out what to do given the name,/// then executes it./// All builtins have to be listed here and point to their given function./// It is prefered that they return a string, which gets printed here,/// and not by the actual function, to make testing easier.pub fn run(shell_state: &mut ShellState, command: ShellCommand) {// check for piping first, because otherwise redirecting builtins// would match the builtin and pipingif command.args.contains(&String::from("|"))|| command.args.contains(&String::from(">>"))|| command.args.contains(&String::from(">")){println!("{}", piped_cmd(&PipedShellCommand::from(&command)));} else {match command.name.as_str() {"calc" => println!("{}", calc(&command.args)),"cat" => println!("{}", cat(&command.args)),"cd" => cd(shell_state, &command),"echo" => println!("{}", echo(&command.args)),"help" => help(&command.args),"ls" => print!("{}", ls(command.args)),"pwd" => println!("{}", std::env::current_dir().unwrap().display()),_ => {cmd(&command);}}}}}/// This struct is a vector, containing all commands and their arguments/// in a pipeline. Every command is represented by a `ShellCommand`.#[derive(Debug)]pub struct PipedShellCommand {pub commands: Vec<ShellCommand>,}impl PipedShellCommand {/// Constructs a `PipedShellCommand` from a given `ShellCommand`./// Takes a `ShellCommand` containing a pipe.pub fn from(input: &ShellCommand) -> PipedShellCommand {fn get_redirection_type(input: &ShellCommand) -> Redirection {if input.args.contains(&String::from(">>")) {Redirection::Append} else if input.args.contains(&String::from(">")) {Redirection::Overwrite} else {Redirection::NoOp}}let parts = input.args.split(|arg| {arg == &String::from("|") ||// Check for appending first because `>` would match both.arg == &String::from(">>") ||arg == &String::from(">")});let mut commands: Vec<ShellCommand> = Vec::new();for (idx, part) in parts.enumerate() {if idx == 0 {let command = ShellCommand {name: input.name.clone(),args: part[0..].to_vec(),redirection: get_redirection_type(input),};commands.push(command);} else {let command = ShellCommand {name: part[0].clone(),args: part[1..].to_vec(),redirection: get_redirection_type(input),};commands.push(command);}}PipedShellCommand { commands }}}/// Helper function to a command, optionally with args.pub fn cmd(command: &ShellCommand) {let child = Command::new(&command.name).args(&command.args).spawn();if let Ok(..) = child {let output = child.unwrap().wait_with_output().unwrap().stdout;let usable_output = std::str::from_utf8(&output).unwrap();println!("{}", usable_output);} else {println!("Sorry, '{}' was not found!", command.name);}}pub fn cmd_with_output(command: &ShellCommand) -> String {let child = Command::new(&command.name).args(&command.args).output();if let Ok(..) = child {std::string::String::from_utf8_lossy(&child.unwrap().stdout).to_string()} else {String::from("Sorry, '{}' was not found!".to_string())}}/// This is a function for checking if the command is piped./// Used to remove a lot of duplicate code.pub fn is_piped(args: &[String], cmd: &str) {fn run_pipe(cmd: &str, args: &[String], redirection: Redirection) {let command = return_shellcommand(cmd.to_string(), args.to_vec(), redirection);let pipe = PipedShellCommand::from(&command);piped_cmd(&pipe);}if args.contains(&"|".to_string()) {run_pipe(cmd, args, Redirection::NoOp);} else if args.contains(&">>".to_string()) {run_pipe(cmd, args, Redirection::Append);} else if args.contains(&">".to_string()) {run_pipe(cmd, args, Redirection::Overwrite);} else {// Do nothing.}}/// Returns a `Child` of a command wrapped in a `Result`.fn return_child(cmd: &str, args: &[String]) -> Result<std::process::Child, ()> {Command::new(cmd).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn().or(Err(()))}/// Takes a `PipedShellCommand`, iterating over all `ShellCommand` structs/// contained by it, checking if it is the first or the last in the pipeline,/// and taking the appropriate meassurements to pipe stdout.pub fn piped_cmd(pipe: &PipedShellCommand) -> String {let mut output_prev = String::new();match pipe.commands[0].name.as_str() {"cat" => output_prev = cat(&pipe.commands[0].args.clone()),"echo" => output_prev = echo(&pipe.commands[0].args.clone()),"calc" => output_prev = calc(&pipe.commands[0].args.clone()),"ls" => output_prev = ls(pipe.commands[0].args.clone()),_ => {let child = return_child(&pipe.commands[0].name.clone(), &pipe.commands[0].args);if child.is_err() {println!("{} failed", pipe.commands[0].name.clone());}child.unwrap().stdout.unwrap().read_to_string(&mut output_prev).unwrap();}}for (idx, command) in pipe.commands.iter().enumerate() {if idx == 0 {continue;}if idx == pipe.commands.len() - 1 {break;}match command.name.as_str() {"cat" => output_prev = cat(&command.args.clone()),"echo" => output_prev = echo(&command.args.clone()),"calc" => output_prev = calc(&command.args.clone()),"ls" => output_prev = ls(command.args.clone()),_ => {let child = return_child(&command.name.clone(), &command.args);match child {Ok(mut child) => {child.stdin.take().unwrap().write_all(output_prev.as_bytes()).unwrap();output_prev = "".to_string();child.stdout.unwrap().read_to_string(&mut output_prev).unwrap();}Err(_) => println!("{} failed", command.name.clone()),}}}}let file_string = &pipe.commands[pipe.commands.len() - 1].name;if file_string.contains('/') {let file_vec: Vec<&str> = file_string.split('/').collect();let mut parent_dir = String::new();for (id, chunk) in file_vec.iter().enumerate() {if id == file_vec.len() - 1 {break;}let part = format!("{}/", chunk);parent_dir.push_str(&part);}ensure_directory(&parent_dir, true).unwrap();}let file_path = &Path::new(file_string);match pipe.commands[pipe.commands.len() - 1].redirection {Redirection::Overwrite => {let mut file = std::fs::File::create(file_path).unwrap();file.write_all(output_prev.as_bytes()).unwrap();return String::new();}Redirection::Append => {let mut file = std::fs::OpenOptions::new().write(true).append(true).create(true).open(file_path).unwrap();writeln!(file, "{}", output_prev).unwrap();return String::new();}Redirection::NoOp => (),}match pipe.commands[pipe.commands.len() - 1].name.as_str() {"cat" => cat(&pipe.commands[pipe.commands.len() - 1].args.clone()),"echo" => echo(&pipe.commands[pipe.commands.len() - 1].args.clone()),"calc" => calc(&pipe.commands[pipe.commands.len() - 1].args.clone()),"ls" => ls(pipe.commands[pipe.commands.len() - 1].args.clone()),_ => {let child = return_child(&pipe.commands[pipe.commands.len() - 1].name.clone(),&pipe.commands[pipe.commands.len() - 1].args,);match child {Ok(mut child) => {child.stdin.take().unwrap().write_all(output_prev.as_bytes()).unwrap();let mut output = String::new();match child.stdout.take().unwrap().read_to_string(&mut output) {Err(why) => return format!("ERROR: could not read cmd2 stdout: {}", why),Ok(_) => output,}}Err(_) => pipe.commands[pipe.commands.len() - 1].name.clone(),}}}}