initial commit
This commit is contained in:
commit
7b4f2ce45c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target
|
4
Cargo.lock
generated
Normal file
4
Cargo.lock
generated
Normal file
@ -0,0 +1,4 @@
|
||||
[root]
|
||||
name = "rust-shell"
|
||||
version = "0.1.0"
|
||||
|
4
Cargo.toml
Normal file
4
Cargo.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "rust-shell"
|
||||
version = "0.1.0"
|
||||
authors = ["Mahdi Dibaiee <mdibaiee@aol.com>"]
|
47
src/commands/cd.rs
Normal file
47
src/commands/cd.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
pub fn exec(directory: &str, args: Vec<String>) -> 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::<String>(&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 <directory>\n
|
||||
|
||||
special:
|
||||
.. => parent
|
||||
. => current");
|
||||
}
|
34
src/commands/echo.rs
Normal file
34
src/commands/echo.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use super::option_parser::parse;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn exec(args: Vec<String>) {
|
||||
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 <line1> <line2> ... <lineN>\n\n
|
||||
|
||||
Options:\n
|
||||
--break=n Adds |n| line breaks before message\n");
|
||||
}
|
29
src/commands/help.rs
Normal file
29
src/commands/help.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use super::{cd, ls, echo};
|
||||
|
||||
pub fn exec(args: Vec<String>) {
|
||||
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 <command> 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");
|
||||
}
|
||||
}
|
67
src/commands/ls.rs
Normal file
67
src/commands/ls.rs
Normal file
@ -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<String>) {
|
||||
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<T: Iterator<Item=fs::DirEntry>>(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 <directory>\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");
|
||||
}
|
5
src/commands/mod.rs
Normal file
5
src/commands/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod cd;
|
||||
pub mod help;
|
||||
pub mod ls;
|
||||
pub mod echo;
|
||||
mod option_parser;
|
27
src/commands/option_parser.rs
Normal file
27
src/commands/option_parser.rs
Normal file
@ -0,0 +1,27 @@
|
||||
pub fn parse(args: Vec<String>) -> (Vec<String>, Vec<String>) {
|
||||
let iter = args.into_iter();
|
||||
let mut options: Vec<String> = vec![];
|
||||
let mut parameters: Vec<String> = vec![];
|
||||
|
||||
for item in iter {
|
||||
let i: &str = item.as_ref();
|
||||
if i.starts_with("--") {
|
||||
|
||||
let split: Vec<String> = i.split("=").map(|x| x.to_string()).collect();
|
||||
let key = split[0].chars().skip(2).collect::<String>();
|
||||
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::<String>();
|
||||
options.push(string.to_string());
|
||||
options.push("".to_string());
|
||||
} else {
|
||||
|
||||
parameters.push(item.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
(options, parameters)
|
||||
}
|
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod commands;
|
63
src/main.rs
Normal file
63
src/main.rs
Normal file
@ -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<String>) {
|
||||
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<String> = line.split(" ").map(|x| x.to_string()).collect();
|
||||
let mut iter = params.into_iter();
|
||||
|
||||
let cmd = iter.next().unwrap();
|
||||
let rest: Vec<String> = iter.collect();
|
||||
|
||||
(cmd, rest)
|
||||
}
|
||||
|
||||
fn run_command(directory: &str, command: String, args: Vec<String>) -> () {
|
||||
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());
|
||||
}
|
Loading…
Reference in New Issue
Block a user