aboutsummaryrefslogtreecommitdiff
path: root/src/bibiman.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bibiman.rs')
-rw-r--r--src/bibiman.rs288
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:!?")
+ }
}