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