diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index d1d5d85b90b56ea21948c53394434028a7df28c6..57c644debb2826c243cb3ae9c7862b0a7c789a37 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -28,8 +28,10 @@ use self::{ }; use std::{ + borrow::Cow, error::Error, io::{self, BufRead, Write}, + path::PathBuf, }; use hashbrown::HashMap; @@ -39,6 +41,7 @@ use crate::{ shell::{self, status::*, ProcessState, Shell}, sys, types, }; +use itertools::Itertools; use small; const HELP_DESC: &str = "Display helpful information about a given command or list commands if \ @@ -61,6 +64,17 @@ macro_rules! map { }}; } +// parses -N or +N patterns +// required for popd, pushd, dirs +fn parse_numeric_arg(arg: &str) -> Option<(bool, usize)> { + match arg.chars().nth(0) { + Some('+') => Some(true), + Some('-') => Some(false), + _ => None, + } + .and_then(|b| arg[1..].parse::<usize>().ok().map(|num| (b, num))) +} + /// A container for builtins and their respective help text /// /// Note: To reduce allocations, function are provided as pointer rather than boxed closures @@ -308,41 +322,214 @@ fn builtin_is(args: &[small::String], shell: &mut Shell) -> i32 { } fn builtin_dirs(args: &[small::String], shell: &mut Shell) -> i32 { + // converts pbuf to an absolute path if possible + fn try_abs_path(pbuf: &PathBuf) -> Cow<str> { + Cow::Owned( + pbuf.canonicalize().unwrap_or_else(|_| pbuf.clone()).to_string_lossy().to_string(), + ) + } + if check_help(args, MAN_DIRS) { return SUCCESS; } - shell.dir_stack(args.iter().skip(1)) + let mut clear = false; // -c + let mut abs_pathnames = false; // -l + let mut multiline = false; // -p | -v + let mut index = false; // -v + + let mut num_arg = None; + + for arg in args.iter().skip(1) { + match arg.as_ref() { + "-c" => clear = true, + "-l" => abs_pathnames = true, + "-p" => multiline = true, + "-v" => { + index = true; + multiline = true; + } + _ => num_arg = Some(arg), + } + } + + if clear { + shell.clear_dir_stack(); + } + + let mapper: fn((usize, &PathBuf)) -> Cow<str> = match (abs_pathnames, index) { + // ABS, INDEX + (true, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, try_abs_path(x))), + (true, false) => |(_, x)| try_abs_path(x), + (false, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, x.to_string_lossy())), + (false, false) => |(_, x)| x.to_string_lossy(), + }; + + let mut iter = shell.dir_stack().enumerate().map(mapper); + + if let Some(arg) = num_arg { + let num = match parse_numeric_arg(arg.as_ref()) { + Some((true, num)) => num, + Some((false, num)) if shell.dir_stack().count() > num => { + shell.dir_stack().count() - num - 1 + } + _ => return FAILURE, /* Err(Cow::Owned(format!("ion: dirs: {}: invalid + * argument\n", arg))) */ + }; + match iter.nth(num) { + Some(x) => { + println!("{}", x); + SUCCESS + } + None => FAILURE, + } + } else { + let folder: fn(String, Cow<str>) -> String = + if multiline { |x, y| x + "\n" + &y } else { |x, y| x + " " + &y }; + + if let Some(x) = iter.next() { + println!("{}", iter.fold(x.to_string(), folder)); + } + SUCCESS + } } fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> i32 { if check_help(args, MAN_PUSHD) { return SUCCESS; } - match shell.pushd(args.iter().skip(1)) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.as_bytes()); - FAILURE + + enum Action { + Switch, // <no arguments> + RotLeft(usize), // +[num] + RotRight(usize), // -[num] + Push(PathBuf), // [dir] + } + + let mut keep_front = false; // whether the -n option is present + let mut action = Action::Switch; + + for arg in args.iter().skip(1) { + let arg = arg.as_ref(); + if arg == "-n" { + keep_front = true; + } else if let Action::Switch = action { + // if action is not yet defined + action = match parse_numeric_arg(arg) { + Some((true, num)) => Action::RotLeft(num), + Some((false, num)) => Action::RotRight(num), + None => Action::Push(PathBuf::from(arg)), // no numeric arg => `dir`-parameter + }; + } else { + eprintln!("ion: pushd: too many arguments"); + return FAILURE; } } + + match action { + Action::Switch => { + if !keep_front { + if let Err(why) = shell.swap(1) { + eprintln!("ion: pushd: {}", why); + return FAILURE; + } + } + } + Action::RotLeft(num) => { + if !keep_front { + if let Err(why) = shell.rotate_left(num) { + eprintln!("ion: pushd: {}", why); + return FAILURE; + } + } + } + Action::RotRight(num) => { + if !keep_front { + if let Err(why) = shell.rotate_right(num) { + eprintln!("ion: pushd: {}", why); + return FAILURE; + } + } + } + Action::Push(dir) => { + if let Err(why) = shell.pushd(dir, keep_front) { + eprintln!("ion: pushd: {}", why); + return FAILURE; + } + } + }; + + println!( + "{}", + shell.dir_stack().map(|dir| dir.to_str().unwrap_or("ion: no directory found")).join(" ") + ); + SUCCESS } fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { if check_help(args, MAN_POPD) { return SUCCESS; } - match shell.popd(args.iter().skip(1)) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.as_bytes()); - FAILURE + + let len = shell.dir_stack().len(); + if len <= 1 { + eprintln!("ion: popd: directory stack empty"); + return FAILURE; + } + + let mut keep_front = false; // whether the -n option is present + let mut index: usize = 0; + + for arg in args.iter().skip(1) { + let arg = arg.as_ref(); + if arg == "-n" { + keep_front = true; + } else { + let (count_from_front, num) = match parse_numeric_arg(arg) { + Some(n) => n, + None => { + eprintln!("ion: popd: {}: invalid argument", arg); + return FAILURE; + } + }; + + index = if count_from_front { + // <=> input number is positive + num + } else if let Some(n) = (len - 1).checked_sub(num) { + n + } else { + eprintln!("ion: popd: negative directory stack index out of range"); + return FAILURE; + }; } } + + // apply -n + if index == 0 && keep_front { + index = 1; + } else if index == 0 { + // change to new directory, return if not possible + if let Err(why) = shell.set_current_dir_by_index(1) { + eprintln!("ion: popd: {}", why); + return FAILURE; + } + } + + // pop element + if shell.popd(index).is_some() { + println!( + "{}", + shell + .dir_stack() + .map(|dir| dir.to_str().unwrap_or("ion: no directory found")) + .join(" ") + ); + SUCCESS + } else { + eprintln!("ion: popd: {}: directory stack index out of range", index); + FAILURE + } } fn builtin_alias(args: &[small::String], shell: &mut Shell) -> i32 { diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 270e8873be68e34277d8dc3501066106c63fb798..5ff9351307e2db060740a28e13b98e88bfdcc599 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -16,4 +16,7 @@ mod memory; mod shell; pub(crate) use self::memory::IonPool; -pub use crate::shell::*; +pub use crate::{ + builtins::{BuiltinFunction, BuiltinMap}, + shell::*, +}; diff --git a/src/lib/shell/directory_stack.rs b/src/lib/shell/directory_stack.rs index 7c5d09aabab4df13b8595757d8e94dcfb4819006..0b677ff9849fd55b69736e6c4137d78e07f9ca4a 100644 --- a/src/lib/shell/directory_stack.rs +++ b/src/lib/shell/directory_stack.rs @@ -1,7 +1,4 @@ -use super::{ - status::{FAILURE, SUCCESS}, - variables::{Value, Variables}, -}; +use super::variables::{Value, Variables}; use crate::sys::env as sys_env; use std::{ borrow::Cow, @@ -56,103 +53,39 @@ impl DirectoryStack { } // pushd -<num> - fn rotate_right(&mut self, num: usize) { + pub fn rotate_right(&mut self, num: usize) -> Result<(), Cow<'static, str>> { let len = self.dirs.len(); - self.rotate_left(len - (num % len)); + self.rotate_left(len - (num % len)) } // pushd +<num> - fn rotate_left(&mut self, num: usize) { - let cloned = self.dirs.clone(); - for (dest, src) in self.dirs.iter_mut().zip(cloned.iter().cycle().skip(num)) { - *dest = src.clone(); + pub fn rotate_left(&mut self, num: usize) -> Result<(), Cow<'static, str>> { + for _ in 0..num { + if let Some(popped_front) = self.dirs.pop_front() { + self.dirs.push_back(popped_front); + } } + self.set_current_dir_by_index(0) } // sets current_dir to the element referred by index - fn set_current_dir_by_index( - &self, - index: usize, - caller: &str, - ) -> Result<(), Cow<'static, str>> { - let dir = self.dirs.get(index).ok_or_else(|| { - Cow::Owned(format!("ion: {}: {}: directory stack out of range", caller, index)) - })?; + pub fn set_current_dir_by_index(&self, index: usize) -> Result<(), Cow<'static, str>> { + let dir = self + .dirs + .get(index) + .ok_or_else(|| Cow::Owned(format!("{}: directory stack out of range", index)))?; set_current_dir_ion(dir) } - fn print_dirs(&self) { - let dir = self.dirs.iter().fold(String::new(), |acc, dir| { - acc + " " + dir.to_str().unwrap_or("ion: no directory found") - }); - println!("{}", dir.trim_start()); - } - pub fn dir_from_bottom(&self, num: usize) -> Option<&PathBuf> { - self.dirs.iter().rev().nth(num) + self.dirs.get(self.dirs.len() - num) } pub fn dir_from_top(&self, num: usize) -> Option<&PathBuf> { self.dirs.get(num) } - pub fn dirs<I: IntoIterator<Item = T>, T: AsRef<str>>(&mut self, args: I) -> i32 { - let mut clear = false; // -c - let mut abs_pathnames = false; // -l - let mut multiline = false; // -p | -v - let mut index = false; // -v - - let mut num_arg = None; - - for arg in args { - match arg.as_ref() { - "-c" => clear = true, - "-l" => abs_pathnames = true, - "-p" => multiline = true, - "-v" => { - index = true; - multiline = true; - } - _ => num_arg = Some(arg), - } - } - - if clear { - self.dirs.truncate(1); - } - - let mapper: fn((usize, &PathBuf)) -> Cow<str> = match (abs_pathnames, index) { - // ABS, INDEX - (true, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, try_abs_path(x))), - (true, false) => |(_, x)| try_abs_path(x), - (false, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, x.to_string_lossy())), - (false, false) => |(_, x)| x.to_string_lossy(), - }; - - let mut iter = self.dirs.iter().enumerate().map(mapper); - - if let Some(arg) = num_arg { - let num = match parse_numeric_arg(arg.as_ref()) { - Some((true, num)) => num, - Some((false, num)) if self.dirs.len() > num => self.dirs.len() - num - 1, - _ => return FAILURE, /* Err(Cow::Owned(format!("ion: dirs: {}: invalid - * argument\n", arg))) */ - }; - match iter.nth(num) { - Some(x) => { - println!("{}", x); - SUCCESS - } - None => FAILURE, - } - } else { - let folder: fn(String, Cow<str>) -> String = - if multiline { |x, y| x + "\n" + &y } else { |x, y| x + " " + &y }; - - if let Some(x) = iter.next() { - println!("{}", iter.fold(x.to_string(), folder)); - } - SUCCESS - } + pub fn dirs(&self) -> impl DoubleEndedIterator<Item = &PathBuf> + ExactSizeIterator { + self.dirs.iter() } fn insert_dir(&mut self, index: usize, path: PathBuf, variables: &Variables) { @@ -162,27 +95,24 @@ impl DirectoryStack { fn push_dir(&mut self, path: PathBuf, variables: &Variables) { self.dirs.push_front(path); - self.dirs.truncate(DirectoryStack::get_size(variables)); } - pub fn change_and_push_dir( + fn change_and_push_dir( &mut self, dir: &str, variables: &Variables, ) -> Result<(), Cow<'static, str>> { let new_dir = self.normalize_path(dir); - match set_current_dir_ion(&new_dir) { - Ok(()) => { - self.push_dir(new_dir, variables); - Ok(()) - } - Err(err) => Err(Cow::Owned(format!( + set_current_dir_ion(&new_dir).map_err(|err| { + Cow::Owned(format!( "ion: failed to set current dir to {}: {}", new_dir.to_string_lossy(), err - ))), - } + )) + })?; + self.push_dir(new_dir, variables); + Ok(()) } fn get_previous_dir(&self) -> Option<String> { @@ -245,127 +175,31 @@ impl DirectoryStack { } } - pub fn pushd<T, I>( - &mut self, - args: I, - variables: &mut Variables, - ) -> Result<(), Cow<'static, str>> - where - T: AsRef<str>, - I: IntoIterator<Item = T>, - { - enum Action { - Switch, // <no arguments> - RotLeft(usize), // +[num] - RotRight(usize), // -[num] - Push(PathBuf), // [dir] - } - - let mut keep_front = false; // whether the -n option is present - let mut action = Action::Switch; - - for arg in args { - let arg = arg.as_ref(); - if arg == "-n" { - keep_front = true; - } else if let Action::Switch = action { - // if action is not yet defined - action = match parse_numeric_arg(arg) { - Some((true, num)) => Action::RotLeft(num), - Some((false, num)) => Action::RotRight(num), - None => Action::Push(PathBuf::from(arg)), // no numeric arg => `dir`-parameter - }; - } else { - return Err(Cow::Borrowed("ion: pushd: too many arguments")); - } + pub fn swap(&mut self, index: usize) -> Result<(), Cow<'static, str>> { + if self.dirs.len() <= index { + return Err(Cow::Borrowed("no other directory")); } - - let len = self.dirs.len(); - match action { - Action::Switch => { - if len < 2 { - return Err(Cow::Borrowed("ion: pushd: no other directory")); - } - if !keep_front { - self.set_current_dir_by_index(1, "pushd")?; - self.dirs.swap(0, 1); - } - } - Action::RotLeft(num) => { - if !keep_front { - self.set_current_dir_by_index(num, "pushd")?; - self.rotate_left(num); - } - } - Action::RotRight(num) => { - if !keep_front { - self.set_current_dir_by_index(len - (num % len), "pushd")?; - self.rotate_right(num); - } - } - Action::Push(dir) => { - let index = if keep_front { 1 } else { 0 }; - let new_dir = self.normalize_path(dir.to_str().unwrap()); - self.insert_dir(index, new_dir, variables); - self.set_current_dir_by_index(index, "pushd")?; - } - }; - - self.print_dirs(); - Ok(()) + self.dirs.swap(0, index); + self.set_current_dir_by_index(0) } - /// Attempts to set the current directory to the directory stack's previous directory, - /// and then removes the front directory from the stack. - pub fn popd<T: AsRef<str>, I: IntoIterator<Item = T>>( + pub fn pushd( &mut self, - args: I, + path: PathBuf, + keep_front: bool, + variables: &mut Variables, ) -> Result<(), Cow<'static, str>> { - let len = self.dirs.len(); - if len <= 1 { - return Err(Cow::Borrowed("ion: popd: directory stack empty")); - } - - let mut keep_front = false; // whether the -n option is present - let mut index: usize = 0; - - for arg in args { - let arg = arg.as_ref(); - if arg == "-n" { - keep_front = true; - } else { - let (count_from_front, num) = parse_numeric_arg(arg) - .ok_or_else(|| Cow::Owned(format!("ion: popd: {}: invalid argument", arg)))?; - - index = if count_from_front { - // <=> input number is positive - num - } else { - (len - 1).checked_sub(num).ok_or_else(|| { - Cow::Owned( - "ion: popd: negative directory stack index out of range".to_owned(), - ) - })? - }; - } - } + let index = if keep_front { 1 } else { 0 }; + let new_dir = self.normalize_path(path.to_str().unwrap()); + self.insert_dir(index, new_dir, variables); + self.set_current_dir_by_index(index) + } - // apply -n - if index == 0 && keep_front { - index = 1; - } else if index == 0 { - // change to new directory, return if not possible - self.set_current_dir_by_index(1, "popd")?; - } + /// Attempts to set the current directory to the directory stack's previous directory, + /// and then removes the front directory from the stack. + pub fn popd(&mut self, index: usize) -> Option<PathBuf> { self.dirs.remove(index) } - // pop element - if self.dirs.remove(index).is_some() { - self.print_dirs(); - Ok(()) - } else { - Err(Cow::Owned(format!("ion: popd: {}: directory stack index out of range", index))) - } - } + pub fn clear(&mut self) { self.dirs.truncate(1) } /// This function will take a map of variables as input and attempt to parse the value of /// the @@ -393,19 +227,3 @@ impl DirectoryStack { DirectoryStack { dirs } } } - -// parses -N or +N patterns -// required for popd, pushd, dirs -fn parse_numeric_arg(arg: &str) -> Option<(bool, usize)> { - match arg.chars().nth(0) { - Some('+') => Some(true), - Some('-') => Some(false), - _ => None, - } - .and_then(|b| arg[1..].parse::<usize>().ok().map(|num| (b, num))) -} - -// converts pbuf to an absolute path if possible -fn try_abs_path(pbuf: &PathBuf) -> Cow<str> { - Cow::Owned(pbuf.canonicalize().unwrap_or_else(|_| pbuf.clone()).to_string_lossy().to_string()) -} diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index 21a5599ea3e1d909ce5e510372ad72b9cfedb9b1..373de69af8b212c4dcd949c22beb50e19a49e271 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -12,18 +12,13 @@ pub(crate) mod signals; pub mod status; pub mod variables; -pub use self::{ - fork::{Capture, IonResult}, - job::Job, - pipe_exec::job_control::ProcessState, - variables::Value, -}; +pub use self::{fork::Capture, job::Job, pipe_exec::job_control::ProcessState, variables::Value}; use self::{ directory_stack::DirectoryStack, flow_control::{Block, Function, FunctionError, Statement}, foreground::ForegroundSignals, - fork::Fork, + fork::{Fork, IonResult}, pipe_exec::{foreground, job_control::BackgroundProcess}, status::*, variables::{GetVariable, Variables}, @@ -40,7 +35,7 @@ use std::{ fmt, fs::{self, OpenOptions}, io::{self, Write}, - path::Path, + path::{Path, PathBuf}, process, sync::{atomic::Ordering, Arc, Mutex}, time::SystemTime, @@ -205,28 +200,38 @@ impl<'a> Shell<'a> { } } + pub fn rotate_right(&mut self, num: usize) -> Result<(), Cow<'static, str>> { + self.directory_stack.rotate_right(num) + } + + pub fn rotate_left(&mut self, num: usize) -> Result<(), Cow<'static, str>> { + self.directory_stack.rotate_left(num) + } + + pub fn swap(&mut self, index: usize) -> Result<(), Cow<'static, str>> { + self.directory_stack.swap(index) + } + + pub fn set_current_dir_by_index(&self, index: usize) -> Result<(), Cow<'static, str>> { + self.directory_stack.set_current_dir_by_index(index) + } + pub fn cd<T: AsRef<str>>(&mut self, dir: Option<T>) -> Result<(), Cow<'static, str>> { self.directory_stack.cd(dir, &mut self.variables) } - pub fn pushd<T: AsRef<str>, I: IntoIterator<Item = T>>( - &mut self, - iter: I, - ) -> Result<(), Cow<'static, str>> { - self.directory_stack.pushd(iter, &mut self.variables) + pub fn pushd(&mut self, path: PathBuf, keep_front: bool) -> Result<(), Cow<'static, str>> { + self.directory_stack.pushd(path, keep_front, &mut self.variables) } - pub fn popd<T: AsRef<str>, I: IntoIterator<Item = T>>( - &mut self, - iter: I, - ) -> Result<(), Cow<'static, str>> { - self.directory_stack.popd(iter) - } + pub fn popd(&mut self, index: usize) -> Option<PathBuf> { self.directory_stack.popd(index) } - pub fn dir_stack<T: AsRef<str>, I: IntoIterator<Item = T>>(&mut self, iter: I) -> i32 { - self.directory_stack.dirs(iter) + pub fn dir_stack(&self) -> impl DoubleEndedIterator<Item = &PathBuf> + ExactSizeIterator { + self.directory_stack.dirs() } + pub fn clear_dir_stack(&mut self) { self.directory_stack.clear() } + /// Resets the flow control fields to their default values. pub fn reset_flow(&mut self) { self.flow_control.clear(); }