From 059591a1be6b887eaca9b114fdb5b350a65bae43 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 17 Feb 2025 19:50:55 +0100 Subject: implement basic config struct and example file --- src/config.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/config.rs (limited to 'src/config.rs') diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2ef296a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,46 @@ +// bibiman - a TUI for managing BibLaTeX databases +// Copyright (C) 2024 lukeflo +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +///// + +use std::path::PathBuf; + +use config::{ConfigError, FileFormat}; +use serde::Deserialize; + +use crate::cliargs::CLIArgs; + +#[derive(Debug, Clone, Deserialize)] +pub struct BibiConfig { + pub general: General, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct General { + pub bibfiles: Vec, + pub editor: String, +} + +impl BibiConfig { + pub fn new(args: &mut CLIArgs) -> Result { + let mut cfg = config::Config::builder(); + cfg = cfg.add_source( + config::File::from(args.cfg_path.clone()) + .format(FileFormat::Toml) + .required(true), + ); + cfg.build()?.try_deserialize() + } +} -- cgit v1.2.3 From d443843d352d740b895c4d622eb9af9567aa7423 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 17 Feb 2025 20:55:46 +0100 Subject: improve file handling + If config file **and** CLI args have different files/dirs, concat them and open all + Adapt UI to show which files are choosen + TODO: Flag for ignoring config file --- example-config.toml | 3 + files/bibiman.toml | 3 - src/app.rs | 5 +- src/bibiman.rs | 56 ++-- src/bibiman/bibisetup.rs | 2 +- src/cliargs.rs | 6 +- src/config.rs | 15 +- src/main.rs | 8 +- src/tui/ui.rs | 30 +-- tests/biblatex-test.bib | 662 +++++++++++++++++++++++++++-------------------- 10 files changed, 450 insertions(+), 340 deletions(-) create mode 100644 example-config.toml delete mode 100644 files/bibiman.toml (limited to 'src/config.rs') diff --git a/example-config.toml b/example-config.toml new file mode 100644 index 0000000..8a4aef6 --- /dev/null +++ b/example-config.toml @@ -0,0 +1,3 @@ +[general] +bibfiles = ["./tests/biblatex-test.bib"] +editor = "vi" diff --git a/files/bibiman.toml b/files/bibiman.toml deleted file mode 100644 index e24b597..0000000 --- a/files/bibiman.toml +++ /dev/null @@ -1,3 +0,0 @@ -[general] -bibfiles = ["../tests/biblatex-test.bib"] -editor = "vi" diff --git a/src/app.rs b/src/app.rs index 977cb6c..c60d81e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,6 +16,7 @@ ///// use crate::bibiman::{CurrentArea, FormerArea}; +use crate::config::BibiConfig; use color_eyre::eyre::{Context, Ok, Result}; // use super::Event; use crate::cliargs::CLIArgs; @@ -46,11 +47,11 @@ pub struct App { impl App { // Constructs a new instance of [`App`]. - pub fn new(args: &CLIArgs) -> Result { + pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result { // Self::default() let running = true; let input = Input::default(); - let bibiman = Bibiman::new(args)?; + let bibiman = Bibiman::new(args, cfg)?; Ok(Self { running, bibiman, diff --git a/src/bibiman.rs b/src/bibiman.rs index 71288ce..6aa138d 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -15,12 +15,13 @@ // along with this program. If not, see . ///// -use crate::app; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{bibisetup::*, search::BibiSearch}; use crate::cliargs::CLIArgs; +use crate::config::BibiConfig; use crate::tui::popup::{PopupArea, PopupKind}; use crate::tui::Tui; +use crate::{app, cliargs}; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; use color_eyre::eyre::Result; @@ -61,7 +62,7 @@ pub enum FormerArea { #[derive(Debug)] pub struct Bibiman { // main bib file - // pub main_bibfiles: Vec, + pub main_bibfiles: Vec, // main bibliography pub main_biblio: BibiSetup, // search struct: @@ -82,15 +83,17 @@ pub struct Bibiman { impl Bibiman { // Constructs a new instance of [`App`]. - pub fn new(args: &CLIArgs) -> Result { - // let main_bibfiles = args.fileargs.clone(); - let main_biblio = BibiSetup::new(&args.files); + pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result { + let mut main_bibfiles: Vec = args.pos_args.clone(); + main_bibfiles.append(&mut cfg.general.bibfiles); + let main_bibfiles = cliargs::parse_files(main_bibfiles); + let main_biblio = BibiSetup::new(&main_bibfiles); let tag_list = TagList::new(main_biblio.keyword_list.clone()); let search_struct = BibiSearch::default(); let entry_table = EntryTable::new(main_biblio.entry_list.clone()); let current_area = CurrentArea::EntryArea; Ok(Self { - // main_bibfiles, + main_bibfiles, main_biblio, tag_list, search_struct, @@ -128,8 +131,8 @@ impl Bibiman { self.former_area = None; } - pub fn update_lists(&mut self, args: &CLIArgs) { - self.main_biblio = BibiSetup::new(&args.files); + pub fn update_lists(&mut self) { + self.main_biblio = BibiSetup::new(&self.main_bibfiles); self.tag_list = TagList::new(self.main_biblio.keyword_list.clone()); self.entry_table = EntryTable::new(self.main_biblio.entry_list.clone()); } @@ -338,17 +341,17 @@ impl Bibiman { // Check if multiple files were passed to bibiman and // return the correct file path - let filepath = if args.files.len() == 1 { - args.files.first().unwrap().as_os_str() + let filepath = if self.main_bibfiles.len() == 1 { + self.main_bibfiles.first().unwrap().as_os_str() } else { let mut idx = 0; - for f in &args.files { + for f in &self.main_bibfiles { if search::search_pattern_in_file(&citekey_pattern, &f).is_some() { break; } idx += 1; } - args.files[idx].as_os_str() + self.main_bibfiles[idx].as_os_str() }; let filecontent = fs::read_to_string(&filepath).unwrap(); @@ -391,7 +394,7 @@ impl Bibiman { tui.terminal.clear()?; // Update the database and the lists to show changes - Self::update_lists(self, args); + Self::update_lists(self); // Select entry which was selected before entering editor self.select_entry_by_citekey(citekey); @@ -431,7 +434,7 @@ impl Bibiman { .expect("Couldn't parse fetched entry into string"); self.popup_area.popup_sel_item = entry; self.popup_area.popup_kind = Some(PopupKind::AppendToFile); - self.append_to_file(args); + self.append_to_file(); self.former_area = Some(FormerArea::EntryArea); self.current_area = CurrentArea::PopupArea; self.popup_area.popup_state.select(Some(0)) @@ -441,14 +444,21 @@ impl Bibiman { } } - pub fn append_to_file(&mut self, args: &CLIArgs) { + pub fn append_to_file(&mut self) { let mut items = vec!["Create new file".to_owned()]; - if args.files.len() > 1 { - for f in args.files.clone() { + if self.main_bibfiles.len() > 1 { + for f in self.main_bibfiles.clone() { items.push(f.to_str().unwrap().to_owned()); } } else { - items.push(args.files.first().unwrap().to_str().unwrap().to_owned()); + items.push( + self.main_bibfiles + .first() + .unwrap() + .to_str() + .unwrap() + .to_owned(), + ); } self.popup_area.popup_selection(items); } @@ -472,8 +482,8 @@ impl Bibiman { let mut file = if self.popup_area.popup_list[popup_idx].contains("Create new file") { let citekey = PathBuf::from(&citekey); // Get path of current files - let path: PathBuf = if args.files[0].is_file() { - args.files[0].parent().unwrap().to_owned() + let path: PathBuf = if self.main_bibfiles[0].is_file() { + self.main_bibfiles[0].parent().unwrap().to_owned() } else { dirs::home_dir().unwrap() // home dir as fallback }; @@ -482,11 +492,11 @@ impl Bibiman { let newfile = path.join(citekey); - args.files.push(newfile.clone()); + self.main_bibfiles.push(newfile.clone()); File::create_new(newfile).unwrap() } else { - let file_path = &args.files[popup_idx - 1]; + let file_path = &self.main_bibfiles[popup_idx - 1]; // Check if similar citekey already exists let file_string = read_to_string(&file_path).unwrap(); @@ -522,7 +532,7 @@ impl Bibiman { // Write content to file file.write_all(self.popup_area.popup_sel_item.as_bytes())?; // Update the database and the lists to reflect the new content - self.update_lists(args); + self.update_lists(); self.close_popup(); // Select newly created entry diff --git a/src/bibiman/bibisetup.rs b/src/bibiman/bibisetup.rs index 21b3c4b..3f64d9c 100644 --- a/src/bibiman/bibisetup.rs +++ b/src/bibiman/bibisetup.rs @@ -144,7 +144,7 @@ impl BibiSetup { if main_bibfiles.is_empty() { println!( "{}", - "No bibfile passed as argument. Please select a valid file." + "No bibfile passed as argument or through config file. Please select a valid file." .red() .bold() ); diff --git a/src/cliargs.rs b/src/cliargs.rs index ecec93e..50ed6f5 100644 --- a/src/cliargs.rs +++ b/src/cliargs.rs @@ -31,7 +31,7 @@ pub struct CLIArgs { pub helparg: bool, pub versionarg: bool, pub pos_args: Vec, - pub files: Vec, + // pub files: Vec, pub cfg_path: PathBuf, // INFO: AppColors struct later should be moved to config/app struct // when config file is implemented @@ -65,7 +65,7 @@ impl CLIArgs { } } - args.files = parse_files(args.pos_args.clone()); + // args.files = parse_files(args.pos_args.clone()); Ok(args) } @@ -74,7 +74,7 @@ impl CLIArgs { /// This function maps a vector containing paths to another vector containing paths. /// But it will walk all entries of the first vec which are directories /// and put only valid file paths with `.bib` ending to the resulting vec. -fn parse_files(args: Vec) -> Vec { +pub fn parse_files(args: Vec) -> Vec { let mut files: Vec = Vec::new(); // If pos arg is file, just push it to path vec for i in args { diff --git a/src/config.rs b/src/config.rs index 2ef296a..a80cc13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,16 +30,25 @@ pub struct BibiConfig { #[derive(Debug, Clone, Deserialize)] pub struct General { pub bibfiles: Vec, - pub editor: String, + pub editor: Option, } impl BibiConfig { - pub fn new(args: &mut CLIArgs) -> Result { + pub fn default(args: &CLIArgs) -> Self { + Self { + general: General { + bibfiles: args.pos_args.clone(), + editor: None, + }, + } + } + + pub fn new(args: &CLIArgs) -> Result { let mut cfg = config::Config::builder(); cfg = cfg.add_source( config::File::from(args.cfg_path.clone()) .format(FileFormat::Toml) - .required(true), + .required(false), ); cfg.build()?.try_deserialize() } diff --git a/src/main.rs b/src/main.rs index a0f69d1..94f5042 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,12 +45,16 @@ async fn main() -> Result<()> { std::process::exit(0); } - let cfg = BibiConfig::new(&mut parsed_args)?; + let mut cfg = if parsed_args.cfg_path.is_file() { + BibiConfig::new(&parsed_args)? + } else { + BibiConfig::default(&parsed_args) + }; init_error_hooks()?; // Create an application. - let mut app = App::new(&parsed_args)?; + let mut app = App::new(&mut parsed_args, &mut cfg)?; app.run(&mut parsed_args).await?; Ok(()) diff --git a/src/tui/ui.rs b/src/tui/ui.rs index d85f318..52d2d9a 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -410,27 +410,21 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .horizontal_margin(1) .areas(rect); - let file_info = if args.pos_args.len() == 1 && args.pos_args.first().unwrap().is_file() { + let file_info = if app.bibiman.main_bibfiles.len() == 1 + && app.bibiman.main_bibfiles.first().unwrap().is_file() + { Line::from(vec![ Span::raw("File: ") .fg(Color::Indexed(args.colors.main_text_color)) .bold(), - Span::raw(args.pos_args[0].file_name().unwrap().to_string_lossy()) - .fg(Color::Indexed(args.colors.main_text_color)) - .bold(), - ]) - .bg(Color::Indexed(args.colors.bar_bg_color)) - } else if args.pos_args.len() == 1 && args.pos_args.first().unwrap().is_dir() { - Line::from(vec![ - Span::raw("Directory: ") - .bold() - .fg(Color::Indexed(args.colors.main_text_color)), - Span::raw(args.pos_args[0].file_name().unwrap().to_string_lossy()) - .fg(Color::Indexed(args.colors.main_text_color)) - .bold(), - Span::raw("/*.bib") - .fg(Color::Indexed(args.colors.main_text_color)) - .bold(), + Span::raw( + app.bibiman.main_bibfiles[0] + .file_name() + .unwrap() + .to_string_lossy(), + ) + .fg(Color::Indexed(args.colors.main_text_color)) + .bold(), ]) .bg(Color::Indexed(args.colors.bar_bg_color)) } else { @@ -438,7 +432,7 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Span::raw("Multiple files (") .fg(Color::Indexed(args.colors.main_text_color)) .bold(), - Span::raw(count_files(&args.files).to_string()) + Span::raw(count_files(&app.bibiman.main_bibfiles).to_string()) .fg(Color::Indexed(args.colors.main_text_color)) .bold(), Span::raw(")") diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib index d0fc0a6..0a1202e 100644 --- a/tests/biblatex-test.bib +++ b/tests/biblatex-test.bib @@ -1,378 +1,470 @@ @set{set, - entryset = {herrmann,aksin,yoon}, - annotation = {A \texttt{set} with three members.}, + entryset = {herrmann,aksin,yoon}, + annotation = {A \texttt{set} with three members.}, } @set{stdmodel, - entryset = {glashow,weinberg,salam}, - annotation = {A \texttt{set} with three members discussing the standard model of particle physics.}, + entryset = {glashow,weinberg,salam}, + annotation = {A \texttt{set} with three members discussing the standard + model of particle physics.}, } @collection{matuz:doody, - title = {Contemporary Literary Criticism}, - year = {1990}, - location = {Detroit}, - publisher = {Gale}, - volume = {61}, - pages = {204--208}, - editor = {Matuz, Roger and Miller, Helen}, - keywords = {narration}, - langid = {english}, - langidopts = {variant=american}, - annotation = {A \texttt{collection} entry providing the excerpt information for the \texttt{doody} entry. Note the format of the \texttt{pages} field}, + title = {Contemporary Literary Criticism}, + year = {1990}, + location = {Detroit}, + publisher = {Gale}, + volume = {61}, + pages = {204--208}, + editor = {Matuz, Roger and Miller, Helen}, + keywords = {narration}, + langid = {english}, + langidopts = {variant=american}, + annotation = {A \texttt{collection} entry providing the excerpt information + for the \texttt{doody} entry. Note the format of the \texttt{ + pages} field}, } @article{aksin, - title = {Effect of immobilization on catalytic characteristics of saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck} reactions}, - author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent and {\c{C}}etinkaya, Bekir and Ni, Chaoying and B{\"u}y{\"u}kg{\"u}ng{\"o}r, Orhan and {\"O}zkal, Erhan}, - volume = {691}, - number = {13}, - pages = {3027--3036}, - journaltitle = jomch, - date = {2006}, - indextitle = {Effect of immobilization on catalytic characteristics}, + title = {Effect of immobilization on catalytic characteristics of saturated + {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck} reactions}, + author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent and { + \c{C}}etinkaya, Bekir and Ni, Chaoying and B{\"u}y{\"u}kg{\"u}ng{\" + o}r, Orhan and {\"O}zkal, Erhan}, + volume = {691}, + number = {13}, + pages = {3027--3036}, + journaltitle = jomch, + date = {2006}, + indextitle = {Effect of immobilization on catalytic characteristics}, } @article{angenendt, - title = {In Honore Salvatoris~-- Vom Sinn und Unsinn der Patrozinienkunde}, - shorttitle = {In Honore Salvatoris}, - author = {Angenendt, Arnold}, - volume = {97}, - pages = {431--456, 791--823}, - journaltitle = {Revue d'Histoire Eccl{\'e}siastique}, - date = {2002}, - langid = {german}, - indextitle = {In Honore Salvatoris}, - annotation = {A German article in a French journal. Apart from that, a typical \texttt{article} entry. Note the \texttt{indextitle} field}, + title = {In Honore Salvatoris~-- Vom Sinn und Unsinn der Patrozinienkunde}, + shorttitle = {In Honore Salvatoris}, + author = {Angenendt, Arnold}, + volume = {97}, + pages = {431--456, 791--823}, + journaltitle = {Revue d'Histoire Eccl{\'e}siastique}, + date = {2002}, + langid = {german}, + indextitle = {In Honore Salvatoris}, + annotation = {A German article in a French journal. Apart from that, a + typical \texttt{article} entry. Note the \texttt{indextitle} + field}, } @book{aristotle:anima, - title = {De Anima}, - author = {Aristotle}, - location = {Cambridge}, - publisher = cup, - date = {1907}, - editor = {Hicks, Robert Drew}, - keywords = {primary, ancient, philosophy, athens}, - langid = {english}, - langidopts = {variant=british}, - annotation = {A \texttt{book} entry with an \texttt{author} and an \texttt{editor}}, + title = {De Anima}, + author = {Aristotle}, + location = {Cambridge}, + publisher = cup, + date = {1907}, + editor = {Hicks, Robert Drew}, + keywords = {primary, ancient, philosophy, athens}, + langid = {english}, + langidopts = {variant=british}, + annotation = {A \texttt{book} entry with an \texttt{author} and an \texttt{ + editor}}, } @book{aristotle:physics, - title = {Physics}, - shorttitle = {Physics}, - author = {Aristotle}, - location = {New York}, - publisher = {G. P. Putnam}, - url = {https://www.infobooks.org/authors/classic/aristotle-books/#Physic}, - date = {1929}, - translator = {Wicksteed, P. H. and Cornford, F. M.}, - keywords = {primary, ancient, philosophy}, - langid = {english}, - langidopts = {variant=american}, - file = {~/Documents/projects/coding/bibiman/tests/aristotle_physics.pdf}, - annotation = {A \texttt{book} entry with a \texttt{translator} field}, + title = {Physics}, + shorttitle = {Physics}, + author = {Aristotle}, + location = {New York}, + publisher = {G. P. Putnam}, + url = {https://www.infobooks.org/authors/classic/aristotle-books/#Physic}, + date = {1929}, + translator = {Wicksteed, P. H. and Cornford, F. M.}, + keywords = {primary, ancient, philosophy}, + langid = {english}, + langidopts = {variant=american}, + file = {~/Documents/projects/coding/bibiman/tests/aristotle_physics.pdf}, + annotation = {A \texttt{book} entry with a \texttt{translator} field}, } @book{aristotle:poetics, - title = {Poetics}, - shorttitle = {Poetics}, - author = {Aristotle}, - location = {Oxford}, - publisher = {Clarendon Press}, - series = {Clarendon {Aristotle}}, - date = {1968}, - editor = {Lucas, D. W.}, - keywords = {primary}, - langid = {english}, - langidopts = {variant=british}, - annotation = {A \texttt{book} entry with an \texttt{author} and an \texttt{editor} as well as a \texttt{series} field}, + title = {Poetics}, + shorttitle = {Poetics}, + author = {Aristotle}, + location = {Oxford}, + publisher = {Clarendon Press}, + series = {Clarendon {Aristotle}}, + date = {1968}, + editor = {Lucas, D. W.}, + keywords = {primary}, + langid = {english}, + langidopts = {variant=british}, + annotation = {A \texttt{book} entry with an \texttt{author} and an \texttt{ + editor} as well as a \texttt{series} field}, } @mvbook{aristotle:rhetoric, - title = {The Rhetoric of {Aristotle} with a commentary by the late {Edward Meredith Cope}}, - shorttitle = {Rhetoric}, - author = {Aristotle}, - publisher = cup, - date = {1877}, - editor = {Cope, Edward Meredith}, - commentator = {Cope, Edward Meredith}, - volumes = {3}, - keywords = {primary}, - langid = {english}, - langidopts = {variant=british}, - sorttitle = {Rhetoric of Aristotle}, - indextitle = {Rhetoric of {Aristotle}, The}, - annotation = {A commented edition. Note the concatenation of the \texttt{editor} and \texttt{commentator} fields as well as the \texttt{volumes}, \texttt{sorttitle}, and \texttt{indextitle} fields}, + title = {The Rhetoric of {Aristotle} with a commentary by the late {Edward + Meredith Cope}}, + shorttitle = {Rhetoric}, + author = {Aristotle}, + publisher = cup, + date = {1877}, + editor = {Cope, Edward Meredith}, + commentator = {Cope, Edward Meredith}, + volumes = {3}, + keywords = {primary}, + langid = {english}, + langidopts = {variant=british}, + sorttitle = {Rhetoric of Aristotle}, + indextitle = {Rhetoric of {Aristotle}, The}, + annotation = {A commented edition. Note the concatenation of the \texttt{ + editor} and \texttt{commentator} fields as well as the \texttt{ + volumes}, \texttt{sorttitle}, and \texttt{indextitle} fields}, } @book{augustine, - title = {Heterogeneous catalysis for the synthetic chemist}, - shorttitle = {Heterogeneous catalysis}, - author = {Augustine, Robert L.}, - location = {New York}, - publisher = {Marcel Dekker}, - date = {1995}, - langid = {english}, - langidopts = {variant=american}, - annotation = {A plain \texttt{book} entry}, - keywords = {chemistry}, + title = {Heterogeneous catalysis for the synthetic chemist}, + shorttitle = {Heterogeneous catalysis}, + author = {Augustine, Robert L.}, + location = {New York}, + publisher = {Marcel Dekker}, + date = {1995}, + langid = {english}, + langidopts = {variant=american}, + annotation = {A plain \texttt{book} entry}, + keywords = {chemistry}, } @book{averroes/bland, - title = {The Epistle on the Possibility of Conjunction with the Active Intellect by {Ibn Rushd} with the Commentary of {Moses Narboni}}, - shorttitle = {Possibility of Conjunction}, - author = {Averroes}, - location = {New York}, - publisher = {Jewish Theological Seminary of America}, - series = {Moreshet: Studies in {Jewish} History, Literature and Thought}, - number = {7}, - date = {1982}, - editor = {Bland, Kalman P.}, - translator = {Bland, Kalman P.}, - keywords = {primary}, - langid = {english}, - langidopts = {variant=american}, - indextitle = {Epistle on the Possibility of Conjunction, The}, - annotation = {A \texttt{book} entry with a \texttt{series} and a \texttt{number}. Note the concatenation of the \texttt{editor} and \texttt{translator} fields as well as the \texttt{indextitle} field}, + title = {The Epistle on the Possibility of Conjunction with the Active + Intellect by {Ibn Rushd} with the Commentary of {Moses Narboni}}, + shorttitle = {Possibility of Conjunction}, + author = {Averroes}, + location = {New York}, + publisher = {Jewish Theological Seminary of America}, + series = {Moreshet: Studies in {Jewish} History, Literature and Thought}, + number = {7}, + date = {1982}, + editor = {Bland, Kalman P.}, + translator = {Bland, Kalman P.}, + keywords = {primary}, + langid = {english}, + langidopts = {variant=american}, + indextitle = {Epistle on the Possibility of Conjunction, The}, + annotation = {A \texttt{book} entry with a \texttt{series} and a \texttt{ + number}. Note the concatenation of the \texttt{editor} and + \texttt{translator} fields as well as the \texttt{indextitle} + field}, } @article{baez/article, - title = {Higher-Dimensional Algebra {V}: 2-Groups}, - author = {Baez, John C. and Lauda, Aaron D.}, - volume = {12}, - pages = {423--491}, - journaltitle = {Theory and Applications of Categories}, - date = {2004}, - version = {3}, - eprint = {math/0307200v3}, - eprinttype = {arxiv}, - langid = {english}, - keywords = {math}, - langidopts = {variant=american}, - annotation = {An \texttt{article} with \texttt{eprint} and \texttt{eprinttype} fields. Note that the arXiv reference is transformed into a clickable link if \texttt{hyperref} support has been enabled. Compare \texttt{baez\slash online}, which is the same item given as an \texttt{online} entry}, + title = {Higher-Dimensional Algebra {V}: 2-Groups}, + author = {Baez, John C. and Lauda, Aaron D.}, + volume = {12}, + pages = {423--491}, + journaltitle = {Theory and Applications of Categories}, + date = {2004}, + version = {3}, + eprint = {math/0307200v3}, + eprinttype = {arxiv}, + langid = {english}, + keywords = {math}, + langidopts = {variant=american}, + annotation = {An \texttt{article} with \texttt{eprint} and \texttt{ + eprinttype} fields. Note that the arXiv reference is + transformed into a clickable link if \texttt{hyperref} support + has been enabled. Compare \texttt{baez\slash online}, which is + the same item given as an \texttt{online} entry}, } @article{bertram, - title = {Gromov invariants for holomorphic maps on {Riemann} surfaces}, - shorttitle = {Gromov invariants}, - author = {Bertram, Aaron and Wentworth, Richard}, - volume = {9}, - number = {2}, - pages = {529--571}, - journaltitle = jams, - date = {1996}, - langid = {english}, - langidopts = {variant=american}, - annotation = {An \texttt{article} entry with a \texttt{volume} and a \texttt{number} field}, + title = {Gromov invariants for holomorphic maps on {Riemann} surfaces}, + shorttitle = {Gromov invariants}, + author = {Bertram, Aaron and Wentworth, Richard}, + volume = {9}, + number = {2}, + pages = {529--571}, + journaltitle = jams, + date = {1996}, + langid = {english}, + langidopts = {variant=american}, + annotation = {An \texttt{article} entry with a \texttt{volume} and a \texttt + {number} field}, } @article{doody, - title = {Hemingway's Style and {Jake's} Narration}, - author = {Doody, Terrence}, - year = {1974}, - journal = {The Journal of Narrative Technique}, - volume = {4}, - number = {3}, - pages = {212--225}, - langid = {english}, - langidopts = {variant=american}, - related = {matuz:doody}, + title = {Hemingway's Style and {Jake's} Narration}, + author = {Doody, Terrence}, + year = {1974}, + journal = {The Journal of Narrative Technique}, + volume = {4}, + number = {3}, + pages = {212--225}, + langid = {english}, + langidopts = {variant=american}, + related = {matuz:doody}, relatedstring = {\autocap{e}xcerpt in}, - annotation = {An \texttt{article} entry cited as an excerpt from a \texttt{collection} entry. Note the format of the \texttt{related} and \texttt{relatedstring} fields}, + annotation = {An \texttt{article} entry cited as an excerpt from a \texttt{ + collection} entry. Note the format of the \texttt{related} and + \texttt{relatedstring} fields}, } @article{gillies, - title = {Herder and the Preparation of {Goethe's} Idea of World Literature}, - author = {Gillies, Alexander}, - series = {newseries}, - volume = {9}, - pages = {46--67}, - journaltitle = {Publications of the English Goethe Society}, - date = {1933}, - langid = {english}, - langidopts = {variant=british}, - annotation = {An \texttt{article} entry with a \texttt{series} and a \texttt{volume} field. Note that format of the \texttt{series} field in the database file}, + title = {Herder and the Preparation of {Goethe's} Idea of World Literature}, + author = {Gillies, Alexander}, + series = {newseries}, + volume = {9}, + pages = {46--67}, + journaltitle = {Publications of the English Goethe Society}, + date = {1933}, + langid = {english}, + langidopts = {variant=british}, + annotation = {An \texttt{article} entry with a \texttt{series} and a \texttt + {volume} field. Note that format of the \texttt{series} field + in the database file}, } @article{glashow, - title = {Partial Symmetries of Weak Interactions}, - author = {Glashow, Sheldon}, - volume = {22}, - pages = {579--588}, - journaltitle = {Nucl.~Phys.}, - date = {1961}, + title = {Partial Symmetries of Weak Interactions}, + author = {Glashow, Sheldon}, + volume = {22}, + pages = {579--588}, + journaltitle = {Nucl.~Phys.}, + date = {1961}, } @article{herrmann, - title = {A carbocyclic carbene as an efficient catalyst ligand for {C--C} coupling reactions}, - author = {Herrmann, Wolfgang A. and {\"O}fele, Karl and Schneider, Sabine K. and Herdtweck, Eberhardt and Hoffmann, Stephan D.}, - volume = {45}, - number = {23}, - pages = {3859--3862}, - journaltitle = anch-ie, - date = {2006}, - indextitle = {Carbocyclic carbene as an efficient catalyst, A}, + title = {A carbocyclic carbene as an efficient catalyst ligand for {C--C} + coupling reactions}, + author = {Herrmann, Wolfgang A. and {\"O}fele, Karl and Schneider, Sabine K. + and Herdtweck, Eberhardt and Hoffmann, Stephan D.}, + volume = {45}, + number = {23}, + pages = {3859--3862}, + journaltitle = anch-ie, + date = {2006}, + indextitle = {Carbocyclic carbene as an efficient catalyst, A}, } @article{murray, - title = {Alkanethiolate gold cluster molecules with core diameters from 1.5 to 5.2~{nm}}, - shorttitle = {Alkanethiolate gold cluster molecules}, - author = {Hostetler, Michael J. and Wingate, Julia E. and Zhong, Chuan-Jian and Harris, Jay E. and Vachet, Richard W. and Clark, Michael R. and Londono, J. David and Green, Stephen J. and Stokes, Jennifer J. and Wignall, George D. and Glish, Gary L. and Porter, Marc D. and Evans, Neal D. and Murray, Royce W.}, - volume = {14}, - number = {1}, - pages = {17--30}, - journaltitle = {Langmuir}, - date = {1998}, - subtitle = {Core and monolayer properties as a function of core size}, - langid = {english}, - langidopts = {variant=american}, - indextitle = {Alkanethiolate gold cluster molecules}, - annotation = {An \texttt{article} entry with \arabic{author} authors. By default, long author and editor lists are automatically truncated. This is configurable}, + title = {Alkanethiolate gold cluster molecules with core diameters from 1.5 + to 5.2~{nm}}, + shorttitle = {Alkanethiolate gold cluster molecules}, + author = {Hostetler, Michael J. and Wingate, Julia E. and Zhong, Chuan-Jian + and Harris, Jay E. and Vachet, Richard W. and Clark, Michael R. and + Londono, J. David and Green, Stephen J. and Stokes, Jennifer J. and + Wignall, George D. and Glish, Gary L. and Porter, Marc D. and Evans + , Neal D. and Murray, Royce W.}, + volume = {14}, + number = {1}, + pages = {17--30}, + journaltitle = {Langmuir}, + date = {1998}, + subtitle = {Core and monolayer properties as a function of core size}, + langid = {english}, + langidopts = {variant=american}, + indextitle = {Alkanethiolate gold cluster molecules}, + annotation = {An \texttt{article} entry with \arabic{author} authors. By + default, long author and editor lists are automatically + truncated. This is configurable}, } @article{kastenholz, - title = {Computation of methodology\hyphen independent ionic solvation free energies from molecular simulations}, - author = {Kastenholz, M. A. and H{\"u}nenberger, Philippe H.}, - volume = {124}, - doi = {10.1063/1.2172593}, - journaltitle = jchph, - date = {2006}, - subtitle = {{I}. {The} electrostatic potential in molecular liquids}, - eid = {124106}, - langid = {english}, - langidopts = {variant=american}, - indextitle = {Computation of ionic solvation free energies}, - annotation = {An \texttt{article} entry with an \texttt{eid} and a \texttt{doi} field. Note that the \textsc{doi} is transformed into a clickable link if \texttt{hyperref} support has been enabled}, - abstract = {The computation of ionic solvation free energies from atomistic simulations is a surprisingly difficult problem that has found no satisfactory solution for more than 15 years. The reason is that the charging free energies evaluated from such simulations are affected by very large errors. One of these is related to the choice of a specific convention for summing up the contributions of solvent charges to the electrostatic potential in the ionic cavity, namely, on the basis of point charges within entire solvent molecules (M scheme) or on the basis of individual point charges (P scheme). The use of an inappropriate convention may lead to a charge-independent offset in the calculated potential, which depends on the details of the summation scheme, on the quadrupole-moment trace of the solvent molecule, and on the approximate form used to represent electrostatic interactions in the system. However, whether the M or P scheme (if any) represents the appropriate convention is still a matter of on-going debate. The goal of the present article is to settle this long-standing controversy by carefully analyzing (both analytically and numerically) the properties of the electrostatic potential in molecular liquids (and inside cavities within them).}, + title = {Computation of methodology\hyphen independent ionic solvation free + energies from molecular simulations}, + author = {Kastenholz, M. A. and H{\"u}nenberger, Philippe H.}, + volume = {124}, + doi = {10.1063/1.2172593}, + journaltitle = jchph, + date = {2006}, + subtitle = {{I}. {The} electrostatic potential in molecular liquids}, + eid = {124106}, + langid = {english}, + langidopts = {variant=american}, + indextitle = {Computation of ionic solvation free energies}, + annotation = {An \texttt{article} entry with an \texttt{eid} and a \texttt{ + doi} field. Note that the \textsc{doi} is transformed into a + clickable link if \texttt{hyperref} support has been enabled}, + abstract = {The computation of ionic solvation free energies from atomistic + simulations is a surprisingly difficult problem that has found no + satisfactory solution for more than 15 years. The reason is that + the charging free energies evaluated from such simulations are + affected by very large errors. One of these is related to the + choice of a specific convention for summing up the contributions + of solvent charges to the electrostatic potential in the ionic + cavity, namely, on the basis of point charges within entire + solvent molecules (M scheme) or on the basis of individual point + charges (P scheme). The use of an inappropriate convention may + lead to a charge-independent offset in the calculated potential, + which depends on the details of the summation scheme, on the + quadrupole-moment trace of the solvent molecule, and on the + approximate form used to represent electrostatic interactions in + the system. However, whether the M or P scheme (if any) + represents the appropriate convention is still a matter of + on-going debate. The goal of the present article is to settle + this long-standing controversy by carefully analyzing (both + analytically and numerically) the properties of the electrostatic + potential in molecular liquids (and inside cavities within them). + }, } @article{sarfraz, - title = {Technical section: {An} algorithm for automatic capturing of the font outlines}, - author = {M. Sarfraz and M. F. A. Razzak}, - year = {2002}, - journal = {Computers and Graphics}, - volume = {26}, - number = {5}, - pages = {795--804}, - issn = {0097-8493}, - annotation = {An \texttt{article} entry with an \texttt{issn} field}, + title = {Technical section: {An} algorithm for automatic capturing of the + font outlines}, + author = {M. Sarfraz and M. F. A. Razzak}, + year = {2002}, + journal = {Computers and Graphics}, + volume = {26}, + number = {5}, + pages = {795--804}, + issn = {0097-8493}, + annotation = {An \texttt{article} entry with an \texttt{issn} field}, } @article{reese, - title = {Georgia in {Anglo-Spanish} Diplomacy, 1736--1739}, - author = {Reese, Trevor R.}, - series = {3}, - volume = {15}, - pages = {168--190}, - journaltitle = {William and Mary Quarterly}, - date = {1958}, - langid = {english}, - langidopts = {variant=american}, - annotation = {An \texttt{article} entry with a \texttt{series} and a \texttt{volume} field. Note the format of the series. If the value of the \texttt{series} field is an integer, this number is printed as an ordinal and the string \enquote*{series} is appended automatically}, + title = {Georgia in {Anglo-Spanish} Diplomacy, 1736--1739}, + author = {Reese, Trevor R.}, + series = {3}, + volume = {15}, + pages = {168--190}, + journaltitle = {William and Mary Quarterly}, + date = {1958}, + langid = {english}, + langidopts = {variant=american}, + annotation = {An \texttt{article} entry with a \texttt{series} and a \texttt + {volume} field. Note the format of the series. If the value of + the \texttt{series} field is an integer, this number is printed + as an ordinal and the string \enquote*{series} is appended + automatically}, } @article{shore, - title = {Twice-Born, Once Conceived}, - author = {Shore, Bradd}, - series = {newseries}, - volume = {93}, - number = {1}, - pages = {9--27}, - journaltitle = {American Anthropologist}, - date = {1991-03}, - subtitle = {Meaning Construction and Cultural Cognition}, - annotation = {An \texttt{article} entry with \texttt{series}, \texttt{volume}, and \texttt{number} fields. Note the format of the \texttt{series} which is a localization key}, + title = {Twice-Born, Once Conceived}, + author = {Shore, Bradd}, + series = {newseries}, + volume = {93}, + number = {1}, + pages = {9--27}, + journaltitle = {American Anthropologist}, + date = {1991-03}, + subtitle = {Meaning Construction and Cultural Cognition}, + annotation = {An \texttt{article} entry with \texttt{series}, \texttt{volume + }, and \texttt{number} fields. Note the format of the \texttt{ + series} which is a localization key}, } @article{sigfridsson, - title = {Comparison of methods for deriving atomic charges from the electrostatic potential and moments}, - author = {Sigfridsson, Emma and Ryde, Ulf}, - volume = {19}, - number = {4}, - pages = {377--395}, - doi = {10.1002/(SICI)1096-987X(199803)19:4<377::AID-JCC1>3.0.CO;2-P}, - journaltitle = {Journal of Computational Chemistry}, - date = {1998}, - langid = {english}, - langidopts = {variant=american}, - indextitle = {Methods for deriving atomic charges}, - annotation = {An \texttt{article} entry with \texttt{volume}, \texttt{number}, and \texttt{doi} fields. Note that the \textsc{doi} is transformed into a clickable link if \texttt{hyperref} support has been enabled}, - abstract = {Four methods for deriving partial atomic charges from the quantum chemical electrostatic potential (CHELP, CHELPG, Merz-Kollman, and RESP) have been compared and critically evaluated. It is shown that charges strongly depend on how and where the potential points are selected. Two alternative methods are suggested to avoid the arbitrariness in the point-selection schemes and van der Waals exclusion radii: CHELP-BOW, which also estimates the charges from the electrostatic potential, but with potential points that are Boltzmann-weighted after their occurrence in actual simulations using the energy function of the program in which the charges will be used, and CHELMO, which estimates the charges directly from the electrostatic multipole moments. Different criteria for the quality of the charges are discussed.}, + title = {Comparison of methods for deriving atomic charges from the + electrostatic potential and moments}, + author = {Sigfridsson, Emma and Ryde, Ulf}, + volume = {19}, + number = {4}, + pages = {377--395}, + doi = {10.1002/(SICI)1096-987X(199803)19:4<377::AID-JCC1>3.0.CO;2-P}, + journaltitle = {Journal of Computational Chemistry}, + date = {1998}, + langid = {english}, + langidopts = {variant=american}, + indextitle = {Methods for deriving atomic charges}, + annotation = {An \texttt{article} entry with \texttt{volume}, \texttt{number + }, and \texttt{doi} fields. Note that the \textsc{doi} is + transformed into a clickable link if \texttt{hyperref} support + has been enabled}, + abstract = {Four methods for deriving partial atomic charges from the + quantum chemical electrostatic potential (CHELP, CHELPG, + Merz-Kollman, and RESP) have been compared and critically + evaluated. It is shown that charges strongly depend on how and + where the potential points are selected. Two alternative methods + are suggested to avoid the arbitrariness in the point-selection + schemes and van der Waals exclusion radii: CHELP-BOW, which also + estimates the charges from the electrostatic potential, but with + potential points that are Boltzmann-weighted after their + occurrence in actual simulations using the energy function of the + program in which the charges will be used, and CHELMO, which + estimates the charges directly from the electrostatic multipole + moments. Different criteria for the quality of the charges are + discussed.}, } @article{spiegelberg, - title = {\mkbibquote{Intention} und \mkbibquote{Intentionalit{\"a}t} in der Scholastik, bei Brentano und Husserl}, - shorttitle = {Intention und Intentionalit{\"a}t}, - author = {Spiegelberg, Herbert}, - volume = {29}, - pages = {189--216}, - journaltitle = {Studia Philosophica}, - date = {1969}, - langid = {german}, - sorttitle = {Intention und Intentionalitat in der Scholastik, bei Brentano und Husserl}, - indexsorttitle = {Intention und Intentionalitat in der Scholastik, bei Brentano und Husserl}, - annotation = {An \texttt{article} entry. Note the \texttt{sorttitle} and \texttt{indexsorttitle} fields and the markup of the quotes in the database file}, + title = {\mkbibquote{Intention} und \mkbibquote{Intentionalit{\"a}t} in der + Scholastik, bei Brentano und Husserl}, + shorttitle = {Intention und Intentionalit{\"a}t}, + author = {Spiegelberg, Herbert}, + volume = {29}, + pages = {189--216}, + journaltitle = {Studia Philosophica}, + date = {1969}, + langid = {german}, + sorttitle = {Intention und Intentionalitat in der Scholastik, bei Brentano + und Husserl}, + indexsorttitle = {Intention und Intentionalitat in der Scholastik, bei + Brentano und Husserl}, + annotation = {An \texttt{article} entry. Note the \texttt{sorttitle} and + \texttt{indexsorttitle} fields and the markup of the quotes in + the database file}, } @article{springer, - title = {Mediaeval Pilgrim Routes from {Scandinavia} to {Rome}}, - shorttitle = {Mediaeval Pilgrim Routes}, - author = {Springer, Otto}, - volume = {12}, - pages = {92--122}, - journaltitle = {Mediaeval Studies}, - date = {1950}, - langid = {english}, - langidopts = {variant=british}, - annotation = {A plain \texttt{article} entry}, + title = {Mediaeval Pilgrim Routes from {Scandinavia} to {Rome}}, + shorttitle = {Mediaeval Pilgrim Routes}, + author = {Springer, Otto}, + volume = {12}, + pages = {92--122}, + journaltitle = {Mediaeval Studies}, + date = {1950}, + langid = {english}, + langidopts = {variant=british}, + annotation = {A plain \texttt{article} entry}, } @article{weinberg, - title = {A Model of Leptons}, - author = {Weinberg, Steven}, - volume = {19}, - pages = {1264--1266}, - journaltitle = {Phys.~Rev.~Lett.}, - date = {1967}, + title = {A Model of Leptons}, + author = {Weinberg, Steven}, + volume = {19}, + pages = {1264--1266}, + journaltitle = {Phys.~Rev.~Lett.}, + date = {1967}, } @string{anch-ie = {Angew.~Chem. Int.~Ed.}} -@string{cup = {Cambridge University Press}} +@string{cup = {Cambridge University Press}} -@string{dtv = {Deutscher Taschenbuch-Verlag}} +@string{dtv = {Deutscher Taschenbuch-Verlag}} -@string{hup = {Harvard University Press}} +@string{hup = {Harvard University Press}} -@string{jams = {J.~Amer. Math. Soc.}} +@string{jams = {J.~Amer. Math. Soc.}} -@string{jchph = {J.~Chem. Phys.}} +@string{jchph = {J.~Chem. Phys.}} -@string{jomch = {J.~Organomet. Chem.}} +@string{jomch = {J.~Organomet. Chem.}} -@string{pup = {Princeton University Press}} +@string{pup = {Princeton University Press}} @incollection{westfahl:space, - title = {The True Frontier}, - author = {Westfahl, Gary}, - pages = {55--65}, - subtitle = {Confronting and Avoiding the Realities of Space in {American} Science Fiction Films}, - crossref = {westfahl:frontier}, - langid = {english}, - langidopts = {variant=american}, - indextitle = {True Frontier, The}, - annotation = {A cross-referenced article from a \texttt{collection}. This is an \texttt{incollection} entry with a \texttt{crossref} field. Note the \texttt{subtitle} and \texttt{indextitle} fields}, + title = {The True Frontier}, + author = {Westfahl, Gary}, + pages = {55--65}, + subtitle = {Confronting and Avoiding the Realities of Space in {American} + Science Fiction Films}, + crossref = {westfahl:frontier}, + langid = {english}, + langidopts = {variant=american}, + indextitle = {True Frontier, The}, + annotation = {A cross-referenced article from a \texttt{collection}. This is + an \texttt{incollection} entry with a \texttt{crossref} field. + Note the \texttt{subtitle} and \texttt{indextitle} fields}, } @article{yoon, - title = {Palladium pincer complexes with reduced bond angle strain: efficient catalysts for the {Heck} reaction}, - author = {Yoon, Myeong S. and Ryu, Dowook and Kim, Jeongryul and Ahn, Kyo Han}, - volume = {25}, - number = {10}, - pages = {2409--2411}, - journaltitle = {Organometallics}, - date = {2006}, - indextitle = {Palladium pincer complexes}, + title = {Palladium pincer complexes with reduced bond angle strain: + efficient catalysts for the {Heck} reaction}, + author = {Yoon, Myeong S. and Ryu, Dowook and Kim, Jeongryul and Ahn, Kyo + Han}, + volume = {25}, + number = {10}, + pages = {2409--2411}, + journaltitle = {Organometallics}, + date = {2006}, + indextitle = {Palladium pincer complexes}, } -- cgit v1.2.3 From dc5ded8160177864963a31476c2a7afe8ca8e834 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Wed, 19 Feb 2025 21:13:50 +0100 Subject: resource opener in config + implement config field `pdf_opener` for setting app to open PDFs/Epubs + implement config field `url_opener` for setting app to open URLs/DOIs + function to select fallback if no field is provided in config --- README.md | 2 ++ example-config.toml | 2 ++ src/app.rs | 55 +++++++++++++++++++++++++++++++++++------------------ src/bibiman.rs | 10 ++++++---- src/config.rs | 26 +++++++++++++++++++++---- src/main.rs | 2 +- 6 files changed, 69 insertions(+), 28 deletions(-) (limited to 'src/config.rs') diff --git a/README.md b/README.md index 725cd8e..47d57de 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ POSITIONAL ARGS: FLAGS: -h, --help Show this help and exit -v, --version Show the version and exit + -c, --config-file Path to config file for current session + Takes precedence over standard config file --light-terminal Enable color mode for light terminal background ``` diff --git a/example-config.toml b/example-config.toml index 989350e..9d110ed 100644 --- a/example-config.toml +++ b/example-config.toml @@ -1,3 +1,5 @@ [general] bibfiles = ["./tests/biblatex-test.bib"] # files and dirs are possible editor = "vim" # arguments are possible: "vim -y" +# pdf_opener = "xdg-open" +# url_opener = "xdg-open" diff --git a/src/app.rs b/src/app.rs index b49e883..e03f8d7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -275,7 +275,7 @@ impl App { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup(); } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind { - self.bibiman.open_connected_res()?; + self.bibiman.open_connected_res(cfg)?; } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind { self.bibiman.append_entry_to_file(args)? @@ -362,16 +362,20 @@ impl App { } } -pub fn open_connected_file(file: &OsStr) -> Result<()> { +pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard // TODO: make custom opener command possible through config - let cmd = { - match std::env::consts::OS { - "linux" => String::from("xdg-open"), - "macos" => String::from("open"), - "windows" => String::from("start"), - _ => panic!("Couldn't detect OS for setting correct opener"), - } + // let cmd = { + // match std::env::consts::OS { + // "linux" => String::from("xdg-open"), + // "macos" => String::from("open"), + // "windows" => String::from("start"), + // _ => panic!("Couldn't detect OS for setting correct opener"), + // } + // }; + let cmd = match &cfg.general.pdf_opener { + Some(c) => c, + None => &select_opener(), }; // If necessary, replace ~ with /home dir @@ -381,7 +385,7 @@ pub fn open_connected_file(file: &OsStr) -> Result<()> { // Pass filepath as argument, pipe stdout and stderr to /dev/null // to keep the TUI clean (where is it piped on Windows???) - let _ = Command::new(&cmd) + let _ = Command::new(cmd) .arg(file) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -391,21 +395,25 @@ pub fn open_connected_file(file: &OsStr) -> Result<()> { Ok(()) } -pub fn open_connected_link(link: &str) -> Result<()> { +pub fn open_connected_link(cfg: &BibiConfig, link: &str) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard // TODO: make custom opener command possible through config - let cmd = { - match std::env::consts::OS { - "linux" => String::from("xdg-open"), - "macos" => String::from("open"), - "windows" => String::from("start"), - _ => panic!("Couldn't detect OS for setting correct opener"), - } + // let cmd = { + // match std::env::consts::OS { + // "linux" => String::from("xdg-open"), + // "macos" => String::from("open"), + // "windows" => String::from("start"), + // _ => panic!("Couldn't detect OS for setting correct opener"), + // } + // }; + let cmd = match &cfg.general.url_opener { + Some(c) => c, + None => &select_opener(), }; // Pass filepath as argument, pipe stdout and stderr to /dev/null // to keep the TUI clean (where is it piped on Windows???) - let _ = Command::new(&cmd) + let _ = Command::new(cmd) .arg(link) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -437,6 +445,15 @@ fn expand_home(path: &PathBuf) -> PathBuf { } } +fn select_opener() -> String { + match std::env::consts::OS { + "linux" => String::from("xdg-open"), + "macos" => String::from("open"), + "windows" => String::from("start"), + _ => panic!("Couldn't detect OS for setting correct opener"), + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/bibiman.rs b/src/bibiman.rs index 20cdfc6..ecddc4c 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -85,7 +85,9 @@ impl Bibiman { // Constructs a new instance of [`App`]. pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result { let mut main_bibfiles: Vec = args.pos_args.clone(); - main_bibfiles.append(&mut cfg.general.bibfiles); + if cfg.general.bibfiles.is_some() { + main_bibfiles.append(cfg.general.bibfiles.as_mut().unwrap()) + }; let main_bibfiles = cliargs::parse_files(main_bibfiles); let main_biblio = BibiSetup::new(&main_bibfiles); let tag_list = TagList::new(main_biblio.keyword_list.clone()); @@ -542,7 +544,7 @@ impl Bibiman { Ok(()) } - pub fn open_connected_res(&mut self) -> Result<()> { + pub fn open_connected_res(&mut self, cfg: &BibiConfig) -> Result<()> { // Index of selected entry let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); @@ -553,10 +555,10 @@ impl Bibiman { if self.popup_area.popup_list[popup_idx].contains("Weblink") { let object = self.entry_table.entry_table_items[entry_idx].doi_url(); let url = app::prepare_weblink(object); - app::open_connected_link(&url)?; + app::open_connected_link(cfg, &url)?; } else if self.popup_area.popup_list[popup_idx].contains("File") { let object = self.entry_table.entry_table_items[entry_idx].filepath(); - app::open_connected_file(object)?; + app::open_connected_file(cfg, object)?; } else { eprintln!("Unable to find ressource to open"); }; diff --git a/src/config.rs b/src/config.rs index a80cc13..abb610b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,33 +17,51 @@ use std::path::PathBuf; +use color_eyre::eyre::Result; use config::{ConfigError, FileFormat}; use serde::Deserialize; use crate::cliargs::CLIArgs; +/// Main struct of the config file. Contains substructs/headings in toml #[derive(Debug, Clone, Deserialize)] pub struct BibiConfig { pub general: General, } +/// Substruct [general] in config.toml #[derive(Debug, Clone, Deserialize)] pub struct General { - pub bibfiles: Vec, + pub bibfiles: Option>, pub editor: Option, + pub pdf_opener: Option, + pub url_opener: Option, } impl BibiConfig { - pub fn default(args: &CLIArgs) -> Self { + pub fn default() -> Self { Self { general: General { - bibfiles: args.pos_args.clone(), + bibfiles: None, editor: None, + pdf_opener: None, + url_opener: None, }, } } - pub fn new(args: &CLIArgs) -> Result { + pub fn new(args: &CLIArgs) -> Result { + // let mut cfg = config::Config::builder(); + // cfg = cfg.add_source( + // config::File::from(args.cfg_path.clone()) + // .format(FileFormat::Toml) + // .required(false), + // ); + // cfg.build()?.try_deserialize() + Ok(Self::parse_cfg_file(args)?) + } + + fn parse_cfg_file(args: &CLIArgs) -> Result { let mut cfg = config::Config::builder(); cfg = cfg.add_source( config::File::from(args.cfg_path.clone()) diff --git a/src/main.rs b/src/main.rs index 37bead0..d53aaab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ async fn main() -> Result<()> { let mut cfg = if parsed_args.cfg_path.is_file() { BibiConfig::new(&parsed_args)? } else { - BibiConfig::default(&parsed_args) + BibiConfig::default() }; init_error_hooks()?; -- cgit v1.2.3 From ddb6326c1896b82b759d930fb08ea46a820e275a Mon Sep 17 00:00:00 2001 From: lukeflo Date: Fri, 21 Feb 2025 20:17:27 +0100 Subject: parse config file + set default values for config fields + merge fields named in the config file with default values + values from file take precedence over default values + to accomplish this, all config values are wrapped in `Option` --- Cargo.lock | 5 +++ Cargo.toml | 2 +- src/app.rs | 12 ++----- src/bibiman.rs | 6 ++-- src/config.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 12 ++++--- 6 files changed, 122 insertions(+), 26 deletions(-) (limited to 'src/config.rs') diff --git a/Cargo.lock b/Cargo.lock index f8a062e..342fba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block2" @@ -263,6 +266,7 @@ dependencies = [ "itoa", "rustversion", "ryu", + "serde", "static_assertions", ] @@ -1366,6 +1370,7 @@ dependencies = [ "itertools", "lru", "paste", + "serde", "strum", "unicode-segmentation", "unicode-truncate", diff --git a/Cargo.toml b/Cargo.toml index 971df49..c2f42a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ rand = "0.8" itertools = "0.13.0" lexopt = "0.3.0" nucleo-matcher = "0.3.1" -ratatui = { version = "0.29.0", features = ["unstable-rendered-line-info"]} +ratatui = { version = "0.29.0", features = ["unstable-rendered-line-info", "serde"]} signal-hook = "0.3.17" tokio = { version = "1.39.3", features = ["full"] } tokio-util = "0.7.12" diff --git a/src/app.rs b/src/app.rs index 55f49de..b3778af 100644 --- a/src/app.rs +++ b/src/app.rs @@ -364,11 +364,7 @@ impl App { pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard - let cmd = match &cfg.general.pdf_opener { - Some(c) => c, - None => &select_opener(), - }; - + let cmd = cfg.general.as_ref().unwrap().pdf_opener.as_ref().unwrap(); // If necessary, replace ~ with /home dir let file = PathBuf::from(file); @@ -388,11 +384,7 @@ pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { pub fn open_connected_link(cfg: &BibiConfig, link: &str) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard - let cmd = match &cfg.general.url_opener { - Some(c) => c, - None => &select_opener(), - }; - + let cmd = cfg.general.as_ref().unwrap().url_opener.as_ref().unwrap(); // Pass filepath as argument, pipe stdout and stderr to /dev/null // to keep the TUI clean (where is it piped on Windows???) let _ = Command::new(cmd) diff --git a/src/bibiman.rs b/src/bibiman.rs index ecddc4c..b68b0fa 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -85,8 +85,8 @@ impl Bibiman { // Constructs a new instance of [`App`]. pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result { let mut main_bibfiles: Vec = args.pos_args.clone(); - if cfg.general.bibfiles.is_some() { - main_bibfiles.append(cfg.general.bibfiles.as_mut().unwrap()) + if cfg.general.as_ref().unwrap().bibfiles.is_some() { + main_bibfiles.append(cfg.general.as_mut().unwrap().bibfiles.as_mut().unwrap()) }; let main_bibfiles = cliargs::parse_files(main_bibfiles); let main_biblio = BibiSetup::new(&main_bibfiles); @@ -381,7 +381,7 @@ impl Bibiman { tui.exit()?; // Use VISUAL or EDITOR. Set "vi" as last fallback let mut cmd: Command = EditorBuilder::new() - .source(cfg.general.editor.clone()) + .source(cfg.general.as_ref().unwrap().editor.clone()) .environment() .source(Some("vi")) .build() diff --git a/src/config.rs b/src/config.rs index abb610b..22873dd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use config::{ConfigError, FileFormat}; +use ratatui::style::Color; use serde::Deserialize; use crate::cliargs::CLIArgs; @@ -26,7 +27,8 @@ use crate::cliargs::CLIArgs; /// Main struct of the config file. Contains substructs/headings in toml #[derive(Debug, Clone, Deserialize)] pub struct BibiConfig { - pub general: General, + pub general: Option, + pub colors: Option, } /// Substruct [general] in config.toml @@ -38,17 +40,103 @@ pub struct General { pub url_opener: Option, } -impl BibiConfig { - pub fn default() -> Self { +/// Substruct [colors] in config.toml +#[derive(Debug, Clone, Deserialize)] +pub struct Colors { + pub main_text_color: Option, + pub highlight_text_color: Option, + pub entry_color: Option, + pub keyword_color: Option, + pub info_color: Option, + pub confirm_color: Option, + pub warn_color: Option, + pub bar_bg_color: Option, + pub popup_bg_color: Option, + pub selected_row_bg_color: Option, +} + +impl Default for BibiConfig { + fn default() -> Self { Self { - general: General { + general: Some(General { bibfiles: None, editor: None, - pdf_opener: None, - url_opener: None, - }, + pdf_opener: Some(select_opener()), + url_opener: Some(select_opener()), + }), + colors: Some(Colors { + main_text_color: Some(Color::Indexed(250)), + highlight_text_color: Some(Color::Indexed(254)), + entry_color: Some(Color::Indexed(36)), + keyword_color: Some(Color::Indexed(101)), + info_color: Some(Color::Indexed(99)), + confirm_color: Some(Color::Indexed(47)), + warn_color: Some(Color::Indexed(124)), + bar_bg_color: Some(Color::Indexed(234)), + popup_bg_color: Some(Color::Indexed(234)), + selected_row_bg_color: Some(Color::Indexed(237)), + }), } } +} + +impl BibiConfig { + pub fn parse_config(&mut self, args: &CLIArgs) -> Result<()> { + if args.cfg_path.is_file() { + let cfg_file = Self::parse_cfg_file(args)?; + + if let Some(general) = cfg_file.general { + if let Some(bibfiles) = general.bibfiles { + self.general.as_mut().unwrap().bibfiles = Some(bibfiles) + } + if let Some(editor) = general.editor { + self.general.as_mut().unwrap().editor = Some(editor) + } + if let Some(pdf_opener) = general.pdf_opener { + self.general.as_mut().unwrap().pdf_opener = Some(pdf_opener) + } + if let Some(url_opener) = general.url_opener { + self.general.as_mut().unwrap().url_opener = Some(url_opener) + } + } + + if let Some(colors) = cfg_file.colors { + if let Some(main_text_color) = colors.main_text_color { + self.colors.as_mut().unwrap().main_text_color = Some(main_text_color) + } + if let Some(highlight_text_color) = colors.highlight_text_color { + self.colors.as_mut().unwrap().highlight_text_color = Some(highlight_text_color) + } + if let Some(entry_color) = colors.entry_color { + self.colors.as_mut().unwrap().entry_color = Some(entry_color) + } + if let Some(keyword_color) = colors.keyword_color { + self.colors.as_mut().unwrap().keyword_color = Some(keyword_color) + } + if let Some(info_color) = colors.info_color { + self.colors.as_mut().unwrap().info_color = Some(info_color) + } + if let Some(confirm_color) = colors.confirm_color { + self.colors.as_mut().unwrap().confirm_color = Some(confirm_color) + } + if let Some(warn_color) = colors.warn_color { + self.colors.as_mut().unwrap().warn_color = Some(warn_color) + } + if let Some(bar_bg_color) = colors.bar_bg_color { + self.colors.as_mut().unwrap().bar_bg_color = Some(bar_bg_color) + } + if let Some(popup_bg_color) = colors.popup_bg_color { + self.colors.as_mut().unwrap().popup_bg_color = Some(popup_bg_color) + } + if let Some(selected_row_bg_color) = colors.selected_row_bg_color { + self.colors.as_mut().unwrap().selected_row_bg_color = + Some(selected_row_bg_color) + } + } + } + + Ok(()) + } pub fn new(args: &CLIArgs) -> Result { // let mut cfg = config::Config::builder(); @@ -71,3 +159,12 @@ impl BibiConfig { cfg.build()?.try_deserialize() } } + +fn select_opener() -> String { + match std::env::consts::OS { + "linux" => String::from("xdg-open"), + "macos" => String::from("open"), + "windows" => String::from("start"), + _ => panic!("Couldn't detect OS for setting correct opener"), + } +} diff --git a/src/main.rs b/src/main.rs index d53aaab..8ec3b77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,11 +45,13 @@ async fn main() -> Result<()> { std::process::exit(0); } - let mut cfg = if parsed_args.cfg_path.is_file() { - BibiConfig::new(&parsed_args)? - } else { - BibiConfig::default() - }; + // let mut cfg = if parsed_args.cfg_path.is_file() { + // BibiConfig::new(&parsed_args)? + // } else { + // BibiConfig::default() + // }; + let mut cfg = BibiConfig::default(); + cfg.parse_config(&parsed_args)?; init_error_hooks()?; -- cgit v1.2.3 From c34412d9e3725bed48af925646110f2ca34b1bd4 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Fri, 21 Feb 2025 21:55:17 +0100 Subject: implement working config file construct, error handling should be improved --- README.md | 58 ++++++++---- example-config.toml | 12 +++ src/app.rs | 32 ++----- src/bibiman.rs | 6 +- src/cliargs.rs | 11 +-- src/config.rs | 26 ++--- src/main.rs | 13 +-- src/tui.rs | 6 +- src/tui/popup.rs | 33 ++++--- src/tui/ui.rs | 266 ++++++++++++++++++++++++++++------------------------ 10 files changed, 255 insertions(+), 208 deletions(-) (limited to 'src/config.rs') diff --git a/README.md b/README.md index d62c847..41ebdfc 100644 --- a/README.md +++ b/README.md @@ -87,16 +87,6 @@ bibman tests/multi-files/ bibiman tests/biblatex-test.bib tests/multi-files/ ``` -Furthermore, in issue #3 it was asked to enable a color customization to run -`bibiman` also in terminal setups with light background. Thus, now it is -possible to enable a light terminal mode with the `--light-terminal` flag. Full -color customization is not possible right now, but maybe will be added with the -implementation of a config file. - -Here is how the light terminal scheme looks: - -![bibiman-light-theme.png](https://codeberg.org/attachments/7fe9d58d-7e90-4043-9402-5e4664c4e22a) - ## Configuration ### Location of Config File @@ -119,13 +109,14 @@ which takes precedence over the standard one for the active session: bibiman --config-file="/path/to/temporary/config" ``` -### Values +### General Configuration -At the moment, the following values can be set through the config file: +The following general values can be set through the config file: ```toml [general] # Default files/dirs which are loaded on startup +# Use absolute paths (~ for HOME works). Otherwise, loading might not work. bibfiles = [ "/path/to/bibfile", "path/to/dir/with/bibfiles" ] # Default editor to use when editing files. Arguments are possible editor = "vim" # with args: "vim -y" @@ -135,11 +126,44 @@ pdf_opener = "xdg-open" url_opener = "xdg-open" ``` -No value *needs* to be set. For every one exists a default value. Only exception -is the `bibfiles` key. If no file or dir is set, you *have to* add a path via -CLI interface. If the `bibfiles` value is set *and* a further path (or multiple) -is provided through the CLI call, the entries of all those files will be opened -in the started `bibiman` session. +If no file or dir is set as `bibfiles` value, you *have to* add a path via CLI +interface. If the `bibfiles` value is set *and* a further path (or multiple) is +provided through the CLI call, the entries of all those files will be opened in +the started `bibiman` session. + +### Color Configuration + +Furthermore, it is now possible to customize the colors. The following values +can be changed: + +```toml +[colors] +# Default values for dark-themed terminal +main_text_color = "250" +highlight_text_color = "254" +entry_color = "36" +keyword_color = "101" +info_color = "99" +confirm_color = "47" +warn_color = "124" +bar_bg_color = "234" +popup_bg_color = "234" +selected_row_bg_color = "237" +``` + +Colors can be set through three different methods: +[ANSI color names](https://docs.rs/ratatui/latest/ratatui/style/enum.Color.html), +[256-color indices](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) and +[HEX codes](https://www.w3schools.com/colors/colors_hexadecimal.asp). For +example, the following definitions are all valid: + +```toml +selected_row_bg_color = "darkgray" # ANSI color name (light_black or bright_black would also work) +selected_row_bg_color = "237" # 256-color index +selected_row_bg_color = "#3a3a3a" # HEX code +``` + +To run `bibiman` with some default values for a light-colored terminal use the `--light-terminal` flag. ## Features diff --git a/example-config.toml b/example-config.toml index 50c0467..d6d2aa8 100644 --- a/example-config.toml +++ b/example-config.toml @@ -3,3 +3,15 @@ bibfiles = ["./tests/biblatex-test.bib"] # multiple files and dirs are possible editor = "vim" # arguments are possible: "vim -y" # pdf_opener = "xdg-open" # url_opener = "xdg-open" + +# [colors] +# main_text_color = "250" +# highlight_text_color = "254" +# entry_color = "36" +# keyword_color = "101" +# info_color = "99" +# confirm_color = "47" +# warn_color = "124" +# bar_bg_color = "234" +# popup_bg_color = "234" +# selected_row_bg_color = "237" diff --git a/src/app.rs b/src/app.rs index b3778af..7869304 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,7 +24,6 @@ use crate::tui::commands::InputCmdAction; use crate::tui::popup::PopupKind; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; -use core::panic; use std::ffi::OsStr; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -60,14 +59,14 @@ impl App { }) } - pub async fn run(&mut self, args: &mut CLIArgs, cfg: &BibiConfig) -> Result<()> { + pub async fn run(&mut self, cfg: &BibiConfig) -> Result<()> { let mut tui = tui::Tui::new()?; tui.enter()?; // Start the main loop. while self.running { // Render the user interface. - tui.draw(self, args)?; + tui.draw(self, cfg)?; // Handle events. match tui.next().await? { Event::Tick => self.tick(), @@ -86,10 +85,10 @@ impl App { } else { CmdAction::from(key_event) }; - self.run_command(command, args, cfg, &mut tui)? + self.run_command(command, cfg, &mut tui)? } Event::Mouse(mouse_event) => { - self.run_command(CmdAction::from(mouse_event), args, cfg, &mut tui)? + self.run_command(CmdAction::from(mouse_event), cfg, &mut tui)? } Event::Resize(_, _) => {} @@ -111,13 +110,7 @@ impl App { self.running = false; } - pub fn run_command( - &mut self, - cmd: CmdAction, - args: &mut CLIArgs, - cfg: &BibiConfig, - tui: &mut Tui, - ) -> Result<()> { + pub fn run_command(&mut self, cmd: CmdAction, cfg: &BibiConfig, tui: &mut Tui) -> Result<()> { match cmd { CmdAction::Input(cmd) => match cmd { InputCmdAction::Nothing => {} @@ -149,7 +142,7 @@ impl App { || doi.starts_with("http://doi.org") || doi.starts_with("http://dx.doi.org") { - self.bibiman.handle_new_entry_submission(args, doi); + self.bibiman.handle_new_entry_submission(doi); } else { self.bibiman.popup_area.popup_message( "No valid DOI pattern: ", @@ -278,7 +271,7 @@ impl App { self.bibiman.open_connected_res(cfg)?; } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind { - self.bibiman.append_entry_to_file(args)? + self.bibiman.append_entry_to_file()? } } } @@ -310,7 +303,7 @@ impl App { } CmdAction::EditFile => { if let CurrentArea::EntryArea = self.bibiman.current_area { - self.bibiman.run_editor(cfg, args, tui)?; + self.bibiman.run_editor(cfg, tui)?; } } CmdAction::Open => { @@ -419,15 +412,6 @@ pub fn expand_home(path: &PathBuf) -> PathBuf { } } -fn select_opener() -> String { - match std::env::consts::OS { - "linux" => String::from("xdg-open"), - "macos" => String::from("open"), - "windows" => String::from("start"), - _ => panic!("Couldn't detect OS for setting correct opener"), - } -} - #[cfg(test)] mod test { use super::*; diff --git a/src/bibiman.rs b/src/bibiman.rs index b68b0fa..b8ef2c6 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -330,7 +330,7 @@ impl Bibiman { self.entry_table.entry_table_state.select(Some(idx_count)); } - pub fn run_editor(&mut self, cfg: &BibiConfig, args: &CLIArgs, tui: &mut Tui) -> Result<()> { + pub fn run_editor(&mut self, cfg: &BibiConfig, tui: &mut Tui) -> Result<()> { // get filecontent and citekey for calculating line number let citekey: &str = &self.entry_table.entry_table_items [self.entry_table.entry_table_state.selected().unwrap()] @@ -418,7 +418,7 @@ impl Bibiman { ///the new entry via `append_to_file()` function. If not, show error popup /// ///The method needs two arguments: the CLIArgs struct and the `str` containing the DOI - pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &str) { + pub fn handle_new_entry_submission(&mut self, doi_string: &str) { let doi_string = if doi_string.starts_with("10.") { "https://doi.org/".to_string() + doi_string } else { @@ -466,7 +466,7 @@ impl Bibiman { self.popup_area.popup_selection(items); } - pub fn append_entry_to_file(&mut self, args: &mut CLIArgs) -> Result<()> { + pub fn append_entry_to_file(&mut self) -> Result<()> { // Index of selected popup field let popup_idx = self.popup_area.popup_state.selected().unwrap(); diff --git a/src/cliargs.rs b/src/cliargs.rs index bb3c8d1..3c302f4 100644 --- a/src/cliargs.rs +++ b/src/cliargs.rs @@ -24,7 +24,6 @@ use std::path::PathBuf; use walkdir::WalkDir; use crate::app; -use crate::tui::colors::AppColors; // struct for CLIArgs #[derive(Debug, Default, Clone)] @@ -32,11 +31,8 @@ pub struct CLIArgs { pub helparg: bool, pub versionarg: bool, pub pos_args: Vec, - // pub files: Vec, pub cfg_path: PathBuf, - // INFO: AppColors struct later should be moved to config/app struct - // when config file is implemented - pub colors: AppColors, + pub light_theme: bool, } impl CLIArgs { @@ -56,10 +52,7 @@ impl CLIArgs { Short('h') | Long("help") => args.helparg = true, Short('v') | Long("version") => args.versionarg = true, Short('c') | Long("config-file") => args.cfg_path = parser.value()?.parse()?, - Long("light-terminal") => { - args.colors.light_colors(); - args.colors.toggle_color_scheme() - } + Long("light-terminal") => args.light_theme = true, // Value(pos_arg) => parse_files(&mut args, pos_arg), Value(pos_arg) => args.pos_args.push(pos_arg.into()), _ => return Err(arg.unexpected()), diff --git a/src/config.rs b/src/config.rs index 22873dd..faba5d8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -72,7 +72,7 @@ impl Default for BibiConfig { info_color: Some(Color::Indexed(99)), confirm_color: Some(Color::Indexed(47)), warn_color: Some(Color::Indexed(124)), - bar_bg_color: Some(Color::Indexed(234)), + bar_bg_color: Some(Color::Indexed(235)), popup_bg_color: Some(Color::Indexed(234)), selected_row_bg_color: Some(Color::Indexed(237)), }), @@ -138,17 +138,6 @@ impl BibiConfig { Ok(()) } - pub fn new(args: &CLIArgs) -> Result { - // let mut cfg = config::Config::builder(); - // cfg = cfg.add_source( - // config::File::from(args.cfg_path.clone()) - // .format(FileFormat::Toml) - // .required(false), - // ); - // cfg.build()?.try_deserialize() - Ok(Self::parse_cfg_file(args)?) - } - fn parse_cfg_file(args: &CLIArgs) -> Result { let mut cfg = config::Config::builder(); cfg = cfg.add_source( @@ -158,6 +147,19 @@ impl BibiConfig { ); cfg.build()?.try_deserialize() } + + /// Activates the default color scheme for light background terminals + pub fn light_colors(&mut self) { + self.colors.as_mut().unwrap().main_text_color = Some(Color::Indexed(235)); + self.colors.as_mut().unwrap().highlight_text_color = Some(Color::Indexed(232)); + self.colors.as_mut().unwrap().entry_color = Some(Color::Indexed(23)); + self.colors.as_mut().unwrap().keyword_color = Some(Color::Indexed(58)); + self.colors.as_mut().unwrap().info_color = Some(Color::Indexed(57)); + self.colors.as_mut().unwrap().bar_bg_color = Some(Color::Indexed(144)); + self.colors.as_mut().unwrap().popup_bg_color = Some(Color::Indexed(187)); + self.colors.as_mut().unwrap().confirm_color = Some(Color::Indexed(22)); + self.colors.as_mut().unwrap().selected_row_bg_color = Some(Color::Indexed(107)); + } } fn select_opener() -> String { diff --git a/src/main.rs b/src/main.rs index 8ec3b77..3325f80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,12 +45,13 @@ async fn main() -> Result<()> { std::process::exit(0); } - // let mut cfg = if parsed_args.cfg_path.is_file() { - // BibiConfig::new(&parsed_args)? - // } else { - // BibiConfig::default() - // }; + // Build default config let mut cfg = BibiConfig::default(); + + if parsed_args.light_theme { + cfg.light_colors(); + } + // Merge values from config file if present cfg.parse_config(&parsed_args)?; init_error_hooks()?; @@ -58,6 +59,6 @@ async fn main() -> Result<()> { // Create an application. let mut app = App::new(&mut parsed_args, &mut cfg)?; - app.run(&mut parsed_args, &cfg).await?; + app.run(&cfg).await?; Ok(()) } diff --git a/src/tui.rs b/src/tui.rs index b39b5c4..1e3061f 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -20,7 +20,7 @@ pub mod commands; pub mod popup; pub mod ui; -use crate::{cliargs::CLIArgs, App}; +use crate::{config::BibiConfig, App}; use crossterm::{ cursor, event::{ @@ -195,11 +195,11 @@ impl Tui { // // [`Draw`]: ratatui::Terminal::draw // [`rendering`]: crate::ui::render - pub fn draw(&mut self, app: &mut App, args: &CLIArgs) -> Result<()> { + pub fn draw(&mut self, app: &mut App, cfg: &BibiConfig) -> Result<()> { // self.terminal.draw(|frame| ui::render(app, frame))?; self.terminal // .draw(|frame| frame.render_widget(app, frame.area()))?; - .draw(|frame| ui::render_ui(app, args, frame))?; + .draw(|frame| ui::render_ui(app, cfg, frame))?; Ok(()) } diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 4ef9fc3..6a2e8ff 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -16,12 +16,12 @@ ///// use ratatui::{ - style::{Color, Stylize}, + style::Stylize, text::{Line, Span, Text}, widgets::ListState, }; -use crate::cliargs::CLIArgs; +use crate::config::BibiConfig; #[derive(Debug)] pub enum PopupKind { @@ -47,7 +47,7 @@ pub struct PopupArea { } impl PopupArea { - pub fn popup_help<'a>(args: &CLIArgs) -> Text<'a> { + pub fn popup_help<'a>(cfg: &BibiConfig) -> Text<'a> { let help = [ ("General", "first"), ("TAB: ", "Toggle areas (Entries, Keywords)"), @@ -89,22 +89,31 @@ impl PopupArea { for (keys, help) in help { if help == "first" { helptext.push(Line::from( - Span::raw(keys) - .bold() - .fg(Color::Indexed(args.colors.main_text_color)), + Span::raw(keys).bold().fg(cfg + .colors + .as_ref() + .unwrap() + .main_text_color + .unwrap()), )) } else if help == "sub" { helptext.push(Line::from("")); helptext.push(Line::from( - Span::raw(keys) - .bold() - .fg(Color::Indexed(args.colors.main_text_color)), + Span::raw(keys).bold().fg(cfg + .colors + .as_ref() + .unwrap() + .main_text_color + .unwrap()), )) } else { helptext.push(Line::from(vec![ - Span::raw(keys) - .bold() - .fg(Color::Indexed(args.colors.keyword_color)), + Span::raw(keys).bold().fg(cfg + .colors + .as_ref() + .unwrap() + .main_text_color + .unwrap()), Span::raw(help), ])) } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 52d2d9a..5fbe283 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -17,11 +17,11 @@ use std::path::PathBuf; -use super::colors::AppColorScheme; use super::popup::PopupArea; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{CurrentArea, FormerArea}; use crate::cliargs::CLIArgs; +use crate::config::BibiConfig; use crate::tui::popup::PopupKind; use crate::App; use ratatui::layout::{Direction, Position}; @@ -52,8 +52,8 @@ pub fn color_list( highlight: u8, max_diff: i32, ) -> Color { - match args.colors.color_scheme { - AppColorScheme::Dark => { + match args.light_theme { + false => { if list_item == sel_item { Color::Indexed(highlight) } else if (list_item - sel_item) > max_diff @@ -68,7 +68,7 @@ pub fn color_list( Color::Indexed(highlight - (list_item - sel_item) as u8) } } - AppColorScheme::Light => { + true => { if list_item == sel_item { Color::Indexed(highlight) } else if (list_item - sel_item) > max_diff @@ -103,7 +103,7 @@ fn count_files(files: &[PathBuf]) -> u16 { count } -pub fn render_ui(app: &mut App, args: &CLIArgs, frame: &mut Frame) { +pub fn render_ui(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { let [header_area, main_area, footer_area] = Layout::new( Direction::Vertical, [ @@ -128,20 +128,20 @@ pub fn render_ui(app: &mut App, args: &CLIArgs, frame: &mut Frame) { let [tag_area, info_area] = Layout::horizontal([Constraint::Max(25), Constraint::Min(35)]).areas(item_area); - render_header(args, frame, header_area); + render_header(cfg, frame, header_area); if let CurrentArea::SearchArea = app.bibiman.current_area { - render_footer(app, args, frame, footer_area); + render_footer(app, cfg, frame, footer_area); } - render_entrytable(app, args, frame, entry_area); - render_selected_item(app, args, frame, info_area); - render_taglist(app, args, frame, tag_area); - render_file_info(app, args, frame, entry_info_area); + render_entrytable(app, cfg, frame, entry_area); + render_selected_item(app, cfg, frame, info_area); + render_taglist(app, cfg, frame, tag_area); + render_file_info(app, cfg, frame, entry_info_area); if app.bibiman.popup_area.is_popup { - render_popup(app, args, frame); + render_popup(app, cfg, frame); } } -pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { +pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { match app.bibiman.popup_area.popup_kind { Some(PopupKind::Help) => { let block = Block::bordered() @@ -150,13 +150,13 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.popup_bg_color)), + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(Color::Indexed(args.colors.entry_color))); + .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap())); - let text: Text = PopupArea::popup_help(args); + let text: Text = PopupArea::popup_help(cfg); // Calculate max scroll position depending on hight of terminal window // Needed length is number of text lines plus two for borders at bottom and top @@ -191,23 +191,23 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.popup_bg_color)), + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(Color::Indexed(args.colors.entry_color))); + .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap())); // Prepare the input fields let content = vec![Line::from(vec![ Span::styled( "DOI: ", - Style::new().fg(Color::Indexed(args.colors.entry_color)), + Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()), ), Span::raw(app.input.value().to_string().clone()), ])]; let paragraph = Paragraph::new(content) .block(block.clone()) - .style(Style::new().fg(Color::Indexed(args.colors.main_text_color))) + .style(Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap())) .wrap(Wrap { trim: false }); let doi_lines = paragraph.line_count(area.width / 2); @@ -228,18 +228,18 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .title_top( " Message " .bold() - .fg(Color::Indexed(args.colors.confirm_color)), + .fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap()), ) - .border_style(Style::new().fg(Color::Indexed(args.colors.confirm_color))) + .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap())) .style( Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.popup_bg_color)), + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), ); let content = Paragraph::new(app.bibiman.popup_area.popup_message.clone()) .block(block) - .style(Style::new().fg(Color::Indexed(args.colors.confirm_color))); + .style(Style::new().fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap())); // Calculate popup size. Width is number of string chars plus 2 for border let popup_area = popup_area( @@ -259,18 +259,18 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .title_top( " Warning " .bold() - .fg(Color::Indexed(args.colors.warn_color)), + .fg(cfg.colors.as_ref().unwrap().warn_color.unwrap()), ) .border_style(Style::new().fg(Color::Red)) .style( Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.popup_bg_color)), + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), ); let content = Paragraph::new(app.bibiman.popup_area.popup_message.clone()) .block(block) - .style(Style::new().fg(Color::Indexed(args.colors.warn_color))); + .style(Style::new().fg(cfg.colors.as_ref().unwrap().warn_color.unwrap())); // Calculate popup size. Width is number of string chars plus 2 for border let popup_area = popup_area( @@ -306,15 +306,15 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.popup_bg_color)), + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(Color::Indexed(args.colors.keyword_color))); + .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap())); let list = List::new(list_items).block(block).highlight_style( Style::new() - // .fg(Color::Indexed(args.colors.entry_color)) + // .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED), ); @@ -330,15 +330,15 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { } } -pub fn render_header(args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_header(cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let main_header = Paragraph::new("BIBIMAN – BibLaTeX manager TUI") .bold() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .centered(); frame.render_widget(main_header, rect) } -pub fn render_footer(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_footer(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let search_title = { match app.bibiman.former_area { Some(FormerArea::EntryArea) => "Search Entries: ".to_string(), @@ -349,29 +349,36 @@ pub fn render_footer(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rec let title_lenght: u16 = search_title.chars().count() as u16; - let block = Block::new() - .padding(Padding::horizontal(1)) - .bg(Color::Indexed(args.colors.bar_bg_color)); + let block = Block::new().padding(Padding::horizontal(1)).bg(cfg + .colors + .as_ref() + .unwrap() + .bar_bg_color + .unwrap()); let search_string = Paragraph::new(Line::from(vec![ Span::styled( search_title, if let Some(FormerArea::EntryArea) = app.bibiman.former_area { Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD) } else if let Some(FormerArea::TagArea) = app.bibiman.former_area { Style::new() - .fg(Color::Indexed(args.colors.keyword_color)) + .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) .add_modifier(Modifier::BOLD) } else { Style::new() - .fg(Color::Indexed(args.colors.highlight_text_color)) + .fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()) .add_modifier(Modifier::BOLD) }, ), - Span::raw(app.bibiman.search_struct.search_string.clone()) - .fg(Color::Indexed(args.colors.highlight_text_color)), + Span::raw(app.bibiman.search_struct.search_string.clone()).fg(cfg + .colors + .as_ref() + .unwrap() + .highlight_text_color + .unwrap()), ])) .block(block); @@ -383,7 +390,7 @@ pub fn render_footer(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rec // 1. Basename of the currently loaded file // 2. Keyword by which the entries are filtered at the moment // 3. Currently selected entry and total count of entries -pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_file_info(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let block = Block::new() // can also be Block::new // Leave Top empty to simulate one large box with borders of entry list .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) @@ -393,10 +400,10 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: symbols::border::PLAIN }) .border_style(if let CurrentArea::EntryArea = app.bibiman.current_area { - Style::new().fg(Color::Indexed(args.colors.highlight_text_color)) + Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()) } else { Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD) }); @@ -415,7 +422,7 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: { Line::from(vec![ Span::raw("File: ") - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), Span::raw( app.bibiman.main_bibfiles[0] @@ -423,28 +430,33 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .unwrap() .to_string_lossy(), ) - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), ]) - .bg(Color::Indexed(args.colors.bar_bg_color)) + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) } else { Line::from(vec![ Span::raw("Multiple files (") - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), Span::raw(count_files(&app.bibiman.main_bibfiles).to_string()) - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), Span::raw(")") - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), ]) - .bg(Color::Indexed(args.colors.bar_bg_color)) + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) }; let cur_keywords = Line::from(if !app.bibiman.tag_list.selected_keywords.is_empty() { vec![ - Span::raw("Selected keywords: ").fg(Color::Indexed(args.colors.main_text_color)), + Span::raw("Selected keywords: ").fg(cfg + .colors + .as_ref() + .unwrap() + .main_text_color + .unwrap()), // Show all keywords in correct order if list is filtered // successively by multiple keywords Span::raw(app.bibiman.tag_list.selected_keywords.join(" → ")) @@ -454,7 +466,7 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: } else { vec![Span::raw(" ")] }) - .bg(Color::Indexed(args.colors.bar_bg_color)); + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); // .render(keyword_area, buf); let item_count = Line::from( @@ -490,38 +502,42 @@ pub fn render_file_info(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .to_string() }, ) - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .bold(), - Span::raw("/").fg(Color::Indexed(args.colors.main_text_color)), - Span::raw(app.bibiman.entry_table.entry_table_items.len().to_string()) - .fg(Color::Indexed(args.colors.main_text_color)), + Span::raw("/").fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), + Span::raw(app.bibiman.entry_table.entry_table_items.len().to_string()).fg(cfg + .colors + .as_ref() + .unwrap() + .main_text_color + .unwrap()), ] } else { vec![Span::raw("No entries")] }, ) .right_aligned() - .bg(Color::Indexed(args.colors.bar_bg_color)); + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); frame.render_widget(file_info, file_area); frame.render_widget(cur_keywords, keyword_area); frame.render_widget(item_count, count_area); } -pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let entry_box_selected_border_style: Style = - Style::new().fg(Color::Indexed(args.colors.highlight_text_color)); + Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()); let entry_box_selected_title_style: Style = Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD); let entry_box_unselected_border_style: Style = - Style::new().fg(Color::Indexed(args.colors.main_text_color)); + Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); let entry_box_unselected_title_style: Style = Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD); let selected_table_col_style: Style = Style::new().add_modifier(Modifier::BOLD); let selectec_table_cell_style: Style = Style::new().add_modifier(Modifier::REVERSED); let entry_selected_row_style: Style = Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED); @@ -551,8 +567,8 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: let header_style = Style::default() .bold() - .fg(Color::Indexed(args.colors.main_text_color)) - .bg(Color::Indexed(args.colors.bar_bg_color)); + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); let header = Row::new(vec![ Cell::from( @@ -576,9 +592,9 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: if let EntryTableColumn::Authors = app.bibiman.entry_table.entry_table_selected_column { - Color::Indexed(args.colors.selected_row_bg_color) + cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() } else { - Color::Indexed(args.colors.bar_bg_color) + cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() }, ), ), @@ -602,9 +618,9 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .bg( if let EntryTableColumn::Title = app.bibiman.entry_table.entry_table_selected_column { - Color::Indexed(args.colors.selected_row_bg_color) + cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() } else { - Color::Indexed(args.colors.bar_bg_color) + cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() }, ), ), @@ -628,9 +644,9 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .bg( if let EntryTableColumn::Year = app.bibiman.entry_table.entry_table_selected_column { - Color::Indexed(args.colors.selected_row_bg_color) + cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() } else { - Color::Indexed(args.colors.bar_bg_color) + cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() }, ), ), @@ -655,9 +671,9 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: if let EntryTableColumn::Pubtype = app.bibiman.entry_table.entry_table_selected_column { - Color::Indexed(args.colors.selected_row_bg_color) + cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() } else { - Color::Indexed(args.colors.bar_bg_color) + cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() }, ), ), @@ -672,23 +688,24 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: .entry_table_items .iter_mut() .enumerate() - .map(|(i, data)| { + .map(|(_i, data)| { let item = data.ref_vec(); item.into_iter() .map(|content| Cell::from(Text::from(content.to_string()))) .collect::() .style( - Style::new().fg(color_list( - args, - i as i32, - app.bibiman - .entry_table - .entry_table_state - .selected() - .unwrap_or(0) as i32, - args.colors.highlight_text_color, - 20, - )), + // Style::new().fg(color_list( + // args, + // i as i32, + // app.bibiman + // .entry_table + // .entry_table_state + // .selected() + // .unwrap_or(0) as i32, + // args.colors.highlight_text_color, + // 20, + // )), + Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()), ) .height(1) }); @@ -741,11 +758,11 @@ pub fn render_entrytable(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: } } -pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { // We get the info depending on the item's state. let style_value = Style::new() .bold() - .fg(Color::Indexed(args.colors.main_text_color)); + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); let lines = { if app .bibiman @@ -767,7 +784,7 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re // Span::styled(cur_entry.authors.clone(), Style::new().green()), Span::styled( cur_entry.authors(), - Style::new().fg(Color::Indexed(args.colors.info_color)), + Style::new().fg(cfg.colors.as_ref().unwrap().info_color.unwrap()), ), ])); if cur_entry.subtitle.is_some() { @@ -776,19 +793,19 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re Span::styled( cur_entry.title(), Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::ITALIC), ), Span::styled( ": ", Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::ITALIC), ), Span::styled( cur_entry.subtitle(), Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::ITALIC), ), ])); @@ -798,7 +815,7 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re Span::styled( cur_entry.title(), Style::new() - .fg(Color::Indexed(args.colors.entry_color)) + .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) .add_modifier(Modifier::ITALIC), ), ])); @@ -807,7 +824,7 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re Span::styled("Year: ", style_value), Span::styled( cur_entry.year(), - Style::new().fg(Color::Indexed(args.colors.keyword_color)), + Style::new().fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()), ), ])); // Render keywords in info box in Markdown code style @@ -821,11 +838,13 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re let mut content = vec![Span::styled("Keywords: ", style_value)]; for k in kw { // Add half block highlighted in bg color to enlarge block - content.push(Span::raw("▐").fg(Color::Indexed(args.colors.bar_bg_color))); + content.push( + Span::raw("▐").fg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()), + ); content.push(Span::styled( k, Style::default() - .bg(Color::Indexed(args.colors.bar_bg_color)) + .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) .fg( // Highlight selected keyword green if app @@ -837,11 +856,13 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re { Color::Green } else { - Color::Indexed(args.colors.main_text_color) + cfg.colors.as_ref().unwrap().main_text_color.unwrap() }, ), )); - content.push(Span::raw("▌").fg(Color::Indexed(args.colors.bar_bg_color))); + content.push( + Span::raw("▌").fg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()), + ); } lines.push(Line::from(content)) } @@ -854,7 +875,7 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re Span::styled( cur_entry.doi_url(), Style::new() - .fg(Color::Indexed(args.colors.main_text_color)) + .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) .underlined(), ), ])); @@ -864,14 +885,14 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re Span::styled("File: ", style_value), Span::styled( cur_entry.filepath().to_string_lossy(), - Style::new().fg(Color::Indexed(args.colors.main_text_color)), + Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), ), ])); } lines.push(Line::from("")); lines.push(Line::from(vec![Span::styled( cur_entry.abstract_text.clone(), - Style::new().fg(Color::Indexed(args.colors.main_text_color)), + Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), )])); lines } else { @@ -888,7 +909,7 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re let block = Block::bordered() .title(Line::raw(" Entry Information ").centered().bold()) .border_set(symbols::border::PLAIN) - .border_style(Style::new().fg(Color::Indexed(args.colors.main_text_color))) + .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap())) .padding(Padding::horizontal(1)); // INFO: '.line_count' method only possible with unstable-rendered-line-info feature -> API might change: https://github.com/ratatui/ratatui/issues/293#ref-pullrequest-2027056434 @@ -941,19 +962,19 @@ pub fn render_selected_item(app: &mut App, args: &CLIArgs, frame: &mut Frame, re frame.render_widget(item_info, rect); } -pub fn render_taglist(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rect) { +pub fn render_taglist(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let keyword_box_selected_border_style: Style = - Style::new().fg(Color::Indexed(args.colors.highlight_text_color)); + Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()); let keyword_box_selected_title_style: Style = Style::new() - .fg(Color::Indexed(args.colors.keyword_color)) + .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) .add_modifier(Modifier::BOLD); let keyword_box_unselected_border_style: Style = - Style::new().fg(Color::Indexed(args.colors.main_text_color)); + Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); let keyword_box_unselected_title_style: Style = Style::new() - .fg(Color::Indexed(args.colors.keyword_color)) + .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) .add_modifier(Modifier::BOLD); let keyword_selected_row_style: Style = Style::new() - .fg(Color::Indexed(args.colors.keyword_color)) + .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED); @@ -987,18 +1008,19 @@ pub fn render_taglist(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Re .tag_list_items .iter() .enumerate() - .map(|(i, keyword)| { + .map(|(_i, keyword)| { ListItem::from(keyword.to_owned()).style(Style::new().fg( if app.bibiman.tag_list.tag_list_state.selected().is_some() { - color_list( - args, - i as i32, - app.bibiman.tag_list.tag_list_state.selected().unwrap() as i32, - args.colors.highlight_text_color, - 20, - ) + // color_list( + // args, + // i as i32, + // app.bibiman.tag_list.tag_list_state.selected().unwrap() as i32, + // args.colors.highlight_text_color, + // 20, + // ) + cfg.colors.as_ref().unwrap().highlight_text_color.unwrap() } else { - Color::Indexed(args.colors.main_text_color) + cfg.colors.as_ref().unwrap().main_text_color.unwrap() }, )) //.bg(color) }) -- cgit v1.2.3 From 63d4410fdfe7712faec287aee2f5c0ca288dc996 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 22 Feb 2025 00:48:41 +0100 Subject: replace Config crate with Figment -> better merging of default and config values --- Cargo.lock | 73 ++++++++-------- Cargo.toml | 3 +- example-config.toml | 2 +- src/app.rs | 4 +- src/bibiman.rs | 6 +- src/config.rs | 164 ++++++++++++----------------------- src/main.rs | 13 +-- src/tui/popup.rs | 21 +---- src/tui/ui.rs | 243 +++++++++++++++++++++------------------------------- 9 files changed, 205 insertions(+), 324 deletions(-) (limited to 'src/config.rs') diff --git a/Cargo.lock b/Cargo.lock index 342fba4..9c83245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,14 +58,12 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.86" +name = "atomic" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bytemuck", ] [[package]] @@ -102,10 +100,10 @@ dependencies = [ "arboard", "biblatex", "color-eyre", - "config", "crossterm", "dirs", "editor-command", + "figment", "futures", "itertools", "lexopt", @@ -270,29 +268,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "config" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf9dc8d4ef88e27a8cb23e85cb116403dedd57f7971964dc4b18ccead548901" -dependencies = [ - "async-trait", - "convert_case", - "pathdiff", - "serde", - "toml", - "winnow", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -488,6 +463,19 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1236,12 +1224,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -1924,6 +1906,15 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.13" @@ -2025,6 +2016,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -2310,9 +2307,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index c2f42a0..2bd6a3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,5 +35,6 @@ tui-input = "0.11.0" walkdir = "2.5.0" regex = "1.11.1" ureq = "2.12.1" -config = { version = "0.15.8", default-features = false, features = ["async", "async-trait", "convert-case", "convert_case", "toml"] } +# config = { version = "0.15.8", default-features = false, features = ["async", "async-trait", "convert-case", "convert_case", "toml"] } serde = { version = "1.0.217", features = ["serde_derive"] } +figment = { version = "0.10.19", features = [ "toml" ]} diff --git a/example-config.toml b/example-config.toml index d6d2aa8..89220df 100644 --- a/example-config.toml +++ b/example-config.toml @@ -13,5 +13,5 @@ editor = "vim" # arguments are possible: "vim -y" # confirm_color = "47" # warn_color = "124" # bar_bg_color = "234" -# popup_bg_color = "234" +# popup_bg_color = "55" # selected_row_bg_color = "237" diff --git a/src/app.rs b/src/app.rs index 7869304..e41defc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -357,7 +357,7 @@ impl App { pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard - let cmd = cfg.general.as_ref().unwrap().pdf_opener.as_ref().unwrap(); + let cmd = &cfg.general.pdf_opener; // If necessary, replace ~ with /home dir let file = PathBuf::from(file); @@ -377,7 +377,7 @@ pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { pub fn open_connected_link(cfg: &BibiConfig, link: &str) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard - let cmd = cfg.general.as_ref().unwrap().url_opener.as_ref().unwrap(); + let cmd = &cfg.general.url_opener; // Pass filepath as argument, pipe stdout and stderr to /dev/null // to keep the TUI clean (where is it piped on Windows???) let _ = Command::new(cmd) diff --git a/src/bibiman.rs b/src/bibiman.rs index b8ef2c6..e36d268 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -85,8 +85,8 @@ impl Bibiman { // Constructs a new instance of [`App`]. pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result { let mut main_bibfiles: Vec = args.pos_args.clone(); - if cfg.general.as_ref().unwrap().bibfiles.is_some() { - main_bibfiles.append(cfg.general.as_mut().unwrap().bibfiles.as_mut().unwrap()) + if cfg.general.bibfiles.is_some() { + main_bibfiles.append(cfg.general.bibfiles.as_mut().unwrap()) }; let main_bibfiles = cliargs::parse_files(main_bibfiles); let main_biblio = BibiSetup::new(&main_bibfiles); @@ -381,7 +381,7 @@ impl Bibiman { tui.exit()?; // Use VISUAL or EDITOR. Set "vi" as last fallback let mut cmd: Command = EditorBuilder::new() - .source(cfg.general.as_ref().unwrap().editor.clone()) + .source(cfg.general.editor.as_ref()) .environment() .source(Some("vi")) .build() diff --git a/src/config.rs b/src/config.rs index faba5d8..e2c34b9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,147 +18,95 @@ use std::path::PathBuf; use color_eyre::eyre::Result; -use config::{ConfigError, FileFormat}; +use figment::{ + providers::{Format, Serialized, Toml}, + Figment, +}; use ratatui::style::Color; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::cliargs::CLIArgs; /// Main struct of the config file. Contains substructs/headings in toml -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct BibiConfig { - pub general: Option, - pub colors: Option, + pub general: General, + pub colors: Colors, } /// Substruct [general] in config.toml -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct General { pub bibfiles: Option>, pub editor: Option, - pub pdf_opener: Option, - pub url_opener: Option, + pub pdf_opener: String, + pub url_opener: String, } /// Substruct [colors] in config.toml -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Colors { - pub main_text_color: Option, - pub highlight_text_color: Option, - pub entry_color: Option, - pub keyword_color: Option, - pub info_color: Option, - pub confirm_color: Option, - pub warn_color: Option, - pub bar_bg_color: Option, - pub popup_bg_color: Option, - pub selected_row_bg_color: Option, + pub main_text_color: Color, + pub highlight_text_color: Color, + pub entry_color: Color, + pub keyword_color: Color, + pub info_color: Color, + pub confirm_color: Color, + pub warn_color: Color, + pub bar_bg_color: Color, + pub popup_bg_color: Color, + pub selected_row_bg_color: Color, } impl Default for BibiConfig { fn default() -> Self { Self { - general: Some(General { + general: General { bibfiles: None, editor: None, - pdf_opener: Some(select_opener()), - url_opener: Some(select_opener()), - }), - colors: Some(Colors { - main_text_color: Some(Color::Indexed(250)), - highlight_text_color: Some(Color::Indexed(254)), - entry_color: Some(Color::Indexed(36)), - keyword_color: Some(Color::Indexed(101)), - info_color: Some(Color::Indexed(99)), - confirm_color: Some(Color::Indexed(47)), - warn_color: Some(Color::Indexed(124)), - bar_bg_color: Some(Color::Indexed(235)), - popup_bg_color: Some(Color::Indexed(234)), - selected_row_bg_color: Some(Color::Indexed(237)), - }), + pdf_opener: select_opener(), + url_opener: select_opener(), + }, + colors: Colors { + main_text_color: Color::Indexed(250), + highlight_text_color: Color::Indexed(254), + entry_color: Color::Indexed(36), + keyword_color: Color::Indexed(101), + info_color: Color::Indexed(99), + confirm_color: Color::Indexed(47), + warn_color: Color::Indexed(124), + bar_bg_color: Color::Indexed(235), + popup_bg_color: Color::Indexed(234), + selected_row_bg_color: Color::Indexed(237), + }, } } } impl BibiConfig { - pub fn parse_config(&mut self, args: &CLIArgs) -> Result<()> { - if args.cfg_path.is_file() { - let cfg_file = Self::parse_cfg_file(args)?; + pub fn parse_config(args: &CLIArgs) -> Result { + let cfg_file: BibiConfig = if args.cfg_path.is_file() { + Figment::from(Serialized::defaults(BibiConfig::default())) + .merge(Toml::file(&args.cfg_path)) + .extract()? + } else { + BibiConfig::default() + }; - if let Some(general) = cfg_file.general { - if let Some(bibfiles) = general.bibfiles { - self.general.as_mut().unwrap().bibfiles = Some(bibfiles) - } - if let Some(editor) = general.editor { - self.general.as_mut().unwrap().editor = Some(editor) - } - if let Some(pdf_opener) = general.pdf_opener { - self.general.as_mut().unwrap().pdf_opener = Some(pdf_opener) - } - if let Some(url_opener) = general.url_opener { - self.general.as_mut().unwrap().url_opener = Some(url_opener) - } - } - - if let Some(colors) = cfg_file.colors { - if let Some(main_text_color) = colors.main_text_color { - self.colors.as_mut().unwrap().main_text_color = Some(main_text_color) - } - if let Some(highlight_text_color) = colors.highlight_text_color { - self.colors.as_mut().unwrap().highlight_text_color = Some(highlight_text_color) - } - if let Some(entry_color) = colors.entry_color { - self.colors.as_mut().unwrap().entry_color = Some(entry_color) - } - if let Some(keyword_color) = colors.keyword_color { - self.colors.as_mut().unwrap().keyword_color = Some(keyword_color) - } - if let Some(info_color) = colors.info_color { - self.colors.as_mut().unwrap().info_color = Some(info_color) - } - if let Some(confirm_color) = colors.confirm_color { - self.colors.as_mut().unwrap().confirm_color = Some(confirm_color) - } - if let Some(warn_color) = colors.warn_color { - self.colors.as_mut().unwrap().warn_color = Some(warn_color) - } - if let Some(bar_bg_color) = colors.bar_bg_color { - self.colors.as_mut().unwrap().bar_bg_color = Some(bar_bg_color) - } - if let Some(popup_bg_color) = colors.popup_bg_color { - self.colors.as_mut().unwrap().popup_bg_color = Some(popup_bg_color) - } - if let Some(selected_row_bg_color) = colors.selected_row_bg_color { - self.colors.as_mut().unwrap().selected_row_bg_color = - Some(selected_row_bg_color) - } - } - } - - Ok(()) - } - - fn parse_cfg_file(args: &CLIArgs) -> Result { - let mut cfg = config::Config::builder(); - cfg = cfg.add_source( - config::File::from(args.cfg_path.clone()) - .format(FileFormat::Toml) - .required(false), - ); - cfg.build()?.try_deserialize() + Ok(cfg_file) } /// Activates the default color scheme for light background terminals pub fn light_colors(&mut self) { - self.colors.as_mut().unwrap().main_text_color = Some(Color::Indexed(235)); - self.colors.as_mut().unwrap().highlight_text_color = Some(Color::Indexed(232)); - self.colors.as_mut().unwrap().entry_color = Some(Color::Indexed(23)); - self.colors.as_mut().unwrap().keyword_color = Some(Color::Indexed(58)); - self.colors.as_mut().unwrap().info_color = Some(Color::Indexed(57)); - self.colors.as_mut().unwrap().bar_bg_color = Some(Color::Indexed(144)); - self.colors.as_mut().unwrap().popup_bg_color = Some(Color::Indexed(187)); - self.colors.as_mut().unwrap().confirm_color = Some(Color::Indexed(22)); - self.colors.as_mut().unwrap().selected_row_bg_color = Some(Color::Indexed(107)); + self.colors.main_text_color = Color::Indexed(235); + self.colors.highlight_text_color = Color::Indexed(232); + self.colors.entry_color = Color::Indexed(23); + self.colors.keyword_color = Color::Indexed(58); + self.colors.info_color = Color::Indexed(57); + self.colors.bar_bg_color = Color::Indexed(144); + self.colors.popup_bg_color = Color::Indexed(187); + self.colors.confirm_color = Color::Indexed(22); + self.colors.selected_row_bg_color = Color::Indexed(107); } } diff --git a/src/main.rs b/src/main.rs index 3325f80..302ba7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,14 +46,15 @@ async fn main() -> Result<()> { } // Build default config - let mut cfg = BibiConfig::default(); + // let mut cfg = BibiConfig::default(); - if parsed_args.light_theme { - cfg.light_colors(); - } - // Merge values from config file if present - cfg.parse_config(&parsed_args)?; + // if parsed_args.light_theme { + // cfg.light_colors(); + // } + // // Merge values from config file if present + // cfg.parse_config(&parsed_args)?; + let mut cfg = BibiConfig::parse_config(&parsed_args)?; init_error_hooks()?; // Create an application. diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 6a2e8ff..f226429 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -89,31 +89,16 @@ impl PopupArea { for (keys, help) in help { if help == "first" { helptext.push(Line::from( - Span::raw(keys).bold().fg(cfg - .colors - .as_ref() - .unwrap() - .main_text_color - .unwrap()), + Span::raw(keys).bold().fg(cfg.colors.main_text_color), )) } else if help == "sub" { helptext.push(Line::from("")); helptext.push(Line::from( - Span::raw(keys).bold().fg(cfg - .colors - .as_ref() - .unwrap() - .main_text_color - .unwrap()), + Span::raw(keys).bold().fg(cfg.colors.main_text_color), )) } else { helptext.push(Line::from(vec![ - Span::raw(keys).bold().fg(cfg - .colors - .as_ref() - .unwrap() - .main_text_color - .unwrap()), + Span::raw(keys).bold().fg(cfg.colors.main_text_color), Span::raw(help), ])) } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 5fbe283..2d58aec 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -150,11 +150,11 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.popup_bg_color), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap())); + .border_style(Style::new().fg(cfg.colors.entry_color)); let text: Text = PopupArea::popup_help(cfg); @@ -191,23 +191,20 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.popup_bg_color), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap())); + .border_style(Style::new().fg(cfg.colors.entry_color)); // Prepare the input fields let content = vec![Line::from(vec![ - Span::styled( - "DOI: ", - Style::new().fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()), - ), + Span::styled("DOI: ", Style::new().fg(cfg.colors.entry_color)), Span::raw(app.input.value().to_string().clone()), ])]; let paragraph = Paragraph::new(content) .block(block.clone()) - .style(Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap())) + .style(Style::new().fg(cfg.colors.main_text_color)) .wrap(Wrap { trim: false }); let doi_lines = paragraph.line_count(area.width / 2); @@ -225,21 +222,17 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { let area = frame.area(); let block = Block::bordered() - .title_top( - " Message " - .bold() - .fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap()), - ) - .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap())) + .title_top(" Message ".bold().fg(cfg.colors.confirm_color)) + .border_style(Style::new().fg(cfg.colors.confirm_color)) .style( Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.popup_bg_color), ); let content = Paragraph::new(app.bibiman.popup_area.popup_message.clone()) .block(block) - .style(Style::new().fg(cfg.colors.as_ref().unwrap().confirm_color.unwrap())); + .style(Style::new().fg(cfg.colors.confirm_color)); // Calculate popup size. Width is number of string chars plus 2 for border let popup_area = popup_area( @@ -256,21 +249,17 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { let area = frame.area(); let block = Block::bordered() - .title_top( - " Warning " - .bold() - .fg(cfg.colors.as_ref().unwrap().warn_color.unwrap()), - ) + .title_top(" Warning ".bold().fg(cfg.colors.warn_color)) .border_style(Style::new().fg(Color::Red)) .style( Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.popup_bg_color), ); let content = Paragraph::new(app.bibiman.popup_area.popup_message.clone()) .block(block) - .style(Style::new().fg(cfg.colors.as_ref().unwrap().warn_color.unwrap())); + .style(Style::new().fg(cfg.colors.warn_color)); // Calculate popup size. Width is number of string chars plus 2 for border let popup_area = popup_area( @@ -306,15 +295,15 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { .title_alignment(Alignment::Center) .style( Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().popup_bg_color.unwrap()), + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.popup_bg_color), ) .border_set(symbols::border::THICK) - .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap())); + .border_style(Style::new().fg(cfg.colors.keyword_color)); let list = List::new(list_items).block(block).highlight_style( Style::new() - // .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + // .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED), ); @@ -333,7 +322,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { pub fn render_header(cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { let main_header = Paragraph::new("BIBIMAN – BibLaTeX manager TUI") .bold() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .centered(); frame.render_widget(main_header, rect) } @@ -349,36 +338,29 @@ pub fn render_footer(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: R let title_lenght: u16 = search_title.chars().count() as u16; - let block = Block::new().padding(Padding::horizontal(1)).bg(cfg - .colors - .as_ref() - .unwrap() - .bar_bg_color - .unwrap()); + let block = Block::new() + .padding(Padding::horizontal(1)) + .bg(cfg.colors.bar_bg_color); let search_string = Paragraph::new(Line::from(vec![ Span::styled( search_title, if let Some(FormerArea::EntryArea) = app.bibiman.former_area { Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD) } else if let Some(FormerArea::TagArea) = app.bibiman.former_area { Style::new() - .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) + .fg(cfg.colors.keyword_color) .add_modifier(Modifier::BOLD) } else { Style::new() - .fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()) + .fg(cfg.colors.highlight_text_color) .add_modifier(Modifier::BOLD) }, ), - Span::raw(app.bibiman.search_struct.search_string.clone()).fg(cfg - .colors - .as_ref() - .unwrap() - .highlight_text_color - .unwrap()), + Span::raw(app.bibiman.search_struct.search_string.clone()) + .fg(cfg.colors.highlight_text_color), ])) .block(block); @@ -400,10 +382,10 @@ pub fn render_file_info(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect symbols::border::PLAIN }) .border_style(if let CurrentArea::EntryArea = app.bibiman.current_area { - Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()) + Style::new().fg(cfg.colors.highlight_text_color) } else { Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD) }); @@ -421,42 +403,33 @@ pub fn render_file_info(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect && app.bibiman.main_bibfiles.first().unwrap().is_file() { Line::from(vec![ - Span::raw("File: ") - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bold(), + Span::raw("File: ").fg(cfg.colors.main_text_color).bold(), Span::raw( app.bibiman.main_bibfiles[0] .file_name() .unwrap() .to_string_lossy(), ) - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .fg(cfg.colors.main_text_color) .bold(), ]) - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) + .bg(cfg.colors.bar_bg_color) } else { Line::from(vec![ Span::raw("Multiple files (") - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .fg(cfg.colors.main_text_color) .bold(), Span::raw(count_files(&app.bibiman.main_bibfiles).to_string()) - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bold(), - Span::raw(")") - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .fg(cfg.colors.main_text_color) .bold(), + Span::raw(")").fg(cfg.colors.main_text_color).bold(), ]) - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) + .bg(cfg.colors.bar_bg_color) }; let cur_keywords = Line::from(if !app.bibiman.tag_list.selected_keywords.is_empty() { vec![ - Span::raw("Selected keywords: ").fg(cfg - .colors - .as_ref() - .unwrap() - .main_text_color - .unwrap()), + Span::raw("Selected keywords: ").fg(cfg.colors.main_text_color), // Show all keywords in correct order if list is filtered // successively by multiple keywords Span::raw(app.bibiman.tag_list.selected_keywords.join(" → ")) @@ -466,7 +439,7 @@ pub fn render_file_info(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect } else { vec![Span::raw(" ")] }) - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); + .bg(cfg.colors.bar_bg_color); // .render(keyword_area, buf); let item_count = Line::from( @@ -502,42 +475,36 @@ pub fn render_file_info(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect .to_string() }, ) - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) + .fg(cfg.colors.main_text_color) .bold(), - Span::raw("/").fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), - Span::raw(app.bibiman.entry_table.entry_table_items.len().to_string()).fg(cfg - .colors - .as_ref() - .unwrap() - .main_text_color - .unwrap()), + Span::raw("/").fg(cfg.colors.main_text_color), + Span::raw(app.bibiman.entry_table.entry_table_items.len().to_string()) + .fg(cfg.colors.main_text_color), ] } else { vec![Span::raw("No entries")] }, ) .right_aligned() - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); + .bg(cfg.colors.bar_bg_color); frame.render_widget(file_info, file_area); frame.render_widget(cur_keywords, keyword_area); frame.render_widget(item_count, count_area); } pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { - let entry_box_selected_border_style: Style = - Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()); + let entry_box_selected_border_style: Style = Style::new().fg(cfg.colors.highlight_text_color); let entry_box_selected_title_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD); - let entry_box_unselected_border_style: Style = - Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); + let entry_box_unselected_border_style: Style = Style::new().fg(cfg.colors.main_text_color); let entry_box_unselected_title_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD); let selected_table_col_style: Style = Style::new().add_modifier(Modifier::BOLD); let selectec_table_cell_style: Style = Style::new().add_modifier(Modifier::REVERSED); let entry_selected_row_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED); @@ -567,8 +534,8 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec let header_style = Style::default() .bold() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()); + .fg(cfg.colors.main_text_color) + .bg(cfg.colors.bar_bg_color); let header = Row::new(vec![ Cell::from( @@ -592,9 +559,9 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec if let EntryTableColumn::Authors = app.bibiman.entry_table.entry_table_selected_column { - cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() + cfg.colors.selected_row_bg_color } else { - cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() + cfg.colors.bar_bg_color }, ), ), @@ -618,9 +585,9 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec .bg( if let EntryTableColumn::Title = app.bibiman.entry_table.entry_table_selected_column { - cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() + cfg.colors.selected_row_bg_color } else { - cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() + cfg.colors.bar_bg_color }, ), ), @@ -644,9 +611,9 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec .bg( if let EntryTableColumn::Year = app.bibiman.entry_table.entry_table_selected_column { - cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() + cfg.colors.selected_row_bg_color } else { - cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() + cfg.colors.bar_bg_color }, ), ), @@ -671,9 +638,9 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec if let EntryTableColumn::Pubtype = app.bibiman.entry_table.entry_table_selected_column { - cfg.colors.as_ref().unwrap().selected_row_bg_color.unwrap() + cfg.colors.selected_row_bg_color } else { - cfg.colors.as_ref().unwrap().bar_bg_color.unwrap() + cfg.colors.bar_bg_color }, ), ), @@ -705,7 +672,7 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec // args.colors.highlight_text_color, // 20, // )), - Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()), + Style::new().fg(cfg.colors.highlight_text_color), ) .height(1) }); @@ -760,9 +727,7 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { // We get the info depending on the item's state. - let style_value = Style::new() - .bold() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); + let style_value = Style::new().bold().fg(cfg.colors.main_text_color); let lines = { if app .bibiman @@ -782,10 +747,7 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, lines.push(Line::from(vec![ Span::styled("Authors: ", style_value), // Span::styled(cur_entry.authors.clone(), Style::new().green()), - Span::styled( - cur_entry.authors(), - Style::new().fg(cfg.colors.as_ref().unwrap().info_color.unwrap()), - ), + Span::styled(cur_entry.authors(), Style::new().fg(cfg.colors.info_color)), ])); if cur_entry.subtitle.is_some() { lines.push(Line::from(vec![ @@ -793,19 +755,19 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, Span::styled( cur_entry.title(), Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::ITALIC), ), Span::styled( ": ", Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::ITALIC), ), Span::styled( cur_entry.subtitle(), Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::ITALIC), ), ])); @@ -815,17 +777,14 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, Span::styled( cur_entry.title(), Style::new() - .fg(cfg.colors.as_ref().unwrap().entry_color.unwrap()) + .fg(cfg.colors.entry_color) .add_modifier(Modifier::ITALIC), ), ])); } lines.push(Line::from(vec![ Span::styled("Year: ", style_value), - Span::styled( - cur_entry.year(), - Style::new().fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()), - ), + Span::styled(cur_entry.year(), Style::new().fg(cfg.colors.keyword_color)), ])); // Render keywords in info box in Markdown code style if !cur_entry.keywords.is_empty() { @@ -838,31 +797,25 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, let mut content = vec![Span::styled("Keywords: ", style_value)]; for k in kw { // Add half block highlighted in bg color to enlarge block - content.push( - Span::raw("▐").fg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()), - ); + content.push(Span::raw("▐").fg(cfg.colors.bar_bg_color)); content.push(Span::styled( k, - Style::default() - .bg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()) - .fg( - // Highlight selected keyword green - if app - .bibiman - .tag_list - .selected_keywords - .iter() - .any(|e| e == k) - { - Color::Green - } else { - cfg.colors.as_ref().unwrap().main_text_color.unwrap() - }, - ), + Style::default().bg(cfg.colors.bar_bg_color).fg( + // Highlight selected keyword green + if app + .bibiman + .tag_list + .selected_keywords + .iter() + .any(|e| e == k) + { + Color::Green + } else { + cfg.colors.main_text_color + }, + ), )); - content.push( - Span::raw("▌").fg(cfg.colors.as_ref().unwrap().bar_bg_color.unwrap()), - ); + content.push(Span::raw("▌").fg(cfg.colors.bar_bg_color)); } lines.push(Line::from(content)) } @@ -874,9 +827,7 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, Span::styled("DOI/URL: ", style_value), Span::styled( cur_entry.doi_url(), - Style::new() - .fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()) - .underlined(), + Style::new().fg(cfg.colors.main_text_color).underlined(), ), ])); } @@ -885,14 +836,14 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, Span::styled("File: ", style_value), Span::styled( cur_entry.filepath().to_string_lossy(), - Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), + Style::new().fg(cfg.colors.main_text_color), ), ])); } lines.push(Line::from("")); lines.push(Line::from(vec![Span::styled( cur_entry.abstract_text.clone(), - Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()), + Style::new().fg(cfg.colors.main_text_color), )])); lines } else { @@ -909,7 +860,7 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, let block = Block::bordered() .title(Line::raw(" Entry Information ").centered().bold()) .border_set(symbols::border::PLAIN) - .border_style(Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap())) + .border_style(Style::new().fg(cfg.colors.main_text_color)) .padding(Padding::horizontal(1)); // INFO: '.line_count' method only possible with unstable-rendered-line-info feature -> API might change: https://github.com/ratatui/ratatui/issues/293#ref-pullrequest-2027056434 @@ -963,18 +914,16 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, } pub fn render_taglist(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: Rect) { - let keyword_box_selected_border_style: Style = - Style::new().fg(cfg.colors.as_ref().unwrap().highlight_text_color.unwrap()); + let keyword_box_selected_border_style: Style = Style::new().fg(cfg.colors.highlight_text_color); let keyword_box_selected_title_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) + .fg(cfg.colors.keyword_color) .add_modifier(Modifier::BOLD); - let keyword_box_unselected_border_style: Style = - Style::new().fg(cfg.colors.as_ref().unwrap().main_text_color.unwrap()); + let keyword_box_unselected_border_style: Style = Style::new().fg(cfg.colors.main_text_color); let keyword_box_unselected_title_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) + .fg(cfg.colors.keyword_color) .add_modifier(Modifier::BOLD); let keyword_selected_row_style: Style = Style::new() - .fg(cfg.colors.as_ref().unwrap().keyword_color.unwrap()) + .fg(cfg.colors.keyword_color) .add_modifier(Modifier::BOLD) .add_modifier(Modifier::REVERSED); @@ -1018,9 +967,9 @@ pub fn render_taglist(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rect: // args.colors.highlight_text_color, // 20, // ) - cfg.colors.as_ref().unwrap().highlight_text_color.unwrap() + cfg.colors.highlight_text_color } else { - cfg.colors.as_ref().unwrap().main_text_color.unwrap() + cfg.colors.main_text_color }, )) //.bg(color) }) -- cgit v1.2.3 From d8bb353580f8d26e1e3dc2422e421ce64aa85417 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 23 Feb 2025 12:35:56 +0100 Subject: test module for config crate --- Cargo.lock | 2 ++ Cargo.toml | 2 +- src/config.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) (limited to 'src/config.rs') diff --git a/Cargo.lock b/Cargo.lock index 9c83245..3ad5cee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,7 +470,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ "atomic", + "parking_lot", "serde", + "tempfile", "toml", "uncased", "version_check", diff --git a/Cargo.toml b/Cargo.toml index 2bd6a3f..bc73fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,4 @@ regex = "1.11.1" ureq = "2.12.1" # config = { version = "0.15.8", default-features = false, features = ["async", "async-trait", "convert-case", "convert_case", "toml"] } serde = { version = "1.0.217", features = ["serde_derive"] } -figment = { version = "0.10.19", features = [ "toml" ]} +figment = { version = "0.10.19", features = [ "toml", "test" ]} diff --git a/src/config.rs b/src/config.rs index e2c34b9..91bd1e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,14 +28,14 @@ use serde::{Deserialize, Serialize}; use crate::cliargs::CLIArgs; /// Main struct of the config file. Contains substructs/headings in toml -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct BibiConfig { pub general: General, pub colors: Colors, } /// Substruct [general] in config.toml -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct General { pub bibfiles: Option>, pub editor: Option, @@ -44,7 +44,7 @@ pub struct General { } /// Substruct [colors] in config.toml -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct Colors { pub main_text_color: Color, pub highlight_text_color: Color, @@ -118,3 +118,47 @@ fn select_opener() -> String { _ => panic!("Couldn't detect OS for setting correct opener"), } } + +#[cfg(test)] +mod tests { + use figment::{ + providers::{Format, Toml}, + Figment, + }; + + use super::BibiConfig; + + #[test] + fn parse_default_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "bibiman.toml", + r#" + [general] + pdf_opener = "xdg-open" + url_opener = "xdg-open" + + [colors] + main_text_color = "250" + highlight_text_color = "254" + entry_color = "36" + keyword_color = "101" + info_color = "99" + confirm_color = "47" + warn_color = "124" + bar_bg_color = "235" + popup_bg_color = "234" + selected_row_bg_color = "237" + "#, + )?; + + let config: BibiConfig = Figment::new().merge(Toml::file("bibiman.toml")).extract()?; + + let default_config: BibiConfig = BibiConfig::default(); + + assert_eq!(config, default_config); + + Ok(()) + }); + } +} -- cgit v1.2.3 From 7cf08ef15b50b5f83fccbe65ea161f2e92c22cd0 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 24 Feb 2025 14:45:58 +0100 Subject: new gif, update version to 0.11.0 --- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- example-config.toml | 17 ----------------- src/config.rs | 2 ++ 5 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 example-config.toml (limited to 'src/config.rs') diff --git a/.gitignore b/.gitignore index ba75650..e711a10 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ res/*.png .devbox .envrc devbox.json +example-config.toml diff --git a/Cargo.lock b/Cargo.lock index 3ad5cee..193ad2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bibiman" -version = "0.10.0" +version = "0.11.0" dependencies = [ "arboard", "biblatex", diff --git a/Cargo.toml b/Cargo.toml index bc73fcb..f4e1ac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bibiman" -version = "0.10.0" +version = "0.11.0" authors = ["lukeflo "] license = "GPL-3.0-or-later" repository = "https://codeberg.org/lukeflo/bibiman" diff --git a/example-config.toml b/example-config.toml deleted file mode 100644 index 89220df..0000000 --- a/example-config.toml +++ /dev/null @@ -1,17 +0,0 @@ -[general] -bibfiles = ["./tests/biblatex-test.bib"] # multiple files and dirs are possible -editor = "vim" # arguments are possible: "vim -y" -# pdf_opener = "xdg-open" -# url_opener = "xdg-open" - -# [colors] -# main_text_color = "250" -# highlight_text_color = "254" -# entry_color = "36" -# keyword_color = "101" -# info_color = "99" -# confirm_color = "47" -# warn_color = "124" -# bar_bg_color = "234" -# popup_bg_color = "55" -# selected_row_bg_color = "237" diff --git a/src/config.rs b/src/config.rs index 91bd1e8..956b724 100644 --- a/src/config.rs +++ b/src/config.rs @@ -157,6 +157,8 @@ mod tests { let default_config: BibiConfig = BibiConfig::default(); assert_eq!(config, default_config); + assert_eq!(config.general.bibfiles, None); + assert_eq!(config.general.editor, None); Ok(()) }); -- cgit v1.2.3