and so it begins
Brijesh Wawdhane brijesh@wawdhane.com
Sat, 26 Oct 2024 08:30:40 +0530
9 files changed,
195 insertions(+),
0 deletions(-)
A
Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bsh" +version = "0.1.0"
A
Cargo.toml
@@ -0,0 +1,6 @@
+[package] +name = "bsh" +version = "0.1.0" +edition = "2021" + +[dependencies]
A
src/error.rs
@@ -0,0 +1,29 @@
+use std::fmt; +use std::io; + +#[derive(Debug)] +pub enum ShellError { + Io(io::Error), + CommandNotFound(String), + EnvVarError(String), + ParseError(String), +} + +impl fmt::Display for ShellError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ShellError::Io(e) => write!(f, "I/O error: {}", e), + ShellError::CommandNotFound(cmd) => write!(f, "Command not found: {}", cmd), + ShellError::EnvVarError(var) => write!(f, "Environment variable error: {}", var), + ShellError::ParseError(err) => write!(f, "Parse error: {}", err), + } + } +} + +impl std::error::Error for ShellError {} + +impl From<io::Error> for ShellError { + fn from(error: io::Error) -> Self { + ShellError::Io(error) + } +}
A
src/executor.rs
@@ -0,0 +1,49 @@
+use crate::{error::ShellError, parser}; +use std::env; +use std::process::{Command, Stdio}; + +pub fn execute(input: &str) -> Result<(), ShellError> { + // Use the new parser to get the command and arguments + let parsed_command = parser::parse(input).map_err(|e| ShellError::ParseError(e.to_string()))?; + + match parsed_command.command.as_str() { + // Handle built-in commands here + "cd" => { + let target_dir = if let Some(dir) = parsed_command.args.get(0) { + // Expand ~ to home directory + expand_tilde(dir) + } else { + // If no argument, default to home directory + env::var("HOME").map_err(|e| ShellError::EnvVarError(e.to_string()))? + }; + + std::env::set_current_dir(&target_dir).map_err(ShellError::from)?; + } + _ => { + let mut child = Command::new(&parsed_command.command) + .args(&parsed_command.args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .map_err(|_| ShellError::CommandNotFound(parsed_command.command.clone()))?; + + child.wait().map_err(ShellError::from)?; + } + } + Ok(()) +} + +// Function to expand tilde (~) to home directory +fn expand_tilde(path: &str) -> String { + if path.starts_with('~') { + let home = env::var("HOME").unwrap_or_else(|_| String::from("/")); + if path == "~" { + home + } else { + format!("{}{}", home, &path[1..]) // Append the rest of the path after ~ + } + } else { + path.to_string() // Return the original path if not starting with ~ + } +}
A
src/main.rs
@@ -0,0 +1,13 @@
+#![allow(unused)] + +mod error; +mod executor; +mod parser; +mod shell; +mod utils; + +fn main() -> Result<(), error::ShellError> { + let mut shell = shell::Shell::new(); + shell.run()?; + Ok(()) +}
A
src/parser.rs
@@ -0,0 +1,52 @@
+use std::str::FromStr; + +pub struct ParsedCommand { + pub command: String, + pub args: Vec<String>, +} + +pub fn parse(input: &str) -> Result<ParsedCommand, &'static str> { + let mut command = String::new(); + let mut args = Vec::new(); + let mut current_arg = String::new(); + let mut in_single_quote = false; + let mut in_double_quote = false; + + let chars = input.chars().collect::<Vec<_>>(); + + for c in chars { + match c { + '\'' if !in_double_quote => { + in_single_quote = !in_single_quote; // Toggle single quote state + } + '"' if !in_single_quote => { + in_double_quote = !in_double_quote; // Toggle double quote state + } + ' ' if !in_single_quote && !in_double_quote => { + // If we encounter a space and we're not in quotes, we finalize the current argument + if !current_arg.is_empty() { + if command.is_empty() { + command = current_arg.clone(); + } else { + args.push(current_arg.clone()); + } + current_arg.clear(); + } + } + _ => { + current_arg.push(c); // Add character to the current argument + } + } + } + + // Finalize the last argument + if !current_arg.is_empty() { + if command.is_empty() { + command = current_arg.clone(); + } else { + args.push(current_arg); + } + } + + Ok(ParsedCommand { command, args }) +}
A
src/shell.rs
@@ -0,0 +1,38 @@
+use crate::{error::ShellError, executor}; +use std::io::{self, Write}; + +pub struct Shell { + prompt: String, +} + +impl Shell { + pub fn new() -> Self { + Self { + prompt: "bsh> ".to_string(), + } + } + + pub fn run(&mut self) -> Result<(), ShellError> { + loop { + print!("{}", self.prompt); + io::stdout().flush().map_err(|e| ShellError::Io(e))?; + + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .map_err(|e| ShellError::Io(e))?; + let input = input.trim(); + + if input.is_empty() { + continue; + } + if input == "exit" { + println!("Exiting BSH..."); + break; + } + + executor::execute(input)?; + } + Ok(()) + } +}