vrsh 0.1.2

A simple shell written for my own learning.
use std::fmt;
use std::fmt::{Display, Formatter};
use std::io::Error;

use crate::expansions::InitialCmdOrCommentParser;
use crate::grammar::CommandParser;
use crate::replacements::ReplacementCmdParser;
use crate::shell::common::state::State;
use crate::shell::common::types::{
    Cmd, InitialCmd, InitialCmdOrComment, InitialCmdPart, ReplacementPart, ReplacementsCmd,
};
use crate::shell::handle_command::{handle_sub_command, CommandError};

pub enum ParseError {
    IO(std::io::Error),
    LALRPopErr(String, String),
    EvaluationError(CommandError),
    Comment,
    InputEmpty,
}

impl Display for ParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::IO(e) => write!(f, "io error: '{}'", e),
            ParseError::LALRPopErr(s, pass) => {
                write!(f, "failed parsing: '{}' in pass {}", s, pass)
            }
            ParseError::EvaluationError(cmd_err) => {
                write!(f, "failed to evaluate command: {}", cmd_err)
            }
            ParseError::Comment => write!(f, "comment encountered, ignore"),
            ParseError::InputEmpty => write!(f, "input empty, ignore"),
        }
    }
}

impl From<std::io::Error> for ParseError {
    fn from(err: Error) -> Self {
        ParseError::IO(err)
    }
}

impl From<CommandError> for ParseError {
    fn from(cmd_err: CommandError) -> Self {
        ParseError::EvaluationError(cmd_err)
    }
}

// 🏠
pub const HOME: &str = "~";

pub fn parse_input(input: String, state: &mut State) -> Result<Cmd, ParseError> {
    if input.is_empty() {
        return Err(ParseError::InputEmpty);
    }

    let expanded = parse_initial_cmd(&input, state)?;

    let command = evaluate_cmd(expanded, state)?;
    return Ok(command);
}

pub fn parse_initial_cmd(input: &str, state: &mut State) -> Result<String, ParseError> {
    let initial_cmd: InitialCmd = match InitialCmdOrCommentParser::new().parse(&input) {
        Ok(val) => match val {
            InitialCmdOrComment::InitialCmd(v) => v,
            InitialCmdOrComment::Comment => return Err(ParseError::Comment),
        },
        Err(e) => {
            return Err(ParseError::LALRPopErr(
                e.to_string(),
                String::from("initial"),
            ))
        }
    };

    Ok(expand_initial_cmd(initial_cmd, state)?)
}

fn expand_initial_cmd(cmd: InitialCmd, state: &mut State) -> Result<String, ParseError> {
    let mut text = "".to_string();
    for part in cmd.parts.into_iter() {
        match part {
            InitialCmdPart::String(val) => {
                text = text + val.as_str();
            }
            InitialCmdPart::Calculation(cmd) => {
                let inner = expand_initial_cmd(cmd, state)?;
                let new_cmd = evaluate_cmd(inner, state)?;
                text += handle_sub_command(new_cmd, state)?.as_str();
            }
            InitialCmdPart::SingleQuotedString(str) => {
                text = text + str.as_str();
            }
        }
    }

    Ok(text)
}

fn evaluate_cmd(cmd: String, state: &mut State) -> Result<Cmd, ParseError> {
    let replaced = perform_replacements(cmd, state)?;

    match CommandParser::new().parse(&replaced) {
        Ok(val) => Ok(val),
        Err(e) => Err(ParseError::LALRPopErr(
            e.to_string(),
            String::from("command"),
        )),
    }
}

fn perform_replacements(str: String, state: &State) -> Result<String, ParseError> {
    let replaced_cmd: ReplacementsCmd = match ReplacementCmdParser::new().parse(&str) {
        Ok(val) => val,
        Err(e) => {
            return Err(ParseError::LALRPopErr(
                e.to_string(),
                String::from("replacements"),
            ))
        }
    };
    Ok(handle_replaced_cmd(replaced_cmd, state))
}

fn handle_replaced_cmd(replacement_cmd: ReplacementsCmd, state: &State) -> String {
    let mut replaced_str = String::new();
    for part in replacement_cmd.parts.iter() {
        let val = match part {
            ReplacementPart::String(s) => s.to_string(),
            ReplacementPart::Word(word) => word
                .split(" ")
                .map(|w| perform_replacement(w.to_string(), state))
                .collect::<Vec<String>>()
                .join(" "),
            ReplacementPart::Variable(var) => read_var(var.to_string(), state).to_string(),
        };
        replaced_str = replaced_str + &val;
    }
    replaced_str
}

fn perform_replacement(str: String, state: &State) -> String {
    let mut replaced = str.clone();
    for (key, val) in state.aliases.iter() {
        if &str == key {
            replaced = val.clone();
            break;
        }
    }
    replaced.replace(HOME, state.home.as_str())
}

fn read_var(var: String, state: &State) -> &str {
    match state.variables.get(&var) {
        Some(val) => val,
        None => "",
    }
}