From 77cd64dd60a03c06b9b35d76e47242ba3aa6e2a5 Mon Sep 17 00:00:00 2001 From: quak Date: Sun, 15 Dec 2024 00:35:09 +0100 Subject: [PATCH] added function to wrap debug messages in an `echo` command implemented formatted printing of stack and bookmarks --- Cargo.toml | 2 + src/arguments.rs | 7 +-- src/bookmarks.rs | 71 +++++++++++++++++------ src/config.rs | 146 ++++++++++++++++++++++++++++++++++------------- src/debug.rs | 4 ++ src/format.rs | 17 +++++- src/main.rs | 29 +++++----- src/stack.rs | 47 ++++++++++----- todo.md | 6 +- 9 files changed, 234 insertions(+), 95 deletions(-) create mode 100644 src/debug.rs diff --git a/Cargo.toml b/Cargo.toml index e778eb1..aa4ecd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,7 @@ edition = "2021" [dependencies] clap = { version = "4.5.0", features = ["derive"] } +dirs = "5.0.1" +serde = { version = "1.0.216", features = [ "std", "derive" ] } sysinfo = "0.32.0" toml = "0.8.19" diff --git a/src/arguments.rs b/src/arguments.rs index 1367709..7fbcec2 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,3 +1,6 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] + use clap::{Args, Parser, Subcommand}; use std::path::PathBuf; @@ -15,7 +18,6 @@ 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), @@ -55,7 +57,6 @@ pub struct PopArgs { } #[derive(Debug, Clone, Subcommand)] -#[allow(non_camel_case_types)] pub enum PopAction { /// pop all entries and move to first entry in stack all, @@ -77,7 +78,6 @@ pub struct StackArgs { } #[derive(Debug, Clone, Subcommand)] -#[allow(non_camel_case_types)] pub enum StackAction { /// clear stack clear(EmptyArgs), @@ -94,7 +94,6 @@ pub struct BookmarkArgs { } #[derive(Debug, Clone, Subcommand)] -#[allow(non_camel_case_types)] pub enum BookmarkAction { /// list all bookmarks list(EmptyArgs), diff --git a/src/bookmarks.rs b/src/bookmarks.rs index a4d4a8a..120c7de 100644 --- a/src/bookmarks.rs +++ b/src/bookmarks.rs @@ -9,7 +9,9 @@ use std::io::{Error, Result}; use std::path::PathBuf; use std::str::FromStr; -use crate::{RESET_SEQ, STYLES}; +use crate::make_padding_string; + +use super::{apply_format, config::*}; #[derive(Debug, Clone)] pub struct Bookmarks { @@ -80,23 +82,15 @@ impl Bookmarks { Ok(()) } - /// writes the bookmarks file - fn write_bookmark_file(&self) -> Result<()> { - let mut file_content = String::new(); - for (mark, path) in self.bookmarks.iter() { - file_content.push_str(&format!("{}={}\n", mark, path.to_str().unwrap())); + /// returns path of bookmark if it exists + pub fn get_path_by_name(&mut self, name: &str) -> Result { + match self.bookmarks.get(name) { + Some(value) => Ok(value.to_owned()), + None => Err(Error::other(format!( + "-- bookmark with name `{}` does not exist", + name + ))), } - - let mut path = self.conf_dir.clone(); - path.push(Self::BOOKMARK_FILE_NAME); - - fs::write(path, file_content)?; - Ok(()) - } - - /// returns a mutable reference to self.bookmarks - pub fn get_bookmarks(&mut self) -> &mut HashMap { - &mut self.bookmarks } /// adds a key/value pair to bookmarks and writes the bookmarks file @@ -124,4 +118,47 @@ impl Bookmarks { } Ok(()) } + + /// formats and prints bookmarks to string + pub fn to_formatted_string(&self, config: &Settings) -> Result { + let mut buffer = String::new(); + + if self.bookmarks.is_empty() { + buffer.push_str("-- there are no bookmarks defined"); + } else { + let max_name_len = match self.bookmarks.keys().map(String::len).max() { + Some(value) => value, + None => return Err(Error::other("-- failed to determine maximum bookmark name length")), + }; + for (mark, path) in &self.bookmarks { + let padding = make_padding_string(max_name_len - mark.len()); + let name = apply_format(mark, &config.styles.bookmarks_name); + let separator = apply_format( + &config.format.bookmarks_separator, + &config.styles.bookmarks_seperator, + ); + let path = apply_format(path.to_str().unwrap(), &config.styles.bookmarks_path); + if config.format.align_separators { + buffer.push_str(&format!("{}{}{}{}\n", name, padding, separator, path)); + } else { + buffer.push_str(&format!("{}{}{}{}\n", name, separator, padding, path)); + } + } + } + Ok(buffer) + } + + /// writes the bookmarks file + fn write_bookmark_file(&self) -> Result<()> { + let mut file_content = String::new(); + for (mark, path) in self.bookmarks.iter() { + file_content.push_str(&format!("{}={}\n", mark, path.to_str().unwrap())); + } + + let mut path = self.conf_dir.clone(); + path.push(Self::BOOKMARK_FILE_NAME); + + fs::write(path, file_content)?; + Ok(()) + } } diff --git a/src/config.rs b/src/config.rs index 0132125..76f23b6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,54 +1,54 @@ +#![allow(dead_code)] + //! handle the config file and bookmarks stored //! in said config file +use crate::format::*; +use dirs::config_dir; +use serde::{Deserialize, Serialize}; +use std::fs; use std::io::{Error, Result}; use std::path::PathBuf; -use std::env::var; -use std::fs; -use std::str::FromStr; -use toml::{from_str, Table}; - -use crate::{RESET_SEQ, STYLES}; #[derive(Debug, Clone)] pub struct Config { - conf_dir: PathBuf, + conf_file: PathBuf, pub settings: Settings, } -#[allow(dead_code)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(default)] pub struct Settings { pub general: GeneralSettings, pub format: FormatSettings, pub styles: StyleSettings, } -#[allow(dead_code)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(default)] pub struct GeneralSettings { pub show_stack_on_push: bool, pub show_stack_on_pop: bool, pub show_stack_on_bookmark: bool, } -#[allow(dead_code)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(default)] pub struct FormatSettings { pub stack_separator: String, pub bookmarks_separator: String, + pub align_separators: bool, } -#[allow(dead_code)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[serde(default)] pub struct StyleSettings { pub stack_number: String, pub stack_separator: String, pub stack_path: String, - pub bookmarks_key: String, + pub bookmarks_name: String, pub bookmarks_seperator: String, pub bookmarks_path: String, - pub reset: String, } impl Config { @@ -57,7 +57,7 @@ impl Config { /// generates and populates a new instance of Config pub fn new() -> Result { let mut config = Config { - conf_dir: PathBuf::new(), + conf_file: PathBuf::new(), settings: Settings { general: GeneralSettings { show_stack_on_push: false, @@ -65,47 +65,111 @@ impl Config { show_stack_on_bookmark: false, }, format: FormatSettings { - bookmarks_separator: " - ".to_owned(), - stack_separator: " - ".to_owned(), + bookmarks_separator: String::new(), + stack_separator: String::new(), + align_separators: false, }, styles: StyleSettings { - stack_number: "".to_owned(), - stack_separator: "".to_owned(), - stack_path: "".to_owned(), - bookmarks_key: "".to_owned(), - bookmarks_seperator: "".to_owned(), - bookmarks_path: "".to_owned(), - reset: RESET_SEQ.to_owned(), + stack_number: String::new(), + stack_separator: String::new(), + stack_path: String::new(), + bookmarks_name: String::new(), + bookmarks_seperator: String::new(), + bookmarks_path: String::new(), }, }, }; - // get home directory path - let home_dir = match var("HOME") { - Ok(value) => value, - Err(error) => return Err(Error::other(error.to_string())), + // get configuration directory + config.conf_file = match config_dir() { + Some(value) => value, + None => { + return Err(Error::other( + "-- failed to retrieve configuration directory", + )) + } }; - // create PathBuf object from home dir path - config.conf_dir = match PathBuf::from_str(&home_dir) { - Ok(value) => value, - Err(error) => return Err(Error::other(error.to_string())), - }; - // expand home directory path to get configuration directory path - config.build_config()?; + // expand path to configuration file + config + .conf_file + .push(format!("navigate/{}", Self::CONFIG_FILE_NAME)); + + // parse configuration file and populate config struct + config.build_settings()?; + config.set_default_settings()?; + config.write_config_file()?; Ok(config) } + /// formats and prints config to string + pub fn to_formatted_string(&self) -> Result { + let mut buffer = String::new(); + buffer = format!("{:#?}", self.settings); + Ok(buffer) + } + + /// write configuration file to save changed settings + pub fn write_config_file(&self) -> Result<()> { + let conf_str = match toml::to_string(&self.settings) { + Ok(value) => value, + Err(error) => return Err(Error::other(error.to_string())), + }; + fs::write(self.conf_file.clone(), conf_str) + } + /// reads and parses the configuration file - fn build_config(&mut self) -> Result<()> { - let config_file = match fs::read_to_string(&self.conf_dir) { + fn build_settings(&mut self) -> Result<()> { + if !self.conf_file.is_file() { + return Ok(()); + } + let config_str = match fs::read_to_string(&self.conf_file) { Ok(value) => value, Err(error) => return Err(error), }; - let conf_table = match config_file.parse::() { + self.settings = match toml::from_str(&config_str) { Ok(value) => value, Err(error) => return Err(Error::other(error.to_string())), }; 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 = + generate_style_sequence(None, Some(COLORS.fg.default), Some(COLORS.bg.default)); + let default_separator_color = + generate_style_sequence(None, Some(COLORS.fg.cyan), Some(COLORS.bg.default)); + let default_path_color = + generate_style_sequence(None, Some(COLORS.fg.default), Some(COLORS.bg.default)); + + 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.is_empty() { + self.settings.styles.stack_number = default_number_color.clone(); + } + if self.settings.styles.stack_separator.is_empty() { + self.settings.styles.stack_separator = default_separator_color.clone(); + } + if self.settings.styles.stack_path.is_empty() { + self.settings.styles.stack_path = default_path_color.clone(); + } + if self.settings.styles.bookmarks_name.is_empty() { + self.settings.styles.bookmarks_name = default_number_color.clone(); + } + if self.settings.styles.bookmarks_seperator.is_empty() { + self.settings.styles.bookmarks_seperator = default_separator_color.clone(); + } + if self.settings.styles.bookmarks_path.is_empty() { + self.settings.styles.bookmarks_path = default_path_color.clone(); + } + + Ok(()) + } } diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..a224baf --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,4 @@ + +pub fn debug_print(string: &str) { + println!("echo '{}' && ", string); +} \ No newline at end of file diff --git a/src/format.rs b/src/format.rs index 3c22ff8..2c39fa6 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + pub const ESC: &str = "\x1B"; pub const PREFIX: &str = "\x1B["; pub const RESET_ARG: &str = "0"; @@ -105,6 +107,11 @@ pub enum ColorContext { Background, } +/// prepends input with style string and appends the reset sequence at the end +pub fn apply_format(input: &str, style: &str) -> String { + format!("{}{}{}", style, input, RESET_SEQ) +} + /// generates a common style sequence of format /// `\x1B[;;m` /// all elements are optional, if none is supplied the function returns an error @@ -131,7 +138,7 @@ pub fn generate_style_sequence( } // panic if no arguments provided since this is a programming mistake - // which should not + // which should not if arguments.is_empty() { panic!("no arguments provided to 'generate_style_sequence()'"); } @@ -157,7 +164,7 @@ pub fn generate_256color_sequence(context: ColorContext, color: u8) -> String { /// generates a rgb color sequence /// **note**: not all terminal emulators support rgb colors -/// +/// /// rgb sequences are built the same as 256 color sequences: /// `\x1B[;2;;;m` /// where *context* is either '38' or '48' for foreground and background respectively @@ -174,3 +181,9 @@ pub fn generate_rgb_sequence(context: ColorContext, red: u8, green: u8, blue: u8 sequence.push_str(&format!("{red};{green};{blue}m")); sequence } + +/// generates a padding string for numbers in a list +pub fn make_padding_string(len: usize) -> String { + // determine padding needed to align the paths + String::from_utf8(vec![b' '; len]).unwrap() +} diff --git a/src/main.rs b/src/main.rs index a92ce1d..0dfc71e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod format; mod config; mod bookmarks; mod stack; +mod debug; use arguments::*; use clap::Parser; @@ -54,6 +55,7 @@ fn main() -> Result<()> { 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), }; if res.is_err() { @@ -108,11 +110,11 @@ fn handle_pop(args: &PopArgs, _config: &Config, stack: &mut Stack) -> Result<()> fn handle_stack(args: &StackArgs, config: &Config, stack: &mut Stack) -> Result<()> { if args.stack_action.is_some() { match args.stack_action.clone().unwrap() { - StackAction::clear(_) => return stack.clear_stack(config), + StackAction::clear(_) => return stack.clear_stack(&config.settings), } } // retrieve stack - let output: String = stack.to_string(None)?; + let output: String = stack.to_formatted_string(&config.settings)?; print!("echo '{}'", output); Ok(()) } @@ -125,12 +127,9 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar BookmarkAction::add(args) => add_bookmarks(args, config, bookmarks)?, BookmarkAction::remove(args) => remove_bookmarks(args, config, bookmarks)?, }; - } else if args.name.is_some() { - let path = match bookmarks.get_bookmarks().get(args.name.as_ref().unwrap()) { - Some(value) => value, - None => return Err(Error::other("-- requested bookmark does not exist")), - }; - push_path(path, stack)?; + } 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)?; } else { return Err(Error::other( "-- provide either a `subcommand` or a `bookmark name`", @@ -139,12 +138,16 @@ fn handle_bookmark(args: &BookmarkArgs, config: &Config, bookmarks: &mut Bookmar Ok(()) } +// fn handle_config(args: &ConfigArgs, config: &Config) -> Result<()> { +// match args { +// ConfigAction::show => println!("echo '{}'", config.to_formatted_string()), +// } +// Ok(()) +// } + fn list_bookmarks(config: &Config, bookmarks: &mut Bookmarks) -> Result<()> { - let mut buffer = String::new(); - for (mark, path) in bookmarks.get_bookmarks() { - buffer.push_str(&format!("{} : {}\n", mark, path.to_str().unwrap())); - } - println!("echo '{}'", buffer); + let output = bookmarks.to_formatted_string(&config.settings)?; + println!("echo '{}'", output); Ok(()) } diff --git a/src/stack.rs b/src/stack.rs index 9bb9723..0f48a15 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -1,4 +1,5 @@ -use super::config::*; +#![allow(dead_code)] + use std::fs; use std::fs::File; use std::io::{Error, Result}; @@ -6,6 +7,10 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use sysinfo::{Pid, System}; +use crate::make_padding_string; + +use super::{apply_format, config::*}; + #[derive(Debug, Clone)] pub struct Stack { pid: u32, @@ -25,25 +30,37 @@ impl Stack { Ok(stack) } - // return stack - pub fn to_string(&self, _settings: Option) -> Result { + // formats and prints stack to string + pub fn to_formatted_string(&self, config: &Settings) -> Result { + let mut buffer: String = "".to_string(); + if self.stack.is_empty() { - return Err(Error::other("-- the stack is empty")); + buffer.push_str("-- the stack is empty"); + } else { + // print stack to string + let max_num_len = self.stack.len().to_string().len(); + for (n, item) in self.stack.iter().rev().enumerate() { + let padding = make_padding_string(max_num_len - n.to_string().len()); + let number = apply_format(&n.to_string(), &config.styles.stack_number); + let separator = apply_format( + &config.format.stack_separator, + &config.styles.stack_separator, + ); + let path = apply_format(item.to_str().unwrap(), &config.styles.stack_path); + if config.format.align_separators { + buffer.push_str(&format!("{}{}{}{}\n", number, padding, separator, path)); + } else { + buffer.push_str(&format!("{}{}{}{}\n", number, separator, padding, path)); + } + } } - // print stack to string - let mut output: String = "".to_string(); - for (n, item) in self.stack.iter().rev().enumerate() { - output.push_str(&format!("'{} - {}'\n", n, item.to_str().unwrap())); - } - Ok(output) + Ok(buffer) } /// clear stack by deleting the associated stack file - pub fn clear_stack(&mut self, _config: &Config) -> Result<()> { + pub fn clear_stack(&mut self, config: &Settings) -> Result<()> { fs::remove_file(self.path.clone())?; - print!( - "echo stack cleared successfully.'" - ); + print!("echo 'stack cleared successfully.'"); Ok(()) } @@ -172,4 +189,4 @@ impl Stack { Ok(()) } -} // end `impl database` +} diff --git a/todo.md b/todo.md index 2e89c90..360650f 100644 --- a/todo.md +++ b/todo.md @@ -5,8 +5,8 @@ - [x] drop stack - [ ] config file - [ ] dedup stack option - - [ ] parse config file - - [ ] apply config + - [ ] parse config file -- partially done, handling of colors to be fixed + - [ ] apply config -- partially done - [ ] colored output > make it configurable through config file - - [ ] setting for separator string when displaying stack/bookmarks + - [x] setting for separator string when displaying stack/bookmarks - [x] bookmarks