From 28030fd0830478872be2b9e86b74e0c054a96111 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 24 May 2025 17:31:33 +0200 Subject: implemented a workflow, but citekey matching still buggy --- tests/pdf-files/aristotle:rhetoric.pdf | Bin 0 -> 674743 bytes tests/test-config.toml | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 tests/pdf-files/aristotle:rhetoric.pdf (limited to 'tests') diff --git a/tests/pdf-files/aristotle:rhetoric.pdf b/tests/pdf-files/aristotle:rhetoric.pdf new file mode 100644 index 0000000..a6d6eff Binary files /dev/null and b/tests/pdf-files/aristotle:rhetoric.pdf differ diff --git a/tests/test-config.toml b/tests/test-config.toml index 3913f16..1b60acd 100644 --- a/tests/test-config.toml +++ b/tests/test-config.toml @@ -16,6 +16,11 @@ bibfiles = [ "tests/biblatex-test.bib" ] ## Use absolute paths (~ for HOME works). Otherwise, loading might not work. # file_prefix = "/some/path/prefix" +## Path to folder (with subfolders) containing PDF files with the basename +## of the format "citekey.pdf". Other PDF basenames are not accepted. +## Use absolute paths (~ for HOME works). Otherwise, loading might not work. +pdf_path = "tests/pdf-files" + # [colors] ## Default values for dark-themed terminal ## Possible values are: -- cgit v1.2.3 From 06782b5ed1527b1d4eb6e26feb5a260415efe1af Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 25 May 2025 14:21:17 +0200 Subject: enhancement from PR \#28 discussion + Collect basenames and filepaths from pdf dir into HashMap + Identical basenames in different subdirs are possible + Collected into Vector + TODO: implement selection for multiple files (yank and open) --- src/bibiman.rs | 5 +- src/bibiman/bibisetup.rs | 140 ++++++++++++++++----- src/tui/ui.rs | 14 ++- .../annotated-pdfs/ARIStotle:rheTORIC.PDF | Bin 0 -> 25294 bytes tests/pdf-files/aristotle:physics.pdf | Bin 0 -> 25294 bytes tests/pdf-files/aristotle:rhetoric.pdf | Bin 674743 -> 25294 bytes 6 files changed, 122 insertions(+), 37 deletions(-) create mode 100644 tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF create mode 100644 tests/pdf-files/aristotle:physics.pdf (limited to 'tests') diff --git a/src/bibiman.rs b/src/bibiman.rs index ef5dfe3..646a078 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -557,7 +557,8 @@ impl Bibiman { let url = app::prepare_weblink(object); 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(); + // TODO: Selection for multiple files + let object = self.entry_table.entry_table_items[entry_idx].filepath()[0]; app::open_connected_file(cfg, object)?; } else { eprintln!("Unable to find ressource to open"); @@ -604,7 +605,7 @@ impl Bibiman { .filepath .clone(); if let Some(p) = path { - let p = p.as_os_str().to_str(); + let p = p[0].as_os_str().to_str(); if let Some(p) = p { Bibiman::yank_text(p); self.popup_area.popup_message( diff --git a/src/bibiman/bibisetup.rs b/src/bibiman/bibisetup.rs index aa0640c..0914f7c 100644 --- a/src/bibiman/bibisetup.rs +++ b/src/bibiman/bibisetup.rs @@ -19,12 +19,13 @@ use biblatex::{self, Bibliography}; use biblatex::{ChunksExt, Type}; use color_eyre::owo_colors::OwoColorize; use itertools::Itertools; +use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::{fs, path::PathBuf}; use walkdir::WalkDir; use crate::app; -use crate::cliargs::{self, CLIArgs}; +use crate::cliargs::{self}; use crate::config::BibiConfig; // Set necessary fields @@ -51,7 +52,7 @@ pub struct BibiData { pub citekey: String, pub abstract_text: String, pub doi_url: Option, - pub filepath: Option, + pub filepath: Option>, pub subtitle: Option, } @@ -114,8 +115,17 @@ impl BibiData { self.doi_url.as_ref().unwrap() } - pub fn filepath(&self) -> &OsStr { - self.filepath.as_ref().unwrap() + // pub fn filepath(&self) -> &OsStr { + // self.filepath.as_ref().unwrap() + // } + + pub fn filepath(&mut self) -> Vec<&OsStr> { + self.filepath + .as_mut() + .unwrap() + .iter_mut() + .map(|p| p.as_os_str()) + .collect_vec() } pub fn subtitle(&self) -> &str { @@ -190,7 +200,7 @@ impl BibiSetup { cfg: &BibiConfig, ) -> Vec { let mut pdf_files = if cfg.general.pdf_path.is_some() { - collect_pdf_files(cfg.general.pdf_path.as_ref().unwrap()) + collect_pdf_file_paths(cfg.general.pdf_path.as_ref().unwrap()) } else { None }; @@ -343,10 +353,16 @@ impl BibiSetup { pub fn get_filepath( citekey: &str, biblio: &Bibliography, - pdf_files: &mut Option>, - ) -> Option { + pdf_files: &mut Option>>, + ) -> Option> { if biblio.get(citekey).unwrap().file().is_ok() { - Some(biblio.get(citekey).unwrap().file().unwrap().trim().into()) + Some(vec![biblio + .get(citekey) + .unwrap() + .file() + .unwrap() + .trim() + .into()]) } else if pdf_files.is_some() { Self::merge_filepath_or_none(&citekey, pdf_files) } else { @@ -371,11 +387,10 @@ impl BibiSetup { fn merge_filepath_or_none( citekey: &str, - pdf_files: &mut Option>, - ) -> Option { - // Oder n Loop??? + pdf_files: &mut Option>>, + ) -> Option> { let pdf_file = { - let mut idx = 0; + // let mut idx = 0; let citekey = citekey.to_owned().to_ascii_lowercase() + ".pdf"; // let filename = citekey.to_owned() + ".pdf"; // for f in args.pdf_files.unwrap().iter() { @@ -384,27 +399,27 @@ impl BibiSetup { // } // } - loop { - if idx + 1 > pdf_files.as_ref().unwrap().len() { - break None; - } - let cur_entry = pdf_files.as_ref().unwrap()[idx].clone(); - if cur_entry.is_file() - && cur_entry - .file_name() - .unwrap() - .to_ascii_lowercase() - .to_str() - .unwrap() - == citekey - { - let path = cur_entry.to_owned().into_os_string(); - pdf_files.as_mut().unwrap().swap_remove(idx); - break Some(path); - } else { - idx += 1 - } - } + // loop { + // if idx + 1 > pdf_files.as_ref().unwrap().len() { + // break None; + // } + // let cur_entry = pdf_files.as_ref().unwrap()[idx].clone(); + // if cur_entry.is_file() + // && cur_entry + // .file_name() + // .unwrap() + // .to_ascii_lowercase() + // .to_str() + // .unwrap() + // == citekey + // { + // let path = cur_entry.to_owned().into_os_string(); + // pdf_files.as_mut().unwrap().swap_remove(idx); + // break Some(path); + // } else { + // idx += 1 + // } + // } // for file in pdf_files.as_ref().unwrap().iter() { // let filename = file.file_name().unwrap().to_ascii_lowercase(); @@ -422,6 +437,18 @@ impl BibiSetup { // } else { // None // } + + if pdf_files.as_ref().unwrap().contains_key(&citekey) { + let path_vec = pdf_files + .as_ref() + .unwrap() + .get(&citekey) + .unwrap() + .to_owned(); + Some(path_vec.into_iter().map(|p| p.into_os_string()).collect()) + } else { + None + } }; pdf_file @@ -458,3 +485,50 @@ pub fn collect_pdf_files(pdf_dir: &PathBuf) -> Option> { Some(files) } } + +/// This function walks the given dir and collects all pdf files into a `HashMap` +/// of the format `[String, Vec]`, where `String` represents the basename +/// of the file and the `Vec` holds all filepaths ending with this basename. +/// +/// In most cases the latter is only a single path, but there might be some concepts +/// with subdirs were some entries have multiple files associated with them. +pub fn collect_pdf_file_paths(pdf_dir: &PathBuf) -> Option>> { + let mut files: HashMap> = HashMap::new(); + + // Expand tilde to /home/user + let pdf_dir = if pdf_dir.starts_with("~") { + &app::expand_home(&pdf_dir) + } else { + pdf_dir + }; + + // Walk the passed dir and collect all pdf files into hashmap + if pdf_dir.is_dir() { + for file in WalkDir::new(pdf_dir) { + let f = file.unwrap().into_path(); + if f.is_file() + && f.extension().is_some() + && f.extension().unwrap_or_default().to_ascii_lowercase() == "pdf" + { + let filename = f + .file_name() + .unwrap() + .to_ascii_lowercase() + .into_string() + .unwrap(); + + if let Some(paths) = files.get_mut(&filename) { + paths.push(f); + } else { + files.insert(filename, vec![f]); + } + } + } + } + + if files.is_empty() { + None + } else { + Some(files) + } +} diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 921cbb1..883e4df 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -24,6 +24,7 @@ use crate::cliargs::CLIArgs; use crate::config::BibiConfig; use crate::tui::popup::PopupKind; use crate::App; +use itertools::Itertools; use ratatui::layout::{Direction, Position}; use ratatui::widgets::Clear; use ratatui::Frame; @@ -833,15 +834,24 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, ), ])); } - if cur_entry.filepath.is_some() { + if let Some(p) = &cur_entry.filepath { lines.push(Line::from(vec![ Span::styled("File: ", style_value), Span::styled( - cur_entry.filepath().to_string_lossy(), + p.iter().map(|f| f.to_str().unwrap()).join("; "), Style::new().fg(cfg.colors.main_text_color), ), ])); } + // if cur_entry.filepath.is_some() { + // lines.push(Line::from(vec![ + // Span::styled("File: ", style_value), + // Span::styled( + // cur_entry.filepath().to_string_lossy(), + // Style::new().fg(cfg.colors.main_text_color), + // ), + // ])); + // } lines.push(Line::from("")); lines.push(Line::from(vec![Span::styled( cur_entry.abstract_text.clone(), diff --git a/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF b/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF new file mode 100644 index 0000000..6aaba88 Binary files /dev/null and b/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF differ diff --git a/tests/pdf-files/aristotle:physics.pdf b/tests/pdf-files/aristotle:physics.pdf new file mode 100644 index 0000000..6aaba88 Binary files /dev/null and b/tests/pdf-files/aristotle:physics.pdf differ diff --git a/tests/pdf-files/aristotle:rhetoric.pdf b/tests/pdf-files/aristotle:rhetoric.pdf index a6d6eff..6aaba88 100644 Binary files a/tests/pdf-files/aristotle:rhetoric.pdf and b/tests/pdf-files/aristotle:rhetoric.pdf differ -- cgit v1.2.3 From d64596242ab185fffebc773ad2dcb5f1be2fccc2 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 1 Jun 2025 14:54:38 +0200 Subject: some fixes and test for `file_prefix` with new bool --- src/app.rs | 27 +++++++++++++++------ src/bibiman/bibisetup.rs | 63 +++++++++++++++++++++++++++--------------------- src/bibiman/entries.rs | 2 ++ src/bibiman/search.rs | 5 +++- src/tui/popup.rs | 22 ----------------- src/tui/ui.rs | 16 +++++++++--- tests/test-config.toml | 2 +- 7 files changed, 75 insertions(+), 62 deletions(-) (limited to 'tests') diff --git a/src/app.rs b/src/app.rs index f751716..15cdfe3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -350,7 +350,19 @@ impl App { entry.filepath.unwrap().iter().for_each(|p| { items.push(( "File (PDF/EPUB): ".into(), - p.clone().into_string().unwrap(), + // p.clone().into_string().unwrap(), + if entry.file_field && cfg.general.file_prefix.is_some() { + cfg.general + .file_prefix + .clone() + .unwrap() + .join(p) + .into_os_string() + .into_string() + .unwrap() + } else { + p.clone().into_string().unwrap() + }, )) }); } @@ -360,7 +372,7 @@ impl App { } else { self.bibiman.open_popup( PopupKind::MessageError, - Some("Selected entry has no connected ressources: "), + Some("Selected entry has no connected resources: "), Some(&entry.citekey), None, )?; @@ -389,11 +401,12 @@ 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.pdf_opener; // If necessary, replace ~ with /home dir - let file = if cfg.general.file_prefix.is_some() { - cfg.general.file_prefix.clone().unwrap().join(file) - } else { - PathBuf::from(file) - }; + // let file = if cfg.general.file_prefix.is_some() { + // cfg.general.file_prefix.clone().unwrap().join(file) + // } else { + // PathBuf::from(file) + // }; + let file = PathBuf::from(file); let file = expand_home(&file).into_os_string(); diff --git a/src/bibiman/bibisetup.rs b/src/bibiman/bibisetup.rs index 92e02fc..bf5baf5 100644 --- a/src/bibiman/bibisetup.rs +++ b/src/bibiman/bibisetup.rs @@ -53,6 +53,7 @@ pub struct BibiData { pub abstract_text: String, pub doi_url: Option, pub filepath: Option>, + pub file_field: bool, pub subtitle: Option, } @@ -115,10 +116,9 @@ impl BibiData { self.doi_url.as_ref().unwrap() } - // pub fn filepath(&self) -> &OsStr { - // self.filepath.as_ref().unwrap() - // } - + // Gets the path of the associated file for a bib entry. If one is set explicitly + // as a field, use that. If not, try to match it to one of the files found in the + // pdf_files dir. pub fn filepath(&mut self) -> Vec<&OsStr> { self.filepath .as_mut() @@ -207,19 +207,25 @@ impl BibiSetup { citekeys .iter() .enumerate() - .map(|(i, k)| BibiData { - id: i as u32, - authors: Self::get_authors(k, bibliography), - short_author: String::new(), - title: Self::get_title(k, bibliography), - year: Self::get_year(k, bibliography), - pubtype: Self::get_pubtype(k, bibliography), - keywords: Self::get_keywords(k, bibliography), - citekey: k.to_owned(), - abstract_text: Self::get_abstract(k, bibliography), - doi_url: Self::get_weblink(k, bibliography), - filepath: Self::get_filepath(k, bibliography, &mut pdf_files), - subtitle: Self::get_subtitle(k, bibliography), + .map(|(i, k)| { + let filepaths: (Option>, bool) = + { Self::get_filepath(k, bibliography, &mut pdf_files) }; + + BibiData { + id: i as u32, + authors: Self::get_authors(k, bibliography), + short_author: String::new(), + title: Self::get_title(k, bibliography), + year: Self::get_year(k, bibliography), + pubtype: Self::get_pubtype(k, bibliography), + keywords: Self::get_keywords(k, bibliography), + citekey: k.to_owned(), + abstract_text: Self::get_abstract(k, bibliography), + doi_url: Self::get_weblink(k, bibliography), + filepath: filepaths.0, + file_field: filepaths.1, + subtitle: Self::get_subtitle(k, bibliography), + } }) .collect() } @@ -354,19 +360,22 @@ impl BibiSetup { citekey: &str, biblio: &Bibliography, pdf_files: &mut Option>>, - ) -> Option> { + ) -> (Option>, bool) { if biblio.get(citekey).unwrap().file().is_ok() { - Some(vec![biblio - .get(citekey) - .unwrap() - .file() - .unwrap() - .trim() - .into()]) + ( + Some(vec![biblio + .get(citekey) + .unwrap() + .file() + .unwrap() + .trim() + .into()]), + true, + ) } else if pdf_files.is_some() { - Self::merge_filepath_or_none(&citekey, pdf_files) + (Self::merge_filepath_or_none(&citekey, pdf_files), false) } else { - None + (None, false) } } diff --git a/src/bibiman/entries.rs b/src/bibiman/entries.rs index b7c7c9b..88a1583 100644 --- a/src/bibiman/entries.rs +++ b/src/bibiman/entries.rs @@ -157,6 +157,7 @@ mod tests { abstract_text: "An abstract".to_string(), doi_url: None, filepath: None, + file_field: false, subtitle: None, }; @@ -174,6 +175,7 @@ mod tests { abstract_text: "An abstract".to_string(), doi_url: None, filepath: None, + file_field: false, subtitle: None, }; diff --git a/src/bibiman/search.rs b/src/bibiman/search.rs index 1855092..f391aed 100644 --- a/src/bibiman/search.rs +++ b/src/bibiman/search.rs @@ -117,6 +117,8 @@ pub fn search_pattern_in_file<'a>(pattern: &str, file: &'a PathBuf) -> Option<&' #[cfg(test)] mod tests { + use std::ffi::OsString; + use super::*; #[test] @@ -132,7 +134,8 @@ mod tests { citekey: "author_1999".to_string(), abstract_text: "An abstract with multiple sentences. Here is the second".to_string(), doi_url: Some("https://www.bibiman.org".to_string()), - filepath: Some("/home/file/path.pdf".to_string().into()), + filepath: Some(vec![OsString::from("/home/file/path.pdf")]), + file_field: true, subtitle: None, }; diff --git a/src/tui/popup.rs b/src/tui/popup.rs index f3f6d58..93b01c3 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -114,28 +114,6 @@ impl PopupArea { Text::from(helptext) } - /// Creates a popup message. The needed arguments are: - /// - /// - `message` as `str`: The message displayed in the popup. - /// - `object` as `str`: A possible object added to the message. E.g. the content - /// which gets copied to the clipboard. - /// - `msg_confirm` as `bool`: if `true` its a confirmation message displayed in - /// in the set `confirm_color` (default: green), if `false` its a warning - /// message displayed in the set `warn_color` (default: red). - pub fn popup_message(&mut self, message: &str, object: &str, msg_confirm: bool) { - if object.is_empty() { - self.popup_message = message.to_owned(); - } else { - self.popup_message = message.to_owned() + object; //format!("{} \"{}\"", message, object); - } - if msg_confirm { - self.popup_kind = Some(PopupKind::MessageConfirm); - } else { - self.popup_kind = Some(PopupKind::MessageError) - } - self.is_popup = true; - } - /// Opens a popup with a selectable list /// /// The list items are passed as argument of the kind `Vec<(String, String)>`. diff --git a/src/tui/ui.rs b/src/tui/ui.rs index a998bc7..2126135 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -320,16 +320,24 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { // To find the longest line, we need to collect the chars of every item // and add. - let list_widths: Vec = app + let list_widths = app .bibiman .popup_area .popup_list .iter() - .map(|(m, o)| m.chars().count() as u16 + o.chars().count() as u16) - .collect(); + .max_by(|(mes, obj), (m, o)| { + let x = mes.chars().count() + obj.chars().count(); + let y = m.chars().count() + o.chars().count(); + x.cmp(&y) + }) + .unwrap(); + // .map(|(m, o)| m.chars().count() as u16 + o.chars().count() as u16) + // .collect(); // Now take the max number for the width of the popup - let max_item = list_widths.iter().max().unwrap().to_owned(); + // let max_item = list_widths.iter().max().unwrap().to_owned(); + let max_item = + list_widths.0.chars().count() as u16 + list_widths.1.chars().count() as u16; // Check if the popup would exceed the terminal frame width let popup_width = if max_item + 2 > frame.area().width - 2 { diff --git a/tests/test-config.toml b/tests/test-config.toml index 1b60acd..4e4f8c5 100644 --- a/tests/test-config.toml +++ b/tests/test-config.toml @@ -14,7 +14,7 @@ bibfiles = [ "tests/biblatex-test.bib" ] ## Prefix which is prepended to the filepath from the `file` field ## Use absolute paths (~ for HOME works). Otherwise, loading might not work. -# file_prefix = "/some/path/prefix" +file_prefix = "/some/path/prefix" ## Path to folder (with subfolders) containing PDF files with the basename ## of the format "citekey.pdf". Other PDF basenames are not accepted. -- cgit v1.2.3