From dc537b3865b59da28de0e754fe9a79efcaf618f6 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Wed, 25 Dec 2024 21:16:19 +0100 Subject: Bugfix: Handle citekey duplicates + When adding an entry via DOI check if created citekey is already used + If so, loop over alphabetic chars and append it to citekey + When a unique combination is found, break loop and replace original citekey --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bibiman.rs | 133 ++++++++++++++++++++++++++++++++++++++------------------- 3 files changed, 90 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bf0ac3..37b48be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bibiman" -version = "0.9.0" +version = "0.9.1" dependencies = [ "arboard", "biblatex", diff --git a/Cargo.toml b/Cargo.toml index bf3a680..3e28ba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bibiman" -version = "0.9.0" +version = "0.9.1" authors = ["lukeflo "] license = "GPL-3.0-or-later" repository = "https://codeberg.org/lukeflo/bibiman" diff --git a/src/bibiman.rs b/src/bibiman.rs index 5c2b666..7fd98cd 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -27,7 +27,7 @@ use color_eyre::eyre::Result; use editor_command::EditorBuilder; use ratatui::widgets::ScrollbarState; use regex::Regex; -use std::fs; +use std::fs::{self, read_to_string}; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; @@ -113,48 +113,6 @@ impl Bibiman { self.popup_area.popup_kind = Some(PopupKind::Help); } - pub fn add_entry(&mut self) { - if let CurrentArea::EntryArea = self.current_area { - self.former_area = Some(FormerArea::EntryArea); - } - self.popup_area.is_popup = true; - self.current_area = CurrentArea::PopupArea; - self.popup_area.popup_kind = Some(PopupKind::AddEntry); - } - - ///Try to resolve entered DOI. If successfull, choose file where to append - ///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) { - let doi_string = if doi_string.starts_with("10.") { - "https://doi.org/".to_string() + doi_string - } else { - doi_string.to_owned() - }; - - // Send GET request to doi resolver - let doi_entry = ureq::get(&doi_string) - .set("Accept", "application/x-bibtex") - .call(); - - if let Ok(entry) = doi_entry { - // Save generated bibtex entry in structs field - let entry = entry - .into_string() - .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.former_area = Some(FormerArea::EntryArea); - self.current_area = CurrentArea::PopupArea; - self.popup_area.popup_state.select(Some(0)) - } else { - self.popup_area - .popup_message("Can't find DOI: ", &doi_string, false); - } - } - pub fn close_popup(&mut self) { // Reset all popup fields to default values self.popup_area = PopupArea::default(); @@ -441,6 +399,48 @@ impl Bibiman { Ok(()) } + pub fn add_entry(&mut self) { + if let CurrentArea::EntryArea = self.current_area { + self.former_area = Some(FormerArea::EntryArea); + } + self.popup_area.is_popup = true; + self.current_area = CurrentArea::PopupArea; + self.popup_area.popup_kind = Some(PopupKind::AddEntry); + } + + ///Try to resolve entered DOI. If successfull, choose file where to append + ///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) { + let doi_string = if doi_string.starts_with("10.") { + "https://doi.org/".to_string() + doi_string + } else { + doi_string.to_owned() + }; + + // Send GET request to doi resolver + let doi_entry = ureq::get(&doi_string) + .set("Accept", "application/x-bibtex") + .call(); + + if let Ok(entry) = doi_entry { + // Save generated bibtex entry in structs field + let entry = entry + .into_string() + .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.former_area = Some(FormerArea::EntryArea); + self.current_area = CurrentArea::PopupArea; + self.popup_area.popup_state.select(Some(0)) + } else { + self.popup_area + .popup_message("Can't find DOI: ", &doi_string, false); + } + } + pub fn append_to_file(&mut self, args: &CLIArgs) { let mut items = vec!["Create new file".to_owned()]; if args.files.len() > 1 { @@ -487,6 +487,34 @@ impl Bibiman { File::create_new(newfile).unwrap() } else { let file_path = &args.files[popup_idx - 1]; + + // Check if similar citekey already exists + let file_string = read_to_string(&file_path).unwrap(); + + // If choosen file contains entry with fetched citekey, append an + // char to the citekey so no dublettes are created + if file_string.contains(&citekey) { + let mut new_citekey = String::new(); + + // Loop over ASCII alpabetic chars and check again if citekey with + // appended char exists. If yes, move to next char and test again. + // If the citekey is free, use it and break the loop + for c in b'a'..=b'z' { + let append_char = (c as char).to_string(); + new_citekey = citekey.clone() + &append_char; + if !file_string.contains(&new_citekey) { + break; + } + } + + let new_entry_string_clone = self.popup_area.popup_sel_item.clone(); + + // Replace the double citekey with newly created + self.popup_area.popup_sel_item = pattern + .replace(&new_entry_string_clone, format!("{{{},", &new_citekey)) + .to_string(); + } + OpenOptions::new().append(true).open(file_path).unwrap() }; // Optionally, add a newline before the content @@ -842,6 +870,8 @@ impl Bibiman { #[cfg(test)] mod tests { + use regex::Captures; + use super::*; #[test] @@ -857,8 +887,21 @@ mod tests { let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}"); - let result = re.captures(&bibstring).unwrap(); + let citekey = re.captures(&bibstring).unwrap().get(1).unwrap().as_str(); + + assert_eq!(citekey, "citekey77_2001:!?"); - assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?") + if bibstring.contains(&citekey) { + let append_char = "a"; + let new_entry_string_clone = bibstring.clone(); + + let updated_bibstring = re + .replace(&new_entry_string_clone, |caps: &Captures| { + format!("{{{}{},", &caps[1], &append_char) + }) + .to_string(); + + assert_eq!(updated_bibstring, "@article{citekey77_2001:!?a, author = {Hanks, Tom}, title = {A great book}, year = {2001}}") + } } } -- cgit v1.2.3