aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorlukeflo2024-12-16 13:46:24 +0100
committerlukeflo2024-12-23 21:03:19 +0100
commitbf93bbee1b59c9804a01a7476e12264bbbcf5f40 (patch)
treee8b1454a9c85e6932b443a34a4a18a02b71bfccc /src
parenta6fca1fcf164142d84d09242b9d95a1da0b2d2d9 (diff)
downloadbibiman-bf93bbee1b59c9804a01a7476e12264bbbcf5f40.tar.gz
bibiman-bf93bbee1b59c9804a01a7476e12264bbbcf5f40.zip
rewrite add-entry via DOI workflow
+ split some functions to fit with different popups + select if append to file (and to which) or create new file + error handling if resolving doi ist not possible + error handling for wront doi patterns
Diffstat (limited to 'src')
-rw-r--r--src/app.rs188
-rw-r--r--src/bibiman.rs94
-rw-r--r--src/main.rs4
-rw-r--r--src/tui/commands.rs10
-rw-r--r--src/tui/popup.rs8
-rw-r--r--src/tui/ui.rs27
6 files changed, 174 insertions, 157 deletions
diff --git a/src/app.rs b/src/app.rs
index bcdef39..a3691d6 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -17,15 +17,18 @@
use crate::bibiman::{CurrentArea, FormerArea};
use color_eyre::eyre::{Context, Ok, Result};
+use regex::Regex;
// use super::Event;
use crate::cliargs::CLIArgs;
use crate::tui::commands::InputCmdAction;
use crate::tui::popup::PopupKind;
use crate::tui::{self, Tui};
use crate::{bibiman::Bibiman, tui::commands::CmdAction};
-use ratatui::crossterm::event::KeyCode;
+use core::panic;
use std::ffi::OsStr;
-use std::path::PathBuf;
+use std::fs::{File, OpenOptions};
+use std::io::Write;
+use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use tui::Event;
use tui_input::backend::crossterm::EventHandler;
@@ -59,7 +62,7 @@ impl App {
})
}
- pub async fn run(&mut self, args: &CLIArgs) -> Result<()> {
+ pub async fn run(&mut self, args: &mut CLIArgs) -> Result<()> {
let mut tui = tui::Tui::new()?;
tui.enter()?;
@@ -80,72 +83,13 @@ impl App {
{
self.bibiman.close_popup()
}
- // else if let Some(PopupKind::AddEntry) = self.bibiman.popup_area.popup_kind {
- // // Handle key events for AddEntry popup
- // match key_event.code {
- // KeyCode::Char(c) => {
- // let index = self.bibiman.popup_area.add_entry_cursor_position;
- // self.bibiman.popup_area.add_entry_input.insert(index, c);
- // self.bibiman.popup_area.add_entry_cursor_position += 1;
- // }
- // KeyCode::Backspace => {
- // if self.bibiman.popup_area.add_entry_cursor_position > 0 {
- // self.bibiman.popup_area.add_entry_cursor_position -= 1;
- // let index = self.bibiman.popup_area.add_entry_cursor_position;
- // self.bibiman.popup_area.add_entry_input.remove(index);
- // }
- // }
- // KeyCode::Left => {
- // if self.bibiman.popup_area.add_entry_cursor_position > 0 {
- // self.bibiman.popup_area.add_entry_cursor_position -= 1;
- // }
- // }
- // KeyCode::Right => {
- // if self.bibiman.popup_area.add_entry_cursor_position
- // < self.bibiman.popup_area.add_entry_input.len()
- // {
- // self.bibiman.popup_area.add_entry_cursor_position += 1;
- // }
- // }
- // KeyCode::Enter => {
- // // Handle submission of the new entry
- // self.bibiman.handle_new_entry_submission(args, &self.input);
- // self.bibiman.close_popup();
- // self.input_mode = false;
- // }
- // KeyCode::Esc => {
- // // Close the popup without saving
- // self.bibiman.close_popup();
- // self.input_mode = false;
- // }
- // _ => {}
- // }
- // }
- else {
- let command = if self.input_mode {
- CmdAction::Input(InputCmdAction::parse(key_event, &self.input))
- } else {
- CmdAction::from(key_event)
- };
- self.run_command(command, args, &mut tui)?
- }
+ let command = if self.input_mode {
+ CmdAction::Input(InputCmdAction::parse(key_event, &self.input))
+ } else {
+ CmdAction::from(key_event)
+ };
+ self.run_command(command, args, &mut tui)?
}
- // Event::Key(key_event) => {
- // // Automatically close message popups on next keypress
- // if let Some(PopupKind::MessageConfirm) = self.bibiman.popup_area.popup_kind {
- // self.bibiman.close_popup()
- // } else if let Some(PopupKind::MessageError) = self.bibiman.popup_area.popup_kind
- // {
- // self.bibiman.close_popup()
- // }
-
- // let command = if self.input_mode {
- // CmdAction::Input(InputCmdAction::parse(key_event, &self.input))
- // } else {
- // CmdAction::from(key_event)
- // };
- // self.run_command(command, args, &mut tui)?
- // }
Event::Mouse(mouse_event) => {
self.run_command(CmdAction::from(mouse_event), args, &mut tui)?
}
@@ -169,7 +113,7 @@ impl App {
self.running = false;
}
- pub fn run_command(&mut self, cmd: CmdAction, args: &CLIArgs, tui: &mut Tui) -> Result<()> {
+ pub fn run_command(&mut self, cmd: CmdAction, args: &mut CLIArgs, tui: &mut Tui) -> Result<()> {
match cmd {
CmdAction::Input(cmd) => match cmd {
InputCmdAction::Nothing => {}
@@ -191,8 +135,19 @@ impl App {
} else if let CurrentArea::PopupArea = self.bibiman.current_area {
match self.bibiman.popup_area.popup_kind {
Some(PopupKind::AddEntry) => {
- self.bibiman.handle_new_entry_submission(args, &self.input);
+ let doi = self.input.value();
self.bibiman.close_popup();
+ self.input_mode = false;
+ // Check if the DOI pattern is valid. If not, show warning and break
+ if doi.starts_with("10.") || doi.starts_with("http") {
+ self.bibiman.handle_new_entry_submission(args, doi);
+ } else {
+ self.bibiman.popup_area.popup_message(
+ "No valid DOI pattern: ",
+ doi,
+ false,
+ );
+ }
}
_ => {}
}
@@ -218,13 +173,15 @@ impl App {
CurrentArea::TagArea => {
self.bibiman.select_next_tag(amount);
}
- CurrentArea::PopupArea => {
- if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
+ CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind {
+ Some(PopupKind::Help) => {
self.bibiman.popup_area.popup_scroll_down();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ }
+ Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => {
self.bibiman.popup_area.popup_state.scroll_down_by(1)
}
- }
+ _ => {}
+ },
_ => {}
},
CmdAction::SelectPrevRow(amount) => match self.bibiman.current_area {
@@ -235,13 +192,15 @@ impl App {
CurrentArea::TagArea => {
self.bibiman.select_previous_tag(amount);
}
- CurrentArea::PopupArea => {
- if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
+ CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind {
+ Some(PopupKind::Help) => {
self.bibiman.popup_area.popup_scroll_up();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ }
+ Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => {
self.bibiman.popup_area.popup_state.scroll_up_by(1)
}
- }
+ _ => {}
+ },
_ => {}
},
CmdAction::SelectNextCol => {
@@ -290,8 +249,10 @@ impl App {
if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
self.bibiman.popup_area.popup_scroll_pos = 0;
self.bibiman.close_popup()
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind {
self.bibiman.close_popup()
+ } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind {
+ self.bibiman.close_popup();
}
} else {
self.bibiman.reset_current_list();
@@ -303,7 +264,7 @@ impl App {
} else if let CurrentArea::PopupArea = self.bibiman.current_area {
if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
self.bibiman.close_popup();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind {
// Index of selected entry
let entry_idx = self
.bibiman
@@ -330,6 +291,55 @@ impl App {
};
// run command to open file/Url
self.bibiman.close_popup()
+ } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind {
+ // Index of selected popup field
+ let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap();
+
+ // regex pattern to match citekey in fetched bibtexstring
+ let pattern = Regex::new(r"\{([^\{\},]*),").unwrap();
+
+ let citekey = PathBuf::from(
+ pattern
+ .captures(&self.bibiman.popup_area.popup_sel_item)
+ .unwrap()
+ .get(1)
+ .unwrap()
+ .as_str(),
+ );
+
+ // Check if new file or existing file was choosen
+ let mut file = if self.bibiman.popup_area.popup_list[popup_idx]
+ .contains("Create new file")
+ {
+ // 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.bibiman.popup_area.popup_sel_item.as_bytes())?;
+ // Update the database and the lists to reflect the new content
+ self.bibiman.update_lists(args);
+ self.bibiman.close_popup();
+
+ // Select newly created entry
+ self.bibiman
+ .select_entry_by_citekey(citekey.to_str().unwrap());
}
}
}
@@ -378,6 +388,7 @@ impl App {
if entry.filepath.is_some() {
items.push("File (PDF/EPUB)".to_owned())
}
+ self.bibiman.popup_area.popup_kind = Some(PopupKind::SelectRes);
self.bibiman.popup_area.popup_selection(items);
self.bibiman.former_area = Some(FormerArea::EntryArea);
self.bibiman.current_area = CurrentArea::PopupArea;
@@ -392,8 +403,10 @@ impl App {
}
}
CmdAction::AddEntry => {
- self.input_mode = true;
- self.bibiman.add_entry();
+ if let CurrentArea::EntryArea = self.bibiman.current_area {
+ self.input_mode = true;
+ self.bibiman.add_entry();
+ }
}
CmdAction::ShowHelp => {
self.bibiman.show_help();
@@ -498,4 +511,15 @@ mod test {
assert_eq!(path, PathBuf::from(full_path))
}
+
+ #[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:!?")
+ }
}
diff --git a/src/bibiman.rs b/src/bibiman.rs
index 4e6e5e8..0dc64e0 100644
--- a/src/bibiman.rs
+++ b/src/bibiman.rs
@@ -22,7 +22,7 @@ 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::Result;
+use color_eyre::eyre::{eyre, Result};
use doi2bib;
use editor_command::EditorBuilder;
use futures::executor::block_on;
@@ -115,32 +115,32 @@ impl Bibiman {
pub fn add_entry(&mut self) {
if let CurrentArea::EntryArea = self.current_area {
self.former_area = Some(FormerArea::EntryArea);
- } else if let CurrentArea::TagArea = self.current_area {
- self.former_area = Some(FormerArea::TagArea);
}
self.popup_area.is_popup = true;
self.current_area = CurrentArea::PopupArea;
self.popup_area.popup_kind = Some(PopupKind::AddEntry);
}
- pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &Input) {
- let new_entry_title = doi_string.value();
+ ///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 doi2bib = doi2bib::Doi2Bib::new().unwrap();
- let new_entry_future = doi2bib.resolve_doi(new_entry_title);
+ let new_entry_future = doi2bib.resolve_doi(doi_string);
let new_entry = block_on(new_entry_future);
if let Ok(entry) = new_entry {
- // TODO: Add error handling for failed insert
- let formatted_content = Self::format_bibtex_entry(&entry, "");
-
- if self.append_to_file(args, &formatted_content).is_err() {
- self.popup_area.popup_kind = Some(PopupKind::MessageError);
- self.popup_area.popup_message = "Failed to add new entry".to_string();
- }
- // TODO: Add error handling for failed DOI lookup
+ // Save generated bibtex entry in structs field
+ self.popup_area.popup_sel_item = entry;
+ self.popup_area.popup_kind = Some(PopupKind::SelectFile);
+ 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_kind = Some(PopupKind::MessageError);
- self.popup_area.popup_message = "Failed to add new entry".to_string();
+ self.popup_area
+ .popup_message("Failed to add new entry", "", false);
}
}
@@ -336,6 +336,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
@@ -404,38 +424,22 @@ 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;
- }
- idx_count += 1
- }
-
- // Set selected entry to vec-index of match
- self.entry_table.entry_table_state.select(Some(idx_count));
+ // Select entry which was selected before entering editor
+ self.select_entry_by_citekey(citekey);
Ok(())
}
- pub fn append_to_file(&mut self, args: &CLIArgs, content: &str) -> Result<()> {
- // Determine the file path to append to
- let file_path = args.files.first().unwrap();
- // Open the file in append mode
- let mut file = OpenOptions::new().append(true).open(file_path).unwrap();
- // Optionally, add a newline before the content
- file.write_all(b"\n")?;
- // Format the content
- // Write the formatted content to the file
- file.write_all(content.as_bytes())?;
- // Update the database and the lists to reflect the new content
- self.update_lists(args);
- 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());
+ }
+ } else {
+ items.push(args.files.first().unwrap().to_str().unwrap().to_owned());
+ }
+ self.popup_area.popup_selection(items);
}
/// Formats a raw BibTeX entry string for better readability.
@@ -448,7 +452,7 @@ impl Bibiman {
let preamble = &entry[..start_brace_pos + 1];
let preamble = preamble.trim_start();
formatted.push_str(preamble);
- formatted.push('\n'); // Add newline
+ // formatted.push('\n'); // Add newline
// Get the content inside the braces
let rest = &entry[start_brace_pos + 1..];
diff --git a/src/main.rs b/src/main.rs
index b1160e2..78c5075 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,7 +29,7 @@ pub mod tui;
#[tokio::main]
async fn main() -> Result<()> {
// Parse CLI arguments
- let parsed_args = CLIArgs::parse_args().unwrap();
+ let mut parsed_args = CLIArgs::parse_args().unwrap();
// Print help if -h/--help flag is passed and exit
if parsed_args.helparg {
@@ -48,6 +48,6 @@ async fn main() -> Result<()> {
// Create an application.
let mut app = App::new(&parsed_args)?;
- app.run(&parsed_args).await?;
+ app.run(&mut parsed_args).await?;
Ok(())
}
diff --git a/src/tui/commands.rs b/src/tui/commands.rs
index f7fd75b..0e00f95 100644
--- a/src/tui/commands.rs
+++ b/src/tui/commands.rs
@@ -112,7 +112,6 @@ impl From<KeyEvent> for CmdAction {
Self::SelectPrevRow(5)
} else {
Self::Nothing
- // Self::Open(OpenRessource::WebLink)
}
}
// Scroll info/preview area
@@ -132,15 +131,6 @@ impl From<KeyEvent> for CmdAction {
// Switch selected area
KeyCode::Tab => Self::ToggleArea,
KeyCode::BackTab => Self::ToggleArea,
- // // Enter search mode
- // KeyCode::Char('/') => Self::Input(InputCmdAction::Enter),
- // KeyCode::Char('f') => {
- // if key_event.modifiers == KeyModifiers::CONTROL {
- // Self::Input(InputCmdAction::Enter)
- // } else {
- // Self::Nothing
- // }
- // }
// Enter search mode
KeyCode::Char('/') => Self::SearchList,
KeyCode::Char('f') => {
diff --git a/src/tui/popup.rs b/src/tui/popup.rs
index afe0cfc..352b328 100644
--- a/src/tui/popup.rs
+++ b/src/tui/popup.rs
@@ -28,7 +28,8 @@ pub enum PopupKind {
Help,
MessageConfirm,
MessageError,
- Selection,
+ SelectRes,
+ SelectFile,
AddEntry,
}
@@ -40,6 +41,7 @@ pub struct PopupArea {
pub popup_scroll_pos: u16,
pub popup_list: Vec<String>,
pub popup_state: ListState,
+ pub popup_sel_item: String,
// pub add_entry_input: String,
// pub add_entry_cursor_position: usize,
}
@@ -51,7 +53,6 @@ impl PopupArea {
("TAB: ", "Toggle areas (Entries, Keywords)"),
("/|Ctrl+f: ", "Enter search mode"),
("q|Ctrl+c: ", "Quit bibiman"),
- ("a: ", "Add new entry"),
("?: ", "Show help"),
("Entry Table", "sub"),
("j,k|↓,↑: ", "Select next/previous entry"),
@@ -64,6 +65,7 @@ impl PopupArea {
("e: ", "Open editor at selected entry"),
("o: ", "Open with selected entry associated PDF"),
("u: ", "Open DOI/URL of selected entry"),
+ ("a: ", "Add new entry"),
("ESC: ", "Reset all lists"),
("Keyword List", "sub"),
("j,k|↓,↑: ", "Select next/previous item"),
@@ -123,7 +125,7 @@ impl PopupArea {
pub fn popup_selection(&mut self, items: Vec<String>) {
self.popup_list = items;
- self.popup_kind = Some(PopupKind::Selection);
+ // self.popup_kind = Some(PopupKind::SelectRes);
self.is_popup = true;
}
diff --git a/src/tui/ui.rs b/src/tui/ui.rs
index e3fddfd..6a3b8de 100644
--- a/src/tui/ui.rs
+++ b/src/tui/ui.rs
@@ -212,25 +212,14 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
let doi_lines = paragraph.line_count(area.width / 2);
// Calculate popup size
- let popup_width = area.width / 2;
+ let popup_width = area.width / 4 * 3;
let popup_height = doi_lines as u16; // Adjust as needed
let popup_area = popup_area(area, popup_width, popup_height);
// Render the popup
frame.render_widget(Clear, popup_area);
- render_cursor(app, frame, popup_area, 6, 2);
+ render_cursor(app, frame, popup_area, 6, doi_lines as u16 - 1);
frame.render_widget(paragraph, popup_area);
-
- // // Set the cursor position
- // if app.input_mode {
- // // Calculate cursor x and y
- // let input_prefix_len = "Title: ".len() as u16 + 1; // +1 for padding
- // let cursor_x = popup_area.x
- // + input_prefix_len
- // + app.bibiman.popup_area.add_entry_cursor_position as u16;
- // let cursor_y = popup_area.y + 1; // Line after 'Title: '
- // frame.set_cursor_position(Position::new(cursor_x, cursor_y));
- // }
}
Some(PopupKind::MessageConfirm) => {
let area = frame.area();
@@ -294,7 +283,7 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
frame.render_widget(Clear, popup_area);
frame.render_widget(&content, popup_area)
}
- Some(PopupKind::Selection) => {
+ Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => {
let list_items: Vec<ListItem> = app
.bibiman
.popup_area
@@ -303,8 +292,16 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
.map(|item| ListItem::from(item.to_owned()))
.collect();
+ let title = if let Some(PopupKind::SelectRes) = app.bibiman.popup_area.popup_kind {
+ " Open "
+ } else if let Some(PopupKind::SelectFile) = app.bibiman.popup_area.popup_kind {
+ " Select file to append entry "
+ } else {
+ " Select "
+ };
+
let block = Block::bordered()
- .title_top(" Open ".bold())
+ .title_top(title.bold())
.title_bottom(" (j,k|↓,↑) ━ (ENTER) ━ (ESC) ".bold())
.title_alignment(Alignment::Center)
.style(