diff options
Diffstat (limited to 'src/bibiman.rs')
| -rw-r--r-- | src/bibiman.rs | 288 |
1 files changed, 274 insertions, 14 deletions
diff --git a/src/bibiman.rs b/src/bibiman.rs index bba3eec..0c0d99b 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -15,6 +15,7 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// +use crate::app; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{bibisetup::*, search::BibiSearch}; use crate::cliargs::CLIArgs; @@ -22,11 +23,17 @@ use crate::tui::popup::{PopupArea, PopupKind}; use crate::tui::Tui; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; -use color_eyre::eyre::{Ok, Result}; +use color_eyre::eyre::Result; use editor_command::EditorBuilder; +use futures::executor::block_on; use ratatui::widgets::ScrollbarState; +use regex::Regex; use std::fs; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::PathBuf; use std::process::Command; +use std::result::Result::Ok; use tui_input::Input; pub mod bibisetup; @@ -107,6 +114,48 @@ 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(); @@ -299,6 +348,26 @@ impl Bibiman { } } + pub fn select_entry_by_citekey(&mut self, citekey: &str) { + // Search for entry by matching citekeys + let mut idx_count = 0; + loop { + if idx_count == self.entry_table.entry_table_items.len() { + idx_count = 0; + break; + } else if self.entry_table.entry_table_items[idx_count] + .citekey + .contains(citekey) + { + break; + } + idx_count += 1 + } + + // Set selected entry to vec-index of match + self.entry_table.entry_table_state.select(Some(idx_count)); + } + pub fn run_editor(&mut self, args: &CLIArgs, tui: &mut Tui) -> Result<()> { // get filecontent and citekey for calculating line number let citekey: &str = &self.entry_table.entry_table_items @@ -367,25 +436,205 @@ impl Bibiman { // Update the database and the lists to show changes Self::update_lists(self, args); - // Search for entry, selected before editing, by matching citekeys - // Use earlier saved copy of citekey to match - let mut idx_count = 0; - loop { - if self.entry_table.entry_table_items[idx_count] - .citekey - .contains(citekey) - { - break; + // Select entry which was selected before entering editor + self.select_entry_by_citekey(citekey); + + Ok(()) + } + + pub fn append_to_file(&mut self, args: &CLIArgs) { + let mut items = vec!["Create new file".to_owned()]; + if args.files.len() > 1 { + for f in args.files.clone() { + items.push(f.to_str().unwrap().to_owned()); } - idx_count += 1 + } else { + items.push(args.files.first().unwrap().to_str().unwrap().to_owned()); } + self.popup_area.popup_selection(items); + } - // Set selected entry to vec-index of match - self.entry_table.entry_table_state.select(Some(idx_count)); + pub fn append_entry_to_file(&mut self, args: &mut CLIArgs) -> Result<()> { + // Index of selected popup field + let popup_idx = self.popup_area.popup_state.selected().unwrap(); + + // regex pattern to match citekey in fetched bibtexstring + let pattern = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let citekey = pattern + .captures(&self.popup_area.popup_sel_item) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string(); + + // Check if new file or existing file was choosen + 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() + } else { + dirs::home_dir().unwrap() // home dir as fallback + }; + + let citekey = citekey.with_extension("bib"); + + let newfile = path.join(citekey); + + args.files.push(newfile.clone()); + + File::create_new(newfile).unwrap() + } else { + let file_path = &args.files[popup_idx - 1]; + OpenOptions::new().append(true).open(file_path).unwrap() + }; + // Optionally, add a newline before the content + file.write_all(b"\n")?; + // 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.close_popup(); + + // Select newly created entry + self.select_entry_by_citekey(&citekey); + + Ok(()) + } + + pub fn open_connected_res(&mut self) -> Result<()> { + // Index of selected entry + let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); + + // Index of selected popup field + let popup_idx = self.popup_area.popup_state.selected().unwrap(); + + // Choose ressource depending an selected popup field + 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)?; + } 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)?; + } else { + eprintln!("Unable to find ressource to open"); + }; + // run command to open file/Url + self.close_popup(); Ok(()) } + /// Formats a raw BibTeX entry string for better readability. + pub fn format_bibtex_entry(entry: &str, file_path: &str) -> String { + let mut formatted = String::new(); + + // Find the position of the first '{' + if let Some(start_brace_pos) = entry.find('{') { + // Extract the preamble (e.g., '@article{') + let preamble = &entry[..start_brace_pos + 1]; + let preamble = preamble.trim_start(); + formatted.push_str(preamble); + // formatted.push('\n'); // Add newline + + // Get the content inside the braces + let rest = &entry[start_brace_pos + 1..]; + // Remove the last '}' at the end, if present + let rest = rest.trim_end(); + let rest = if rest.ends_with('}') { + &rest[..rest.len() - 1] + } else { + rest + }; + + // Parse the fields, considering braces and quotes + let mut fields = Vec::new(); + let mut current_field = String::new(); + let mut brace_level = 0; + let mut in_quotes = false; + for c in rest.chars() { + match c { + '{' if !in_quotes => { + brace_level += 1; + current_field.push(c); + } + '}' if !in_quotes => { + brace_level -= 1; + current_field.push(c); + } + '"' => { + in_quotes = !in_quotes; + current_field.push(c); + } + ',' if brace_level == 0 && !in_quotes => { + // Outside of braces and quotes, comma separates fields + fields.push(current_field.trim().to_string()); + current_field.clear(); + } + _ => { + current_field.push(c); + } + } + } + // Add the last field + if !current_field.trim().is_empty() { + fields.push(current_field.trim().to_string()); + } + + // **Conditionally Clean the Citation Key** + if let Some(citation_key) = fields.get_mut(0) { + // Check if the citation key contains any non-alphanumerical characters except underscores + let needs_cleaning = citation_key + .chars() + .any(|c| !c.is_alphanumeric() && c != '_'); + if needs_cleaning { + // Retain only alphanumerical characters and underscores + let cleaned_key: String = citation_key + .chars() + .filter(|c| c.is_alphanumeric() || *c == '_') + .collect(); + // If the cleaned key is longer than 14 characters, retain only the last 14 + let limited_key = if cleaned_key.len() > 14 { + cleaned_key + .chars() + .rev() + .take(14) + .collect::<String>() + .chars() + .rev() + .collect() + } else { + cleaned_key + }; + // Replace the original citation key with the cleaned and possibly limited key + *citation_key = limited_key; + } + } + + // Add the new 'file' field + let file_field = format!("file = {{{}}}", file_path); + fields.push(file_field); + + // Reconstruct the entry with proper indentation + for (i, field) in fields.iter().enumerate() { + formatted.push_str(" "); + formatted.push_str(field); + // Add a comma if it's not the last field + if i < fields.len() - 1 { + formatted.push(','); + } + formatted.push('\n'); + } + formatted.push('}'); // Close the entry + formatted + } else { + // No opening brace found, return the entry as is + entry.to_string() + } + } // Search entry list pub fn search_entries(&mut self) { // Use snapshot of entry list saved when starting the search @@ -594,7 +843,7 @@ impl Bibiman { #[cfg(test)] mod tests { - // use super::*; + use super::*; #[test] fn citekey_pattern() { @@ -602,4 +851,15 @@ mod tests { assert_eq!(citekey, "{a_key_2001,") } + + #[test] + fn regex_capture_citekey() { + let re = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}"); + + let result = re.captures(&bibstring).unwrap(); + + assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?") + } } |
