diff --git a/bash_setup.sh b/bash_setup.sh index 90e0a60..318e8c8 100644 --- a/bash_setup.sh +++ b/bash_setup.sh @@ -14,5 +14,5 @@ pop() { } stack() { - echo "$(navigate ${arg_pid} show $*)" + echo "$(navigate ${arg_pid} stack $@)" } diff --git a/src/arguments.rs b/src/arguments.rs index a4287f8..6ff282a 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,5 +1,5 @@ -use clap::{Parser, Args, Subcommand}; -use std::path::PathBuf; +use clap::{Args, Parser, Subcommand}; +use std::{path::PathBuf}; /// implements stack for cd wrapper script @@ -16,6 +16,7 @@ pub struct Arguments { } #[derive(Debug, Clone, Subcommand)] +#[allow(non_camel_case_types)] pub enum Action { /// navigate to path and add current path to the stack push(PushArgs), @@ -24,7 +25,7 @@ pub enum Action { pop(PopArgs), /// show stack - show(ShowArgs), + stack(StackArgs), /// navigate to bookmark and add current path to the stack bookmark(BookmarkArgs), @@ -45,19 +46,36 @@ pub struct PopArgs { /// show stack #[arg(short, long)] pub show_stack: Option, + + /// pop multiple entries and navigate to last retrieved path + pub num_entries: Option, } #[derive(Debug, Clone, Args)] -pub struct ShowArgs { +pub struct StackArgs { /// hide entry numbers - #[arg(short='n', long)] + #[arg(short = 'n', long)] pub hide_numbers: Option, /// show n entries - #[arg(short, long="lines")] + #[arg(short, long = "lines")] pub lines: Option, + + /// stack subcommand + #[command(subcommand)] + pub stack_action: Option, } +#[derive(Debug, Clone, Subcommand)] +#[allow(non_camel_case_types)] +pub enum StackAction { + /// clear stack + clear(ClearArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct ClearArgs {} + #[derive(Debug, Clone, Args)] pub struct BookmarkArgs { /// show stack @@ -65,4 +83,4 @@ pub struct BookmarkArgs { pub show_stack: Option, pub mark: String, -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..e2163c7 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,51 @@ +//! handle the config file and bookmarks stored +//! in said config file + +use std::fs; +use std::fs::File; +use std::io::{Error, ErrorKind, Result}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use sysinfo::{Pid, System}; + +const config_dir_path: &str = "~/.config/navigate/"; + +#[derive(Debug, Clone)] +pub struct Config { + pub bookmarks: Vec, +} + +impl Config { + /// generates and populates a new instance of Config + pub fn new() -> Result { + let mut config = Config { + bookmarks: Vec::::new(), + }; + + // stack.build_stack()?; + if !config.bookmarks[0].is_dir() { + config.bookmarks.remove(0); + }; + + Ok(config) + } + + pub fn build_config(&mut self) -> Result<()> { + let config_dir = match PathBuf::from_str(config_dir_path) { + Ok(result) => result, + Err(_) => return Err(Error::other("failed to create path object for config file")), + }; + + let mut bookmark_file = config_dir.clone(); + bookmark_file.push("bookmarks.conf"); + + if !bookmark_file.is_file() { + _ = File::create(bookmark_file.clone())?; + } + + let mut bookmarks = fs::read_to_string(bookmark_file.clone())?; + // TODO: parse bookmarks + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 8fda1c9..38030a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,48 +1,66 @@ mod arguments; +mod config; mod stack; -use std::io::{Result, Error, ErrorKind}; -use std::path::PathBuf; -use std::env::current_dir; -use clap::{FromArgMatches, Parser}; use arguments::*; +use clap::{Parser}; use stack::Stack; - +use std::env::current_dir; +use std::io::{Error, Result}; fn main() -> Result<()> { let args = Arguments::parse(); - let mut stack = Stack::new(args.pid).expect("failed to build stack"); + let mut stack = match Stack::new(args.pid) { + Ok(stack) => stack, + Err(_) => return Err(Error::other("-- failed to build stack")), + }; - return match args.action { + match args.action { Action::push(push_args) => handle_push(&push_args, &mut stack), Action::pop(pop_args) => handle_pop(&pop_args, &mut stack), - Action::show(show_args) => handle_show(&show_args, &mut stack), + Action::stack(stack_args) => handle_stack(&stack_args, &mut stack), Action::bookmark(bookmark_args) => handle_bookmark(&bookmark_args, &mut stack), - _ => Err(Error::new(ErrorKind::Other, "unknown subcommand")), - }; + } } -pub fn handle_push (args: &PushArgs, stack: &mut Stack) -> Result<()> { // TODO: handle arguments +pub fn handle_push(args: &PushArgs, stack: &mut Stack) -> Result<()> { + // TODO: handle arguments + if !args.path.is_dir() { + return Err(Error::other("-- invalid path argument")); + } let current_path = current_dir()?; stack.push_entry(¤t_path)?; println!("{}", args.path.to_str().unwrap()); - return Ok(()); + Ok(()) } -pub fn handle_pop (args: &PopArgs, stack: &mut Stack) -> Result<()> { // TODO: handle arguments +pub fn handle_pop(args: &PopArgs, stack: &mut Stack) -> Result<()> { + // TODO: handle arguments let path = stack.pop_entry()?; println!("{}", path.to_str().unwrap()); - return Ok(()); + Ok(()) } -pub fn handle_show (args: &ShowArgs, stack: &mut Stack) -> Result<()> { // TODO: handle arguments - let output = stack.get_stack()?; - for item in output { - println!("{}", item.to_str().unwrap()); +pub fn handle_stack(args: &StackArgs, stack: &mut Stack) -> Result<()> { + // TODO: handle arguments + if args.stack_action.is_some() { + match args.stack_action.clone().unwrap() { + StackAction::clear(_) => return stack.clear_stack(), + } } - return Ok(()); + // retrieve stack + let output = stack.get_stack()?; + if output.is_empty() { + return Err(Error::other("-- the stack is empty")); + } + // print stack to standard output + for (n, item) in output.iter().rev().enumerate() { + println!("{} {}", n, item.to_str().unwrap()); + } + Ok(()) } -pub fn handle_bookmark (args: &BookmarkArgs, stack: &mut Stack) -> Result<()> { // TODO: handle arguments - return Ok(()); +pub fn handle_bookmark(args: &BookmarkArgs, stack: &mut Stack) -> Result<()> { + // TODO: handle arguments + Ok(()) } diff --git a/src/stack.rs b/src/stack.rs index a380b27..0f6fadf 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -1,119 +1,130 @@ - use std::fs; use std::fs::File; -use std::path::{PathBuf}; +use std::io::{Error, ErrorKind, Result}; +use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::io::{Result, Error, ErrorKind}; -use sysinfo::{System, Pid}; - +use sysinfo::{Pid, System}; #[derive(Debug, Clone)] -pub struct Stack -{ +pub struct Stack { pid: u32, path: PathBuf, stack: Vec, } -impl Stack -{ - +impl Stack { pub fn new(process_id: u32) -> Result { - let mut stack: Stack = Stack{ - pid : process_id, - path : PathBuf::new(), - stack : Vec::::new(), + let mut stack: Stack = Stack { + pid: process_id, + path: PathBuf::new(), + stack: Vec::::new(), }; - _ = stack.build_stack()?; + // remove first entry if it is empty, because after + // creation of the stack there seems to be an empty + // cell in the vector + stack.build_stack()?; + if !stack.stack[0].is_dir() { + stack.stack.remove(0); + } - return Ok(stack); + Ok(stack) } - /** - * return stack - */ + // return stack pub fn get_stack(&mut self) -> Result<&Vec> { - return Ok(&self.stack); + Ok(&self.stack) } - /** - * push entry to stack - * returns updated stack - */ - pub fn push_entry(&mut self, path: &PathBuf) -> Result<&Vec> { + /// clear stack by deleting the associated stack file + pub fn clear_stack(&mut self) -> Result<()> { + fs::remove_file(self.path.clone()) + } + + /// push entry to stack + /// returns updated stack + pub fn push_entry(&mut self, path: &Path) -> Result<&Vec> { let abs_path = path.canonicalize()?; self.stack.push(abs_path); self.write_stack_file()?; - return Ok(&self.stack); + Ok(&self.stack) } - /** - * pop entry from stack - * return poppe entry - */ + /// pop entry from stack + /// return popped entry pub fn pop_entry(&mut self) -> Result { let entry = self.stack.pop(); self.write_stack_file()?; - return match entry { + match entry { Some(entry) => Ok(entry), - None => Err(Error::new(ErrorKind::Other, "pop failed to retrieve item from stack")), + None => Err(Error::new( + ErrorKind::Other, + "pop failed to retrieve item from stack", + )), } } - /** - * get entry by number - * return nth last entry - */ - pub fn get_entry_by_number(&mut self, entry_number: u32) -> Result<&PathBuf> { - if entry_number > (self.stack.len() as u32 - 1) { - return Err(Error::new(ErrorKind::Other, "requested entry number is out of bounds")); - } + /// get entry by number without removing it from the stack + /// return nth last entry + pub fn get_entry_by_number(&mut self, entry_number: usize) -> Result<&PathBuf> { // index from the end of the vector as new entries are appended at the end of the list - return Ok(&self.stack[self.stack.len() - (entry_number as usize)]); + match self.stack.get( + self.stack + .len() + .checked_sub(entry_number) + .expect("requested entry number is out of bounds"), + ) { + Some(item) => Ok(item), + None => Err(Error::new( + ErrorKind::Other, + "failed to retrieve stack entry by number", + )), + } } - /** - * clean up dead stack files, parse and build stack - */ - fn build_stack(&mut self) -> Result< Vec > { - let stack_dir: PathBuf = PathBuf::from_str("/tmp/navigation/").expect("failed to create path object of '/tmp/navigation'"); + /// clean up dead stack files, parse and build stack + fn build_stack(&mut self) -> Result<()> { + let stack_dir: PathBuf = PathBuf::from_str("/tmp/navigation/") + .expect("failed to create path object of '/tmp/navigation'"); let mut sys = System::new_all(); sys.refresh_all(); let procs = sys.processes(); - + if stack_dir.is_dir() { // clean up stack files of expired processes let members = fs::read_dir(stack_dir.clone())?; for entry in members { let entry = entry?; - let process_id = Pid::from_str(entry.file_name().to_str().expect("failed to convert file name to str")); + let process_id = Pid::from_str( + entry + .file_name() + .to_str() + .expect("failed to convert file name to str"), + ); if !procs.contains_key(&process_id.expect("failed to convert filename to pid")) { fs::remove_file(entry.path()).expect("failed to remove orphaned file"); } } } else { // create temporary directory to store the stack - let _ = fs::create_dir(stack_dir.clone())?; + fs::create_dir(stack_dir.clone())?; } - + self.path = stack_dir.clone(); self.path.push(PathBuf::from(&self.pid.to_string())); if self.path.is_file() { // read and parse stack file - _ = self.read_stack_file(&self.path.clone())?; + self.read_stack_file(&self.path.clone())?; } else { // create stack file and store current path - _ = File::create(self.path.clone())?; + File::create(self.path.clone())?; } - - return Ok(vec![stack_dir]); + + Ok(()) } - /** - * parse stack file - */ + /// parse stack file fn read_stack_file(&mut self, stack_file_path: &PathBuf) -> Result<()> { let stack = fs::read_to_string(stack_file_path)?; let stack = stack.split("\n"); @@ -121,21 +132,21 @@ impl Stack self.stack.push(PathBuf::from(entry)); } - return Ok(()); + Ok(()) } - /** - * write stack current stack to file to save it for next execution - */ + /// write stack current stack to file to save it for next execution fn write_stack_file(&mut self) -> Result<()> { let mut output = Vec::<&str>::new(); for entry in &self.stack { - output.push(entry.to_str().expect("failed to convert stack entry to string")); + output.push( + entry + .to_str() + .expect("failed to convert stack entry to string"), + ); } fs::write(self.path.clone(), output.join("\n"))?; - return Ok(()) + Ok(()) } - } // end `impl database` - diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..8e4006b --- /dev/null +++ b/todo.md @@ -0,0 +1,8 @@ +# todos for 'navigation' + +- [ ] replace `expect` statements in 'stack.rs' with actual error handling +- [ ] pop several entries at a time +- [ ] dedup stack option +- [x] drop stack +- [ ] config file +- [ ] bookmarks