From 7b4f2ce45c77f2cf2a7536cd585d1adb8acf2ef4 Mon Sep 17 00:00:00 2001 From: Mahdi Dibaiee Date: Sat, 2 May 2015 18:01:16 +0430 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 4 +++ Cargo.toml | 4 +++ src/commands/cd.rs | 47 ++++++++++++++++++++++++ src/commands/echo.rs | 34 ++++++++++++++++++ src/commands/help.rs | 29 +++++++++++++++ src/commands/ls.rs | 67 +++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 5 +++ src/commands/option_parser.rs | 27 ++++++++++++++ src/lib.rs | 1 + src/main.rs | 63 ++++++++++++++++++++++++++++++++ 11 files changed, 282 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/commands/cd.rs create mode 100644 src/commands/echo.rs create mode 100644 src/commands/help.rs create mode 100644 src/commands/ls.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/option_parser.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..668193f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "rust-shell" +version = "0.1.0" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e3f1599 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "rust-shell" +version = "0.1.0" +authors = ["Mahdi Dibaiee "] diff --git a/src/commands/cd.rs b/src/commands/cd.rs new file mode 100644 index 0000000..2dd743b --- /dev/null +++ b/src/commands/cd.rs @@ -0,0 +1,47 @@ +use std::path::Path; +use std::fs; + +pub fn exec(directory: &str, args: Vec) -> String { + let dir = args[0].to_string(); + let current_path = Path::new(directory); + + // Match . and .. and return early if needed + match dir.as_ref() { + "." => return directory.to_string(), + ".." => return current_path.parent().unwrap().to_str().unwrap().to_string(), + _ => () + }; + + let new_path = Path::new::(&dir); + + let joined_path = current_path.join(new_path); + let path = joined_path.as_path(); + + let path_string = match path.to_str() { + Some(value) => value.to_string(), + None => directory.to_string() + }; + + if !check_folder(&path) { + println!("Directory not found {}", path_string); + directory.to_string() + } else { + path_string + } +} + +fn check_folder(directory: &Path) -> bool { + match fs::read_dir(directory) { + Ok(..) => true, + Err(..) => false + } +} + +pub fn help() { + println!(" Change Directory\n + usage: cd \n + + special: + .. => parent + . => current"); +} diff --git a/src/commands/echo.rs b/src/commands/echo.rs new file mode 100644 index 0000000..d1ba762 --- /dev/null +++ b/src/commands/echo.rs @@ -0,0 +1,34 @@ +use super::option_parser::parse; +use std::str::FromStr; + +pub fn exec(args: Vec) { + let (options, mut params) = parse(args); + + for param in params.iter_mut() { + param.push_str("\n"); + } + + let mut options_iter = options.iter(); + let break_option = options_iter.position(|x| *x == "break".to_string()); + + let add_break: u32 = match break_option { + Some(..) => u32::from_str(options_iter.next().unwrap()).unwrap(), + None => 0u32 + }; + + for i in 0..add_break { + println!(""); + }; + + let message: String = params.iter().map(|x| x.as_ref()).collect(); + + println!("{}", message); +} + +pub fn help() { + println!(" Prints out stuff\n + usage: echo ... \n\n + + Options:\n + --break=n Adds |n| line breaks before message\n"); +} diff --git a/src/commands/help.rs b/src/commands/help.rs new file mode 100644 index 0000000..2f3fc40 --- /dev/null +++ b/src/commands/help.rs @@ -0,0 +1,29 @@ +use super::{cd, ls, echo}; + +pub fn exec(args: Vec) { + if args.len() > 0 { + + match args[0].as_ref() { + "cd" => cd::help(), + "ls" => ls::help(), + "echo" => echo::help(), + _ => println!("Command not found") + } + + } else { + println!(" + I wrote this program to learn Rust.\n\n + use help for info about an specific command.\n\n + + The shell supports only two kinds of arguments:\n + Options:\n + Single-dash options with no value e.g |ls -a|\n + Double-dash options in key=value format, spaces not allowed |echo --break=2|\n + Parameters:\n + Anything that's not an option, is a parameter\n\n + + Complex things like pipes are not implemented.\n + Commands:\n + cd, ls, pwd\n"); + } +} diff --git a/src/commands/ls.rs b/src/commands/ls.rs new file mode 100644 index 0000000..18ab6ae --- /dev/null +++ b/src/commands/ls.rs @@ -0,0 +1,67 @@ +use std::path::Path; +use std::fs; +use std::fs::File; +use super::option_parser::parse; + +pub fn exec(directory: &str, args: Vec) { + let (options, params) = parse(args); + + let dir: &str = if params.len() > 0 { + ¶ms[0] + } else { + directory + }; + + let dir_path = Path::new(dir); + let dir_entry = fs::read_dir(dir_path).unwrap().map(|x| x.unwrap()); + let details = options.contains(&"l".to_string()); + + if !&options.contains(&"a".to_string()) { + let filtered = dir_entry.filter_map(|entry| { + let path = entry.path(); + let is_hidden = path.file_name().unwrap().to_str().unwrap().starts_with("."); + + if is_hidden { + None + } else { + Some(entry) + } + }); + + print_files(filtered, details); + } else { + print_files(dir_entry, details); + }; +} + +fn print_files>(entries: T, detailed: bool) { + + if detailed { + println!("Name\tSize"); + }; + + for entry in entries { + let path = entry.path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + + if !detailed { + println!("{}", file_name); + continue; + }; + + let file = File::open(&path).unwrap(); + let meta = file.metadata().unwrap(); + let size = meta.len(); + + println!("{}\t{}", file_name, size); + }; +} + +pub fn help() { + println!(" List directory contents\n + usage: ls \n + If no directory is specified, the current directory's files are listed\n\n + Options:\n + -a Show hidden files + -l Show details\n"); +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..56032b0 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,5 @@ +pub mod cd; +pub mod help; +pub mod ls; +pub mod echo; +mod option_parser; diff --git a/src/commands/option_parser.rs b/src/commands/option_parser.rs new file mode 100644 index 0000000..0e93a98 --- /dev/null +++ b/src/commands/option_parser.rs @@ -0,0 +1,27 @@ +pub fn parse(args: Vec) -> (Vec, Vec) { + let iter = args.into_iter(); + let mut options: Vec = vec![]; + let mut parameters: Vec = vec![]; + + for item in iter { + let i: &str = item.as_ref(); + if i.starts_with("--") { + + let split: Vec = i.split("=").map(|x| x.to_string()).collect(); + let key = split[0].chars().skip(2).collect::(); + let value = split[1].to_string(); + options.push(key); + options.push(value); + } else if i.starts_with("-") { + let chars = i.chars().skip(1); + let string: String = chars.collect::(); + options.push(string.to_string()); + options.push("".to_string()); + } else { + + parameters.push(item.to_string()); + } + }; + + (options, parameters) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..82b6da3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod commands; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..542d2cf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,63 @@ +extern crate rust_shell as shell; + +use shell::commands; +use std::io; +use std::io::Write; +use std::process::Command; +use std::str; + +fn main() { + println!("Welcome to Rust Shell, try |help|"); + + let mut directory: String = "/".to_string(); + + loop { + let (cmd, args) = read(&directory); + + match cmd.as_ref() { + "help" => commands::help::exec(args), + "cd" => directory = commands::cd::exec(&directory, args), + "ls" => commands::ls::exec(&directory, args), + "echo" => commands::echo::exec(args), + _ => run_command(&directory, cmd, args) + }; + } +} + +fn read(directory: &str) -> (String, Vec) { + print!("{} ~ $ ", directory); + let mut stdout = io::stdout(); + // Flush to ensure stdout is printed immediately + stdout.flush().unwrap(); + + let mut stdin = io::stdin(); + let mut line: String = "".to_string(); + + stdin.read_line(&mut line).unwrap(); + + // Last character is a line-break we don't need + line.pop(); + + let params: Vec = line.split(" ").map(|x| x.to_string()).collect(); + let mut iter = params.into_iter(); + + let cmd = iter.next().unwrap(); + let rest: Vec = iter.collect(); + + (cmd, rest) +} + +fn run_command(directory: &str, command: String, args: Vec) -> () { + let out = match Command::new(command) + .args(&args) + .current_dir(directory) + .output() { + Ok(out) => out.stdout, + Err(e) => { + println!("Error: {}", e); + vec![0u8] + } + }; + + println!("{}", str::from_utf8(&out).unwrap()); +}