diff --git a/src/bookmarks.rs b/src/bookmarks.rs index c1b2e96..a6da251 100644 --- a/src/bookmarks.rs +++ b/src/bookmarks.rs @@ -1,5 +1,4 @@ -//! handle the config file and bookmarks stored -//! in said config file +//! implements a struct and methods for bookmarks use std::collections::BTreeMap; use std::fs; @@ -14,50 +13,32 @@ use config_parser::{make_padding_string, apply_format}; #[derive(Debug, Clone)] pub struct Bookmarks { - conf_dir: PathBuf, bookmarks: BTreeMap, } impl Bookmarks { - const BOOKMARK_FILE_NAME: &str = "bookmarks.conf"; + const BOOKMARK_FILE_PATH: &str = "navigate/bookmarks.conf"; /// generates and populates a new instance of Config pub fn new() -> Result { let mut bookmarks = Bookmarks { - conf_dir: PathBuf::new(), bookmarks: BTreeMap::::new(), }; // get home directory path - bookmarks.conf_dir = match config_dir() { + let mut bookmark_file = match config_dir() { Some(value) => value, None => return Err(Error::other("-- failed to find configuration directory")), }; // expand home directory path to get configuration directory path - bookmarks.conf_dir.push("navigate/"); - bookmarks.build_bookmarks()?; - - Ok(bookmarks) - } - - /// reads and parses the bookmarks file - fn build_bookmarks(&mut self) -> Result<()> { - // check if configuration directory exists, if not create it - if !self.conf_dir.is_dir() { - fs::create_dir(self.conf_dir.clone())?; - } - - let mut bookmark_file = self.conf_dir.clone(); - - bookmark_file.push(Self::BOOKMARK_FILE_NAME); + bookmark_file.push(Self::BOOKMARK_FILE_PATH); // check if bookmarks file exists, if not create it if !bookmark_file.is_file() { _ = File::create(bookmark_file.clone())?; } - let bookmarks = fs::read_to_string(bookmark_file)?; - let bookmarks = bookmarks.lines(); - for entry in bookmarks { + let bookmarks_str = fs::read_to_string(bookmark_file)?; + for entry in bookmarks_str.lines() { let tokens: Vec<&str> = entry.split("=").collect(); if tokens.len() != 2 { continue; @@ -70,10 +51,9 @@ impl Bookmarks { if !path.is_dir() { continue; } - self.bookmarks.insert(key, path); + bookmarks.bookmarks.insert(key, path); } - - Ok(()) + Ok(bookmarks) } /// returns path of bookmark if it exists @@ -114,7 +94,7 @@ impl Bookmarks { } /// formats and prints bookmarks to string - pub fn to_formatted_string(&self, config: &Settings) -> Result { + pub fn to_formatted_string(&self, config: &Config) -> Result { let mut buffer = String::new(); if self.bookmarks.is_empty() { @@ -149,8 +129,13 @@ impl Bookmarks { file_content.push_str(&format!("{}={}\n", mark, path.to_str().unwrap())); } - let mut path = self.conf_dir.clone(); - path.push(Self::BOOKMARK_FILE_NAME); + let path = match config_dir() { + Some(mut value) => { + value.push(Self::BOOKMARK_FILE_PATH); + value + } + None => return Err(Error::other("-- failed to find configuration directory")), + }; fs::write(path, file_content)?; Ok(()) diff --git a/src/config.rs b/src/config.rs index bb5bd06..e0b516f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,17 +6,10 @@ use dirs::config_dir; use std::fs; use std::io::{Error, Result}; -use std::path::PathBuf; use config_parser::*; -#[derive(Debug, Clone)] -pub struct Config { - conf_file: PathBuf, - pub settings: Settings, -} - #[derive(Debug, Clone, Default, ConfigParser)] -pub struct Settings { +pub struct Config { #[nested_config] pub general: GeneralSettings, #[nested_config] @@ -41,6 +34,10 @@ pub struct FormatSettings { #[derive(Debug, Clone, Default, ConfigParser)] pub struct StyleSettings { + #[style_config] + pub warning_style: String, + #[style_config] + pub error_style: String, #[style_config] pub stack_number_style: String, #[style_config] @@ -61,30 +58,29 @@ impl Config { /// generates and populates a new instance of Config pub fn new(styles_as_ansi_sequences: bool) -> Result { let mut config = Config { - conf_file: PathBuf::new(), - settings: Settings { - general: GeneralSettings { - show_stack_on_push: false, - show_stack_on_pop: false, - show_books_on_bookmark: false, - }, - format: FormatSettings { - bookmarks_separator: String::new(), - stack_separator: String::new(), - align_separators: false, - }, - styles: StyleSettings { - stack_number_style: String::new(), - stack_separator_style: String::new(), - stack_path_style: String::new(), - bookmarks_name_style: String::new(), - bookmarks_seperator_style: String::new(), - bookmarks_path_style: String::new(), - }, + general: GeneralSettings { + show_stack_on_push: false, + show_stack_on_pop: false, + show_books_on_bookmark: false, + }, + format: FormatSettings { + bookmarks_separator: String::new(), + stack_separator: String::new(), + align_separators: false, + }, + styles: StyleSettings { + warning_style: String::new(), + error_style: String::new(), + stack_number_style: String::new(), + stack_separator_style: String::new(), + stack_path_style: String::new(), + bookmarks_name_style: String::new(), + bookmarks_seperator_style: String::new(), + bookmarks_path_style: String::new(), }, }; // get configuration directory - config.conf_file = match config_dir() { + let mut conf_file = match config_dir() { Some(value) => value, None => { return Err(Error::other( @@ -93,27 +89,21 @@ impl Config { } }; // expand path to configuration file - config - .conf_file - .push(format!("navigate/{}", Self::CONFIG_FILE_NAME)); + conf_file.push(format!("navigate/{}", Self::CONFIG_FILE_NAME)); // parse configuration file and populate config struct - if config.conf_file.is_file() { - let config_str = match fs::read_to_string(&config.conf_file) { + if conf_file.is_file() { + let config_str = match fs::read_to_string(&conf_file) { Ok(value) => value, Err(error) => return Err(error), }; - _ = config.settings.parse_from_string(&config_str); + _ = config.parse_from_string(&config_str); } else { // TODO: write default configuration } - // TODO: ALSDKJFJFJASDkk - //config.set_default_settings()?; - //config.parse_color_settings()?; - if styles_as_ansi_sequences { - config.settings.to_ansi_sequences()?; + config.to_ansi_sequences()?; } Ok(config) @@ -121,47 +111,7 @@ impl Config { /// formats and prints config to string pub fn to_formatted_string(&self) -> Result { - Ok(format!("{:#?}", self.settings)) + Ok(format!("{:#?}", self)) } - /// reads and parses the configuration file - fn build_settings(&mut self) -> Result<()> { - Ok(()) - } - - /// sets defaults for settings not found in the configuration file - fn set_default_settings(&mut self) -> Result<()> { - let default_separator = " - ".to_owned(); - let default_number_color = "default".to_owned(); - let default_separator_color = "cyan".to_owned(); - let default_path_color = "default".to_owned(); - - if self.settings.format.stack_separator.is_empty() { - self.settings.format.stack_separator = default_separator.clone(); - } - if self.settings.format.bookmarks_separator.is_empty() { - self.settings.format.bookmarks_separator = default_separator.clone(); - } - - if self.settings.styles.stack_number_style.is_empty() { - self.settings.styles.stack_number_style = default_number_color.clone(); - } - if self.settings.styles.stack_separator_style.is_empty() { - self.settings.styles.stack_separator_style = default_separator_color.clone(); - } - if self.settings.styles.stack_path_style.is_empty() { - self.settings.styles.stack_path_style = default_path_color.clone(); - } - if self.settings.styles.bookmarks_name_style.is_empty() { - self.settings.styles.bookmarks_name_style = default_number_color.clone(); - } - if self.settings.styles.bookmarks_seperator_style.is_empty() { - self.settings.styles.bookmarks_seperator_style = default_separator_color.clone(); - } - if self.settings.styles.bookmarks_path_style.is_empty() { - self.settings.styles.bookmarks_path_style = default_path_color.clone(); - } - - Ok(()) - } } diff --git a/src/main.rs b/src/main.rs index 9128cf4..5520017 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod arguments; mod config; mod bookmarks; mod stack; +mod output; mod debug; use arguments::*; @@ -9,6 +10,7 @@ use clap::Parser; use config::*; use bookmarks::*; use config_parser::*; +use output::Output; use stack::Stack; use std::env::{current_dir, var}; use std::io::{Error, Result}; @@ -16,59 +18,58 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; fn main() -> Result<()> { - let style_error = - generate_style_sequence(Some(STYLES.set.bold), Some(COLORS.fg.red), None); - let args = match Arguments::try_parse() { - Ok(a) => a, - Err(e) => { - print!("echo '{}{}{}' && false", style_error, e, RESET_SEQ); - return Ok(()); - } - }; + let mut output = Output::new(); let config = match Config::new(true) { Ok(value) => value, Err(error) => { - print!("echo '{}{}{}' && false", style_error, error, RESET_SEQ); + // config object is not ready at this point so the style + // has to be created by hand + print!("echo '{}{}{}' && false", generate_style_sequence(None, Some(COLORS.fg.red), None), error, RESET_SEQ); + return Ok(()) + } + }; + let args = match Arguments::try_parse() { + Ok(a) => a, + Err(error) => { + output.push_error(&error.to_string()); + output.print_output(&config); return Ok(()); } }; let mut bookmarks = match Bookmarks::new() { Ok(value) => value, Err(error) => { - print!("echo '{}{}{}' && false", style_error, error, RESET_SEQ); + output.push_error(&error.to_string()); + output.print_output(&config); return Ok(()); } }; let mut stack = match Stack::new(args.pid) { Ok(stack) => stack, Err(_) => { - print!( - "echo '{}-- failed to build stack{}' && false", - style_error, RESET_SEQ - ); + output.push_error(&"-- failed to build stack".to_string()); return Err(Error::other("")); } }; let res = match args.action { - Action::push(push_args) => handle_push(&push_args, &config, &mut stack), - Action::pop(pop_args) => handle_pop(&pop_args, &config, &mut stack), - Action::stack(stack_args) => handle_stack(&stack_args, &config, &mut stack), - Action::bookmark(bookmark_args) => handle_bookmark(&bookmark_args, &config, &mut bookmarks, &mut stack), - // Action::config(config_args) => handle_config(&config_args, &config), + Action::push(push_args) => handle_push(&push_args, &config, &mut stack, &mut output), + Action::pop(pop_args) => handle_pop(&pop_args, &config, &mut stack, &mut output), + Action::stack(stack_args) => handle_stack(&stack_args, &config, &mut stack, &mut output), + Action::bookmark(bookmark_args) => handle_bookmark(&bookmark_args, &config, &mut bookmarks, &mut stack, &mut output), }; if res.is_err() { - print!( - "echo '{}{}{}' && false", - style_error, - res.unwrap_err(), - RESET_SEQ, - ); + output.push_error(&res.unwrap_err().to_string()); + output.push_command(&"false".to_owned()); } + + // print output and command + output.print_output(&config); + Ok(()) } -fn handle_push(args: &PushArgs, config: &Config, stack: &mut Stack) -> Result<()> { +fn handle_push(args: &PushArgs, config: &Config, stack: &mut Stack, output: &mut Output) -> Result<()> { let path = match args.path.clone() { Some(value) => value, None => { @@ -82,11 +83,11 @@ fn handle_push(args: &PushArgs, config: &Config, stack: &mut Stack) -> Result<() } } }; - push_path(&path, stack, config)?; + push_path(&path, stack, config, output)?; Ok(()) } -fn handle_pop(args: &PopArgs, config: &Config, stack: &mut Stack) -> Result<()> { +fn handle_pop(args: &PopArgs, config: &Config, stack: &mut Stack, output: &mut Output) -> Result<()> { let mut num : Option = None; if let Some(a) = &args.action { match a { @@ -96,46 +97,44 @@ fn handle_pop(args: &PopArgs, config: &Config, stack: &mut Stack) -> Result<()> num = Some(*n); } let path = stack.pop_entry(num)?; - if config.settings.general.show_stack_on_push { - print!("echo '{}' && ", stack.to_formatted_string(&config.settings)?); + if config.general.show_stack_on_push { + output.push_info(&stack.to_formatted_string(config)?); } - println!( - "cd -- {}", - match path.to_str() { - Some(value) => value, - None => return Err(Error::other("-- failed to print popped path as string")), - } - ); + output.push_command(&format!("cd -- {}", match path.to_str() { + Some(value) => value, + None => return Err(Error::other("-- failed to print popped path as string")), + })); Ok(()) } -fn handle_stack(args: &StackArgs, config: &Config, stack: &mut Stack) -> Result<()> { +fn handle_stack(args: &StackArgs, config: &Config, stack: &mut Stack, output: &mut Output) -> Result<()> { if args.stack_action.is_some() { match args.stack_action.clone().unwrap() { - StackAction::clear(_) => return stack.clear_stack(&config.settings), + StackAction::clear(_) => { + stack.clear_stack()?; + output.push_info(&"stack cleared.".to_owned()); + return Ok(()); + } } } // retrieve stack - let output: String = stack.to_formatted_string(&config.settings)?; - print!("echo '{}'", output); + output.push_info(&stack.to_formatted_string(config)?); Ok(()) } -fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmarks, stack: &mut Stack) -> Result<()> { +fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmarks, stack: &mut Stack, output: &mut Output) -> Result<()> { // if args.bookmark_action.is_some() { if let Some(action) = &args.bookmark_action { match action { - BookmarkAction::list(_) => list_bookmarks(config, bookmarks)?, - BookmarkAction::add(args) => add_bookmarks(args, config, bookmarks)?, - BookmarkAction::remove(args) => remove_bookmarks(args, config, bookmarks)?, + BookmarkAction::list(_) => list_bookmarks(config, bookmarks, output)?, + BookmarkAction::add(args) => add_bookmarks(args, config, bookmarks, output)?, + BookmarkAction::remove(args) => remove_bookmarks(args, config, bookmarks, output)?, }; } else if args.name.is_some() { // handle `change to bookmark` let path = bookmarks.get_path_by_name(args.name.as_ref().unwrap())?; - push_path(&path, stack, config)?; + push_path(&path, stack, config, output)?; } else { - return Err(Error::other( - "-- provide either a `subcommand` or a `bookmark name`", - )); + list_bookmarks(config, bookmarks, output)?; } Ok(()) } @@ -147,13 +146,12 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar // Ok(()) // } -fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks) -> Result<()> { - let output = bookmarks.to_formatted_string(&config.settings)?; - println!("echo '{}'", output); +fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> { + output.push_info(&bookmarks.to_formatted_string(config)?); Ok(()) } -fn add_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookmarks) -> Result<()> { +fn add_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> { let mut path = match args.path.clone() { Some(value) => value, None => return Err(Error::other("-- missing path argument")), @@ -163,31 +161,40 @@ fn add_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookma Err(error) => return Err(Error::other(error.to_string())), }; bookmarks.add_bookmark(&args.name, &path)?; + + if config.general.show_books_on_bookmark { + output.push_info(&bookmarks.to_formatted_string(config)?); + } else { + output.push_info(&format!("added bookmark `{}{}{}`.", generate_style_sequence(Some(STYLES.set.bold), None, None), args.name, RESET_SEQ)); + } + Ok(()) } -fn remove_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookmarks) -> Result<()> { +fn remove_bookmarks(args: &BookmarkSubArgs, config: &Config, bookmarks: &mut Bookmarks, output: &mut Output) -> Result<()> { bookmarks.remove_bookmark(&args.name)?; + + if config.general.show_books_on_bookmark { + output.push_info(&bookmarks.to_formatted_string(config)?); + } else { + output.push_info(&format!("remove bookmark `{}{}{}`.", generate_style_sequence(Some(STYLES.set.bold), None, None), args.name, RESET_SEQ)); + } Ok(()) } /// push path to stack and print command to navigate to provided path -fn push_path(path: &Path, stack: &mut Stack, config: &Config) -> Result<()> { +fn push_path(path: &Path, stack: &mut Stack, config: &Config, output: &mut Output) -> Result<()> { if !path.is_dir() { return Err(Error::other("-- invalid path argument")); } let current_path = current_dir()?; - let next_path = path.canonicalize()?; stack.push_entry(¤t_path)?; - if config.settings.general.show_stack_on_push { - print!("echo '{}' && ", stack.to_formatted_string(&config.settings)?); + if config.general.show_stack_on_push { + output.push_info(&stack.to_formatted_string(&config)?); } - println!( - "cd -- {}", - match next_path.to_str() { - Some(value) => value, - None => return Err(Error::other("-- failed to print provided path as string")), - } - ); + output.push_command(&format!("cd -- {}", match path.canonicalize()?.to_str() { + Some(value) => value, + None => return Err(Error::other("-- failed to print provided path as string")), + })); Ok(()) } diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..ea078b9 --- /dev/null +++ b/src/output.rs @@ -0,0 +1,83 @@ +#![allow(unused)] + +use super::config::*; + +use std::backtrace::Backtrace; + +/// takes strings to output because the application is +/// called by a script which executes the output with `eval` +pub struct Output { + /// takes direct command output with custom formatting + /// **NOTE** - strings in `command` are not wrapped + /// with `echo` or similar and thus are interpretted as + /// commands + command: Vec, + /// takes formatted output to be printed with `echo` + /// strings in `info` are prepended with `echo` but + /// do not get any formatting applied + info: Vec, + /// takes warnings about command input or the state of + /// application + warning: Vec, + /// takes error messages about command input or the + /// state of application + error: Vec, +} + +impl Output { + pub fn new() -> Self { + Self { + command: Vec::::new(), + info: Vec::::new(), + warning: Vec::::new(), + error: Vec::::new(), + } + } + + /// push a command to the output pipeline + pub fn push_command(&mut self, command: &String) { + self.command.push(command.to_string()); + } + + /// push an information to the output pipeline + pub fn push_info(&mut self, info: &String) { + self.info.push(info.to_string()); + } + + /// push a warning to the output pipeline + pub fn push_warning(&mut self, warning: &String) { + self.warning.push(warning.to_string()); + } + + /// push an error to the output pipeline + pub fn push_error(&mut self, error: &String) { + self.error.push(error.to_string()); + } + + /// format and print styled output + /// NOTE - this will execute any commands held by `command` + pub fn print_output(&self, config: &Config) { + let mut info: String = self.info.iter().map(|entry| format!("echo '{}'", entry)).collect::>().join(" && "); + let mut warning: String = self.warning.iter().map(|entry| format!("echo '{}'", entry)).collect::>().join(" && "); + let mut error: String = self.error.iter().map(|entry| format!("echo '{}'", entry)).collect::>().join(" && "); + let mut command: String = self.command.join(" && "); + let mut output: Vec = Vec::::new(); + + if !info.is_empty() { + output.push(info); + } + if !warning.is_empty() { + warning = format!("echo '{}' && {}", config.styles.warning_style, warning); + output.push(warning); + } + if !error.is_empty() { + error = format!("echo '{}' && {}", config.styles.error_style, error); + output.push(error); + } + if !command.is_empty() { + output.push(command); + } + + println!("{}", output.join(" && echo && ")); + } +} diff --git a/src/stack.rs b/src/stack.rs index c655b76..d844e48 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -9,7 +9,7 @@ use sysinfo::{Pid, System}; use crate::make_padding_string; -use super::{apply_format, config::*}; +use super::{apply_format, config::*, output::Output}; #[derive(Debug, Clone)] pub struct Stack { @@ -31,7 +31,7 @@ impl Stack { } /// formats and prints stack to string - pub fn to_formatted_string(&self, config: &Settings) -> Result { + pub fn to_formatted_string(&self, config: &Config) -> Result { let mut buffer: String = "".to_string(); if self.stack.is_empty() { @@ -58,9 +58,8 @@ impl Stack { } /// clear stack by deleting the associated stack file - pub fn clear_stack(&mut self, config: &Settings) -> Result<()> { + pub fn clear_stack(&mut self) -> Result<()> { fs::remove_file(self.path.clone())?; - print!("echo 'stack cleared successfully.'"); Ok(()) } diff --git a/todo.md b/todo.md index 423e6b9..01af943 100644 --- a/todo.md +++ b/todo.md @@ -4,9 +4,14 @@ - [x] option to pop several entries at a time and option to pop the entire stack - [x] drop stack - [x] config file + - [x] implement procedural macro for config + - [x] to parse config + - [ ] to write default config - [ ] dedup stack option - [x] parse config file - [ ] apply config -- partially more done than before :) - - [ ] colored output > make it configurable through config file --> at least partially done + - [ ] `show-bookmarks-on-book` - [x] setting for separator string when displaying stack/bookmarks + - [ ] color option for punctuation (mostly '/') - [x] bookmarks + - [ ] do not resolve links in bookmarks