diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | src/builtins.rs | 10 | ||||
-rw-r--r-- | src/main.rs | 80 | ||||
-rw-r--r-- | src/prompt.rs | 57 |
7 files changed, 180 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..714dcaf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "rshell" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..933b672 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rshell" +version = "0.1.0" +authors = ["Aqua-sama <aqua@iserlohn-fortress.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b49642 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ + __ __ + ____ ___ __ _____ / /___ _____ ____/ / + / __ \/ _ \/ / / / _ \/ / __ `/ __ \/ __ / + / / / / __/ /_/ / __/ / /_/ / / / / /_/ / + /_/ /_/\___/\__,_/\___/ /\__,_/_/ /_/\__,_/ + out of foppery /_/ and whim + + +### What is this and why should I care? +It's a shell written in Rust. + +### Sounds dumb, how do I use it? +You build it yourself. Clone the repository, and run + cargo build + +### It doesn't work, what now? +Tough luck + diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..4ef2416 --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,10 @@ +use std::path::Path; + +pub fn cd(args: std::str::SplitWhitespace) -> std::path::PathBuf { + let new_dir = args.peekable().peek().map_or("/", |x| *x); + let root = Path::new(new_dir); + if let Err(e) = std::env::set_current_dir(&root) { + eprintln!("{}", e); + } + root.to_path_buf() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a4f3c25 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,80 @@ +use std::process::Command; +use std::process::Stdio; + +mod builtins; +mod prompt; + +fn main() { + println!("rshell motd"); + + let mut prompt = prompt::Prompt::new(); + + loop { + prompt.print(); + + let mut input = String::new(); + match std::io::stdin().read_line(&mut input) { + Ok(0) => return, // EOF + Ok(_) => {} // okay + Err(e) => { + eprintln!("error reading line: {}", e); + return; + } + }; + + let mut commands = input.trim().split(" | ").peekable(); + let mut previous_command: std::option::Option<std::process::Child> = None; + + while let Some(command) = commands.next() { + let mut parts = command.trim().split_whitespace(); + let command = match parts.next() { + Some(cmd) => cmd, + None => break, + }; + + let args = parts; + + match command { + "cd" => { + prompt.pwd = builtins::cd(args); + previous_command = None; + } + "exit" => return, + + command => { + let stdin = match previous_command { + None => Stdio::inherit(), + Some(cmd) => Stdio::from(cmd.stdout.unwrap()), + }; + let stdout = match commands.peek().is_some() { + true => Stdio::piped(), + false => Stdio::inherit(), + }; + + let child = Command::new(command) + .args(args) + .stdin(stdin) + .stdout(stdout) + .spawn(); + match child { + Ok(c) => previous_command = Some(c), + Err(e) => { + previous_command = None; + eprintln!("{}", e); + } + } + } + } + } + + if let Some(mut final_command) = previous_command { + match final_command.wait() { + Ok(ret) => match ret.code() { + Some(code) => println!("exit code [{}]", code), + None => println!("Process termed by signal"), + }, + Err(e) => eprintln!("error waiting on final command: {}", e), + } + } + } // loop +} diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 0000000..fd9c872 --- /dev/null +++ b/src/prompt.rs @@ -0,0 +1,57 @@ +use std::io::Write; + +pub struct Prompt { + home: std::path::PathBuf, + pub pwd: std::path::PathBuf, + username: std::string::String, + hostname: std::string::String, +} + +impl Prompt { + pub fn new() -> Prompt { + let home = match std::env::home_dir() { + Some(p) => p, + None => std::path::Path::new("/").to_path_buf(), + }; + let pwd = match std::env::current_dir() { + Ok(p) => p, + Err(e) => panic!(e), + }; + let username = match std::process::Command::new("whoami").output() { + Ok(s) => { + let output = std::string::String::from_utf8(s.stdout).unwrap(); + output.trim().to_string() + } + Err(e) => panic!(e), + }; + let hostname = match std::process::Command::new("hostname").output() { + Ok(s) => { + let output = std::string::String::from_utf8(s.stdout).unwrap(); + output.trim().to_string() + } + Err(e) => panic!(e), + }; + Prompt { + home, + pwd, + username, + hostname, + } + } + + pub fn print(&self) { + let path = if self.pwd == self.home { + self.pwd.display() + } else if self.pwd.starts_with(&self.home) { + self.pwd.strip_prefix(&self.home).unwrap().display() + } else { + self.pwd.display() + }; + + print!(" {USER}@{HOST} [{PWD}]\n$ ", USER=self.username, HOST=self.hostname, PWD=path); + match std::io::stdout().flush() { + Ok(_) => {} + Err(_) => println!(""), + }; + } +} |