summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-02-19 17:09:07 -0700
committerRory Dudley2024-02-19 17:09:07 -0700
commitd98ab111eebfcf5e6327daaec17b5de578d64a41 (patch)
tree14552e07c1f6e5bdcc053fee86b3c78ce84cad1f
parent08be85a2fc508450c6361af4ef38a7dcd3efbde5 (diff)
downloaddwarvish-evryloop.tar.gz
Path refresh refactor, comments, and error messagesevryloop
The 'eval' function was renamed to 'repl'. The code to refresh the $PATH was moved into it's own function: 'prefresh', and is now being called in three locations: - At the beginning of 'repl', before the main loop - Inside the main loop, possibly during a path search if the command is not initially found - Inside the main loop, if the call to Command::spawn() throws an error, and the error is ErrorKind::NotFound Doc comments were added for each function, as well as a few more comments throughout that detail the program's control flow. The error messages in the main repl loop were cleaned up to have a more consistent style, and to provide more/better detail.
-rw-r--r--src/main.rs145
1 files changed, 102 insertions, 43 deletions
diff --git a/src/main.rs b/src/main.rs
index c27bad7..1dc0217 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,70 @@
use ctrlc;
use std::fs;
-use std::io;
-use std::io::Write;
+use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
-fn eval(paths: &[&str], prompt: &str) {
+/// Refresh the shell's $PATH
+///
+/// This function caches all valid paths within within the directories
+/// specified.
+///
+/// # Arguments
+/// * `paths` - A reference to a vector that holds a list to the shell $PATHs
+///
+/// # Returns
+/// * `bins: Vec<String>` - A new cache of all valid file paths in $PATH
+///
+/// # Examples
+/// ```
+/// let paths = vec!["/bin"];
+/// let paths = paths.into_iter().map(Path::new).collect();
+/// let mut bins = prefresh(&paths);
+/// ...
+/// // A situation occurs where the $PATH needs to be refreshed
+/// bins = prefresh(&paths)
+/// ```
+fn prefresh(paths: &Vec<&Path>) -> Vec<String> {
let mut bins: Vec<String> = Vec::new();
+ for path in paths {
+ let files = fs::read_dir(path).expect(
+ format!(
+ "dwvsh: error: unable to read the contents of {}",
+ path.display().to_string()
+ )
+ .as_str(),
+ );
+
+ for file in files {
+ bins.push(file.unwrap().path().display().to_string());
+ }
+ }
+
+ bins
+}
+
+/// Starts the main shell loop
+///
+/// # Arguments
+/// * `paths` - A reference to a vector that holds a list to the shell $PATHs
+/// * `prompt` - A string slice indicating the shell's prompt
+///
+/// # Examples
+/// ```
+/// fn main() {
+/// let paths = vec!["/bin"];
+/// let paths = paths.into_iter().map(Path::new).collect();
+/// let prompt = "|> ";
+/// ...
+/// repl(&paths, prompt);
+/// }
+/// ```
+fn repl(paths: &Vec<&Path>, prompt: &str) {
+ // Initial path refresh on startup
+ let mut bins: Vec<String> = prefresh(paths);
+
+ // Main shell loop
loop {
// Output the prompt
io::stdout().flush().unwrap();
@@ -18,7 +75,7 @@ fn eval(paths: &[&str], prompt: &str) {
let mut input = String::new();
let bytes = io::stdin()
.read_line(&mut input)
- .expect("Unable to evaluate the input string");
+ .expect("dwvsh: error: unable to evaluate the input string");
// Check if we've reached EOF (i.e. <C-d>)
if bytes == 0 {
@@ -34,11 +91,11 @@ fn eval(paths: &[&str], prompt: &str) {
break;
}
- // Parse command and arguments
+ // Parse command
let mut split = input.split(' ');
- let cmd = match split.next() {
+ let mut cmd = match split.next() {
Some(str) if str.trim().is_empty() => continue,
- Some(str) => str.trim(),
+ Some(str) => String::from(str.trim()),
None => continue,
};
@@ -62,7 +119,7 @@ fn eval(paths: &[&str], prompt: &str) {
match std::env::set_current_dir(path) {
Ok(_) => continue,
Err(_) => {
- println!("cd: Unable to change into {}", path);
+ println!("cd: unable to change into {}", path);
continue;
}
}
@@ -70,7 +127,6 @@ fn eval(paths: &[&str], prompt: &str) {
// Check if the file exists, if given a pull or relative path
// TODO: Check if file at the path is executable (i.e. +x)
- let mut cmd = String::from(cmd);
if !Path::new(cmd.as_str()).exists() {
let b = bins.clone();
// Check if the command exists in $PATH if a full or relative path
@@ -79,32 +135,23 @@ fn eval(paths: &[&str], prompt: &str) {
// If the command is not found the first time, try refreshing the
// path first, and only print an error if if it's not found after
// the path refresh
- cmd = String::from(
- match b
- .clone()
- .iter()
- .find(|b| b.split("/").last().unwrap() == cmd)
- {
- Some(cmd) => cmd,
- None => {
- for path in paths {
- let files =
- fs::read_dir(path).expect("Unable to read files in your path");
- for file in files {
- bins.push(file.unwrap().path().display().to_string());
- }
- }
-
- match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) {
- Some(cmd) => cmd,
- None => {
- println!("dwvsh: error: command not found...");
- continue;
- }
+ cmd = match b
+ .clone()
+ .iter()
+ .find(|b| b.split("/").last().unwrap() == cmd)
+ {
+ Some(cmd) => cmd.clone(),
+ None => {
+ bins = prefresh(&paths);
+ match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) {
+ Some(cmd) => cmd.clone(),
+ None => {
+ println!("dwvsh: {}: command not found", cmd);
+ continue;
}
}
- },
- );
+ }
+ }
}
// Run the command (and wait for it to finish)
@@ -112,15 +159,23 @@ fn eval(paths: &[&str], prompt: &str) {
Ok(ch) => ch,
Err(err) => {
match err.kind() {
- std::io::ErrorKind::PermissionDenied => println!(
- "dwvsh: error: permission denied trying to fork to {}...",
- &cmd
- ),
- // TODO: Refresh paths if error kind is NotFound
- std::io::ErrorKind::NotFound => println!("dwvsh: error: command not found..."),
- _ => println!("dwvsh: error: unable to fork..."),
+ // Occurs when the user doesn't have read access, or if the +x bit is not set
+ std::io::ErrorKind::PermissionDenied => {
+ println!("dwvsh: permission denied: trying to fork to {}", &cmd)
+ }
+
+ // Occurs if a command was removed from the path
+ // If this is the case, refresh the path
+ std::io::ErrorKind::NotFound => {
+ bins = prefresh(&paths);
+ println!("dwvsh: {}: command not found", cmd);
+ }
+
+ // Otherwise print the OS error
+ _ => println!("dwvsh: fork: {}", err),
}
+ // Restart the loop
continue;
}
};
@@ -128,10 +183,13 @@ fn eval(paths: &[&str], prompt: &str) {
}
}
+/// Shell entry
+///
+/// Shell setup and entry
fn main() {
// Define paths
// TODO: Hardcoded path should only be the fallback
- let paths = [
+ let paths = vec![
"/bin",
"/sbin",
"/usr/bin",
@@ -139,6 +197,7 @@ fn main() {
"/usr/local/bin",
"/usr/local/sbin",
];
+ let paths = paths.into_iter().map(Path::new).collect();
// Set the prompt
let prompt = "|> ";
@@ -148,8 +207,8 @@ fn main() {
print!("\n{}", prompt);
io::stdout().flush().unwrap();
})
- .expect("Unable to set <C-c> handler");
+ .expect("dwvsh: signals: unable to set sigint handler");
// Begin evaluating commands
- eval(&paths, prompt);
+ repl(&paths, prompt);
}