Skip to content

Commit

Permalink
feat(builtin): fix error handling in edit-path
Browse files Browse the repository at this point in the history
  • Loading branch information
lthoerner committed Sep 14, 2023
1 parent 233590d commit 5224b11
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 21 deletions.
21 changes: 21 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,24 @@ pub enum StateError {
/// next directory when the stack is empty, this error is returned.
NoNextDirectory,

/// OVERVIEW
/// This error occurs when the shell tries to update the PATH variable using an invalid index.
///
/// CAUSE
/// - A non-existent index was provided to the 'edit-path' builtin using the 'insert' or
/// 'delete' subcommand.
///
/// SOLUTION
/// - Check the PATH variable using the 'environment' builtin and ensure that the index provided
/// is valid.
///
/// TECHNICAL DETAILS
/// The 'edit-path' builtin allows the user to modify the PATH variable. The PATH variable is
/// represented using a vector (list) of paths, which is indexed when the user provides an index
/// to insert or delete a path. If the user provides an index which does not exist, this error
/// is returned.
InvalidPathIndex(usize),

/// OVERVIEW
/// This error occurs when the line editor is unable to interact with the terminal.
///
Expand Down Expand Up @@ -616,6 +634,9 @@ impl Display for StateError {
}
NoPreviousDirectory => write!(f, "No previous directory"),
NoNextDirectory => write!(f, "No next directory"),
InvalidPathIndex(index) => {
write!(f, "Path index {} is invalid", index)
}
UnsupportedTerminal => write!(f, "Terminal is not supported"),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/eval/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl Dispatcher {
command.run(shell, command_args)
} else {
// If the command is not in the Dispatcher, try to run it as an executable from the PATH
let path = Path::try_resolve_executable(command_name, &shell.environment.PATH);
let path = Path::try_resolve_executable(command_name, shell.environment.PATH());
if let Ok(path) = path {
// Check if the file is executable (has the executable bit set)
if let Ok(metadata) = fs_err::metadata(path.path()) {
Expand Down
25 changes: 8 additions & 17 deletions src/exec/builtins/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn exit(_shell: &mut ShellState, args: Vec<&str>) -> Result<()> {

pub fn working_directory(shell: &mut ShellState, args: Vec<&str>) -> Result<()> {
clap_handle!(WorkingDirectoryArgs::try_parse_from(args));
println!("{}", shell.environment.CWD);
println!("{}", shell.CWD());
Ok(())
}

Expand All @@ -60,9 +60,7 @@ pub fn change_directory(shell: &mut ShellState, args: Vec<&str>) -> Result<()> {
pub fn list_directory(shell: &mut ShellState, args: Vec<&str>) -> Result<()> {
let arguments = clap_handle!(ListDirectoryArgs::try_parse_from(&args));
let show_hidden = arguments.show_hidden;
let path_to_read = arguments
.path
.unwrap_or(shell.environment.CWD.path().to_path_buf());
let path_to_read = arguments.path.unwrap_or(shell.CWD().path().to_path_buf());

let read_dir_result =
fs_err::read_dir(&path_to_read).replace_err(|| file_err!(UnknownPath: path_to_read))?;
Expand Down Expand Up @@ -218,9 +216,9 @@ pub fn environment_variable(shell: &mut ShellState, args: Vec<&str>) -> Result<(
match arguments.variable {
USER => println!("{}", shell.environment.USER),
HOME => println!("{}", shell.environment.HOME.display()),
CWD => println!("{}", shell.environment.CWD),
CWD => println!("{}", shell.CWD()),
PATH => {
for (i, path) in shell.environment.PATH.iter().enumerate() {
for (i, path) in shell.environment.PATH().iter().enumerate() {
println!("[{i}]: {path}");
}
}
Expand All @@ -230,26 +228,19 @@ pub fn environment_variable(shell: &mut ShellState, args: Vec<&str>) -> Result<(
}

pub fn edit_path(shell: &mut ShellState, args: Vec<&str>) -> Result<()> {
// TODO: Needs to update the real PATH
let arguments = clap_handle!(EditPathArgs::try_parse_from(args));
use EditPathSubcommand::*;
match arguments.subcommand {
Append(AppendPathCommand { path }) => shell
.environment
.PATH
.push_front(Path::try_from_path(&path, Some(&shell.environment.HOME))?),
.PATH_append(Path::try_from_path(&path, Some(&shell.environment.HOME))?),
Prepend(PrependPathCommand { path }) => shell
.environment
.PATH
.push_front(Path::try_from_path(&path, Some(&shell.environment.HOME))?),
Insert(InsertPathCommand { index, path }) => shell.environment.PATH.insert(
.PATH_prepend(Path::try_from_path(&path, Some(&shell.environment.HOME))?),
Insert(InsertPathCommand { index, path }) => shell.environment.PATH_insert(
index,
Path::try_from_path(&path, Some(&shell.environment.HOME))?,
),
Delete(DeletePathCommand { index }) => {
shell.environment.PATH.remove(index);
}
Delete(DeletePathCommand { index }) => shell.environment.PATH_delete(index),
}

Ok(())
}
45 changes: 43 additions & 2 deletions src/state/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ bitflags! {
pub struct Environment {
pub USER: String,
pub HOME: PathBuf,
pub CWD: Path,
CWD: Path,
// * PATH is not to be confused with the WORKING_DIRECTORY. PATH is a list of directories which
// * the shell will search for executables in. WORKING_DIRECTORY is the current directory the user is in.
pub PATH: VecDeque<Path>,
PATH: VecDeque<Path>,
// ? Should these be `ShellState` fields instead?
backward_directories: VecDeque<Path>,
forward_directories: VecDeque<Path>,
#[allow(dead_code)]
Expand Down Expand Up @@ -157,6 +158,46 @@ impl Environment {
Err(state_err!(NoNextDirectory))
}
}

/// Appends a path to the PATH variable
pub fn PATH_append(&mut self, path: Path) -> Result<()> {
self.PATH.push_back(path);
self.update_process_env_vars(EnvVariables::PATH)
}

/// Prepends a path to the PATH variable
pub fn PATH_prepend(&mut self, path: Path) -> Result<()> {
self.PATH.push_front(path);
self.update_process_env_vars(EnvVariables::PATH)
}

/// Inserts a path at the specified index in the PATH variable
pub fn PATH_insert(&mut self, index: usize, path: Path) -> Result<()> {
if index > self.PATH.len() {
return Err(state_err!(InvalidPathIndex: index));
}

self.PATH.insert(index, path);
self.update_process_env_vars(EnvVariables::PATH)
}

/// Deletes the path at the specified index in the PATH variable
pub fn PATH_delete(&mut self, index: usize) -> Result<()> {
self.PATH
.remove(index)
.replace_err(|| state_err!(InvalidPathIndex: index))?;
self.update_process_env_vars(EnvVariables::PATH)
}

/// Getter for the current working directory
pub fn CWD(&self) -> &Path {
&self.CWD
}

/// Getter for the PATH
pub fn PATH(&self) -> &VecDeque<Path> {
&self.PATH
}
}

/// Gets the environment variables from the parent process during shell initialization
Expand Down
9 changes: 8 additions & 1 deletion src/state/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crossterm::style::Stylize;

use super::config::Configuration;
use super::environment::Environment;
use super::Path;
use crate::errors::Result;

/// Represents the shell state and provides methods for interacting with it
Expand Down Expand Up @@ -32,7 +33,7 @@ impl ShellState {
let user = self.environment.USER.clone();
let home = &self.environment.HOME;
let truncation = self.config.truncation;
let cwd = self.environment.CWD.collapse(home, truncation);
let cwd = self.CWD().collapse(home, truncation);
let prompt_delimiter = match self.config.multiline_prompt {
true => "\n",
false => " ",
Expand All @@ -53,4 +54,10 @@ impl ShellState {
prompt_tick
)
}

/// Convenience getter for the current working directory
#[allow(non_snake_case)]
pub fn CWD(&self) -> &Path {
self.environment.CWD()
}
}

0 comments on commit 5224b11

Please sign in to comment.