merge GUI #1
							
								
								
									
										45
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| { | ||||
|     // Use IntelliSense to learn about possible attributes. | ||||
|     // Hover to view descriptions of existing attributes. | ||||
|     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||
|     "version": "0.2.0", | ||||
|     "configurations": [ | ||||
|         { | ||||
|             "type": "lldb", | ||||
|             "request": "launch", | ||||
|             "name": "Debug executable 'bible'", | ||||
|             "cargo": { | ||||
|                 "args": [ | ||||
|                     "build", | ||||
|                     "--bin=bible", | ||||
|                     "--package=bible" | ||||
|                 ], | ||||
|                 "filter": { | ||||
|                     "name": "bible", | ||||
|                     "kind": "bin" | ||||
|                 } | ||||
|             }, | ||||
|             "args": [], | ||||
|             "cwd": "${workspaceFolder}" | ||||
|         }, | ||||
|         { | ||||
|             "type": "lldb", | ||||
|             "request": "launch", | ||||
|             "name": "Debug unit tests in executable 'bible'", | ||||
|             "cargo": { | ||||
|                 "args": [ | ||||
|                     "test", | ||||
|                     "--no-run", | ||||
|                     "--bin=bible", | ||||
|                     "--package=bible" | ||||
|                 ], | ||||
|                 "filter": { | ||||
|                     "name": "bible", | ||||
|                     "kind": "bin" | ||||
|                 } | ||||
|             }, | ||||
|             "args": [], | ||||
|             "cwd": "${workspaceFolder}" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										4002
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4002
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -4,6 +4,7 @@ version = "0.1.0" | ||||
| edition = "2024" | ||||
|  | ||||
| [dependencies] | ||||
| iced = "0.13.1" | ||||
| inline_colorization = "0.1.6" | ||||
| quick-xml = { version="0.37.2", features=["serialize"] } | ||||
| serde = { version = "1.0.219", features = ["derive"] } | ||||
|   | ||||
| @@ -8,3 +8,6 @@ | ||||
|  | ||||
| [HewbrewLexicon](https://github.com/openscriptures/HebrewLexicon/tree/master) | ||||
|  | ||||
| [27, 91, 49, 109, 91, 69, 110, 103, 108, 105, 115, 104, 32, 78, 65, 83, 66, 93, 32, 27, 91, 52, 109, 71, 101, 110, 101, 115, 105, 115, 32, 49, 58, 49, 27, 91, 48, 109, 58, 32, 73, 110, 32, 116, 104, 101, 32, 98, 101, 103, 105, 110, 110, 105, 110, 103, 32, 71, 111, 100, 32, 99, 114, 101, 97, 116, 101, 100, 32, 116, 104, 101, 32, 104, 101, 97, 118, 101, 110, 115, 32, 97, 110, 100, 32, 116, 104, 101, 32, 101, 97, 114, 116, 104, 46, 10] | ||||
|  | ||||
| [??, [, 1, m, [, E, n,,,] | ||||
							
								
								
									
										383
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										383
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,19 +1,390 @@ | ||||
| use std::env; | ||||
| #![feature(iter_map_windows)] | ||||
| use iced::widget::scrollable; | ||||
| use iced::{ | ||||
|     Color, Element, Length, Task, Theme, clipboard, color, | ||||
|     widget::{ | ||||
|         self, button, column, combo_box, row, | ||||
|         scrollable::{Direction, Scrollbar}, | ||||
|         text, text_input, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| mod strong; | ||||
| mod bible; | ||||
| use std::{env, fs, path::PathBuf}; | ||||
| use texts::*; | ||||
|  | ||||
| fn main() { | ||||
| mod texts; | ||||
|  | ||||
| pub const BIBLE_DIRECTORY: &str = "./Holy-Bible-XML-Format"; | ||||
|  | ||||
| fn main() -> iced::Result { | ||||
|     // CLI | ||||
|     let arg = env::args().collect::<Vec<String>>(); | ||||
|     if let Some(query) = arg.get(1) { | ||||
|         if query.starts_with("H") | query.starts_with("h") { | ||||
|             if let Some(found) = strong::get(query) { | ||||
|                 print!("{found}"); | ||||
|                 return; | ||||
|                 return Ok(()); | ||||
|             } | ||||
|         } | ||||
|         if let Some(second_query) = arg.get(2) { | ||||
|             bible::get(query,second_query); | ||||
|             if let Ok(contents) = fs::read_to_string(&format!("{BIBLE_DIRECTORY}/EnglishNASBBible.xml")) { | ||||
|                 if let Ok(bible) = quick_xml::de::from_str::<bible::Bible>(&contents) { | ||||
|                     if let Some(f) = bible::get( | ||||
|                         query, | ||||
|                         second_query, | ||||
|                         vec![&bible], | ||||
|                     ) { | ||||
|                         println!("{}", f); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return Ok(()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // ALL BUILTIN THEMES | ||||
|     //    Light | ||||
|     //    Dark | ||||
|     //    Dracula | ||||
|     //    Nord | ||||
|     //    SolarizedLight | ||||
|     //    SolarizedDark | ||||
|     //    GruvboxLight | ||||
|     //    GruvboxDark | ||||
|     //    CatppuccinLatte | ||||
|     //    CatppuccinFrappe | ||||
|     //    CatppuccinMacchiato | ||||
|     //    CatppuccinMocha | ||||
|     //    TokyoNight | ||||
|     //    TokyoNightStorm | ||||
|     //    TokyoNightLight | ||||
|     //    KanagawaWave | ||||
|     //    KanagawaDragon | ||||
|     //    KanagawaLotus | ||||
|     //    Moonfly | ||||
|     //    Nightfly | ||||
|     //    Oxocarbon | ||||
|     //    Ferra | ||||
|  | ||||
|     // GUI | ||||
|     iced::application("Bible Study", State::update, State::view) | ||||
|         .theme(|_| Theme::Nord) | ||||
|         .run() | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| enum Message { | ||||
|     BibleSearchInput(usize, String), | ||||
|     BibleSelected(usize, String), | ||||
|     BibleSearchSubmit(usize), | ||||
|     CopyText(usize), | ||||
|     Clear(usize), | ||||
|     WordSearchInput(usize, String), | ||||
|     SubmitWordSearch(usize), | ||||
|     QuickSearch(usize, String), | ||||
|     AddColRight, | ||||
|     DeleteColumn(usize), | ||||
| } | ||||
|  | ||||
| struct ColState { | ||||
|     selected_file: Option<String>, | ||||
|     bible_selected: Option<bible::Bible>, | ||||
|  | ||||
|     bible_search: String, | ||||
|     scripture_body: Option<String>, | ||||
|  | ||||
|     word_search: String, | ||||
|     word_search_results: Option<Vec<String>>, | ||||
| } | ||||
|  | ||||
| impl Default for ColState { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             selected_file: None, | ||||
|             bible_selected: None, | ||||
|  | ||||
|             bible_search: String::new(), | ||||
|             scripture_body: None, | ||||
|  | ||||
|             word_search: String::new(), | ||||
|             word_search_results: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct State { | ||||
|     files: combo_box::State<String>, | ||||
|     states: Vec<ColState>, | ||||
|     cols: usize, | ||||
| } | ||||
|  | ||||
| impl Default for State { | ||||
|     fn default() -> Self { | ||||
|         let files = if let Ok(contents) = fs::read_dir(BIBLE_DIRECTORY) { | ||||
|             let paths = contents | ||||
|                 .into_iter() | ||||
|                 .filter_map(|f| f.ok()) | ||||
|                 .map(|f| (f.path(), f.file_type())) | ||||
|                 .filter(|(_, f)| f.is_ok()) | ||||
|                 .map(|(a, b)| (a, b.unwrap())) | ||||
|                 .filter(|(_, f)| f.is_file()) | ||||
|                 .map(|(f, _)| f) | ||||
|                 .filter(|f| f.extension().is_some()) | ||||
|                 .filter(|f| f.extension().unwrap() == "xml") | ||||
|                 .collect::<Vec<PathBuf>>(); | ||||
|             paths | ||||
|         } else { | ||||
|             todo!() | ||||
|         }; | ||||
|         let files = files | ||||
|             .iter() | ||||
|             .filter_map(|f| f.to_str()) | ||||
|             .map(|f| f.to_owned()) | ||||
|             .collect::<Vec<String>>(); | ||||
|  | ||||
|         Self { | ||||
|             files: combo_box::State::new(files), | ||||
|             states: vec![ColState::default()], | ||||
|             cols: 1, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl State { | ||||
|     fn update(&mut self, msg: Message) -> Task<Message> { | ||||
|         match msg { | ||||
|             Message::DeleteColumn(idx) => { | ||||
|                 self.cols -= 1; | ||||
|                 self.states.remove(idx); | ||||
|             } | ||||
|             Message::AddColRight => { | ||||
|                 self.cols += 1; | ||||
|                 // clone this state into the new one | ||||
|                 // self.states.push(self.states.into_iter().last().unwrap().clone()) | ||||
|                 self.states.push(ColState::default()) | ||||
|             } | ||||
|             Message::BibleSelected(i, file) => { | ||||
|                 if let Ok(contents) = fs::read_to_string(&file) { | ||||
|                     if let Ok(bible) = quick_xml::de::from_str::<bible::Bible>(&contents) { | ||||
|                         // State is held technically in this variable, although | ||||
|                         // the Bible is held in memory in it's own variable. | ||||
|                         self.states[i].selected_file = Some(format!("{bible}")); | ||||
|                         self.states[i].bible_selected = Some(bible); | ||||
|                         // automatically focus the search bar when done here | ||||
|                         // return text_input::focus("bible-search"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Message::QuickSearch(i, s) => { | ||||
|                 self.states[i].bible_search = s; | ||||
|                 return self.update(Message::BibleSearchSubmit(i)); | ||||
|             } | ||||
|             Message::BibleSearchInput(i, query) => self.states[i].bible_search = query, | ||||
|             Message::WordSearchInput(i, query) => self.states[i].word_search = query, | ||||
|             Message::BibleSearchSubmit(i) => { | ||||
|                 let mut splits = self.states[i].bible_search.split_whitespace(); | ||||
|                 let book = splits.next(); | ||||
|                 let location = splits.next(); | ||||
|  | ||||
|                 if let (Some(book), Some(chap_and_ver), Some(bible)) = | ||||
|                     (book, location, &self.states[i].bible_selected) | ||||
|                 { | ||||
|                     self.states[i].scripture_body = bible::get(book, chap_and_ver, vec![bible]); | ||||
|                 } | ||||
|             } | ||||
|             Message::SubmitWordSearch(i) => { | ||||
|                 if let Some(bible) = &self.states[i].bible_selected { | ||||
|                     let res = bible::search_for_word(&self.states[i].word_search, bible); | ||||
|                     self.states[i].word_search_results = Some(res); | ||||
|                 } | ||||
|             } | ||||
|             Message::CopyText(i) => { | ||||
|                 if let Some(text) = &self.states[i].scripture_body { | ||||
|                     return clipboard::write::<Message>(parse(text)); | ||||
|                 } | ||||
|             } | ||||
|             Message::Clear(i) => { | ||||
|                 self.states[i].scripture_body = None; | ||||
|                 self.states[i].word_search_results = None; | ||||
|                 self.states[i].bible_search = String::new(); | ||||
|                 self.states[i].word_search = String::new(); | ||||
|                 // return text_input::focus("bible-search"); | ||||
|             } | ||||
|         }; | ||||
|         Task::none() | ||||
|     } | ||||
|  | ||||
|     fn view(&self) -> Element<Message> { | ||||
|         scrollable( | ||||
|             row![ | ||||
|                 row((0..self.cols).map(|col_index| { | ||||
|                     column![ | ||||
|                         // header | ||||
|                         button("Delete Column") | ||||
|                             .on_press_with(move || Message::DeleteColumn(col_index)) | ||||
|                             .width(Length::Fill) | ||||
|                             .style(button::danger), | ||||
|                         combo_box( | ||||
|                             &self.files, | ||||
|                             "Select Bible", | ||||
|                             self.states[col_index].selected_file.as_ref(), | ||||
|                             move |s| Message::BibleSelected(col_index, s) | ||||
|                         ), | ||||
|                         text_input( | ||||
|                             "Search query, ie: Gen 1:1", | ||||
|                             &self.states[col_index].bible_search | ||||
|                         ) | ||||
|                         .on_input(move |s| Message::BibleSearchInput(col_index, s)) | ||||
|                         .on_submit(Message::BibleSearchSubmit(col_index)), | ||||
|                         widget::text_input("Word Search", &self.states[col_index].word_search) | ||||
|                             .on_input(move |s| Message::WordSearchInput(col_index, s)) | ||||
|                             .on_submit(Message::SubmitWordSearch(col_index)), | ||||
|                         row![ | ||||
|                             button("Clear All") | ||||
|                                 .on_press_with(move || Message::Clear(col_index)) | ||||
|                                 .style(button::secondary), | ||||
|                             button("Copy Scripture") | ||||
|                                 .on_press_with(move || Message::CopyText(col_index)) | ||||
|                                 .style(button::primary), | ||||
|                         ] | ||||
|                         .spacing(5), | ||||
|                         row![ | ||||
|                             // Word search results | ||||
|                             scrollable( | ||||
|                                 column( | ||||
|                                     self.states[col_index] | ||||
|                                         .word_search_results | ||||
|                                         .clone() | ||||
|                                         .unwrap_or(Vec::new()) | ||||
|                                         .into_iter() | ||||
|                                         .map(|i| button(text(i.clone())) | ||||
|                                             .on_press_with(move || Message::QuickSearch( | ||||
|                                                 col_index, | ||||
|                                                 i.to_owned() | ||||
|                                             )) | ||||
|                                             .into()) | ||||
|                                 ) | ||||
|                                 .padding([5, 5]) | ||||
|                             ) | ||||
|                             .spacing(5), | ||||
|                             // Body | ||||
|                             scrollable( | ||||
|                                 if let Some(body) = &self.states[col_index].scripture_body { | ||||
|                                     column(body.split("\n").enumerate().map(|(i, msg)| { | ||||
|                                         let msg = parse(msg); | ||||
|                                         if i & 1 == 0 { | ||||
|                                             text(msg.to_owned()).color(color![0xFFFFFF]).into() | ||||
|                                         } else { | ||||
|                                             text(msg.to_owned()).color(color![0xAAAAAA]).into() | ||||
|                                         } | ||||
|                                     })) | ||||
|                                 } else { | ||||
|                                     column( | ||||
|                                         Vec::<String>::new() | ||||
|                                             .iter() | ||||
|                                             .map(|_| text(String::new()).into()), | ||||
|                                     ) | ||||
|                                 } | ||||
|                             ) | ||||
|                             .spacing(5) | ||||
|                         ], | ||||
|                     ] | ||||
|                     .spacing(5) | ||||
|                     .width(800.) | ||||
|                     .into() | ||||
|                 })), | ||||
|                 button(text("+").center()) | ||||
|                     .height(Length::Fixed(200.)) | ||||
|                     .style(button::primary) | ||||
|                     .on_press(Message::AddColRight) | ||||
|             ] | ||||
|             // 5 pixles css-like padding | ||||
|             .padding([5, 5]) | ||||
|             // space elements inside this 5 pixels apart | ||||
|             .spacing(5), | ||||
|         ) | ||||
|         .direction(Direction::Horizontal(Scrollbar::new())) | ||||
|         .into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // parse out terminal style codes | ||||
| fn parse(input: &str) -> String { | ||||
|     let magic_start_indices = input | ||||
|         .bytes() | ||||
|         .into_iter() | ||||
|         .enumerate() | ||||
|         .map_windows(|[(ai, a), (_, b)]| { | ||||
|             // 27 91 is the start sequence for a color/style | ||||
|             if *a == 27 && *b == 91 { | ||||
|                 Some(*ai) | ||||
|             } else { | ||||
|                 None | ||||
|             } | ||||
|         }) | ||||
|         .filter_map(|f| f) | ||||
|         .collect::<Vec<usize>>(); | ||||
|  | ||||
|     let mut to_remove = Vec::new(); | ||||
|     for start in magic_start_indices { | ||||
|         let range = &input[start..]; | ||||
|         // "m" is the end of sequence | ||||
|         if let Some(end) = range.find("m") { | ||||
|             let end = start + end; | ||||
|             let code = &input[start..=end]; | ||||
|  | ||||
|             match code { | ||||
|                 "\x1B[1m" => {}   //style_bold | ||||
|                 "\x1B[4m" => {}   //style_underline | ||||
|                 "\x1B[0m" => {}   //style_reset | ||||
|                 "\x1B[30m" => {}  //color_black | ||||
|                 "\x1B[31m" => {}  //color_red | ||||
|                 "\x1B[32m" => {}  //color_green | ||||
|                 "\x1B[33m" => {}  //color_yellow | ||||
|                 "\x1B[34m" => {}  //color_blue | ||||
|                 "\x1B[35m" => {}  //color_magenta | ||||
|                 "\x1B[36m" => {}  //color_cyan | ||||
|                 "\x1B[37m" => {}  //color_white | ||||
|                 "\x1B[90m" => {}  //color_bright_black | ||||
|                 "\x1B[91m" => {}  //color_bright_red | ||||
|                 "\x1B[92m" => {}  //color_bright_green | ||||
|                 "\x1B[93m" => {}  //color_bright_yellow | ||||
|                 "\x1B[94m" => {}  //color_bright_blue | ||||
|                 "\x1B[95m" => {}  //color_bright_magenta | ||||
|                 "\x1B[96m" => {}  //color_bright_cyan | ||||
|                 "\x1B[97m" => {}  //color_bright_white | ||||
|                 "\x1B[39m" => {}  //color_reset | ||||
|                 "\x1B[40m" => {}  //bg_black | ||||
|                 "\x1B[41m" => {}  //bg_red | ||||
|                 "\x1B[42m" => {}  //bg_green | ||||
|                 "\x1B[43m" => {}  //bg_yellow | ||||
|                 "\x1B[44m" => {}  //bg_blue | ||||
|                 "\x1B[45m" => {}  //bg_magenta | ||||
|                 "\x1B[46m" => {}  //bg_cyan | ||||
|                 "\x1B[47m" => {}  //bg_white | ||||
|                 "\x1B[100m" => {} //bg_bright_black | ||||
|                 "\x1B[101m" => {} //bg_bright_red | ||||
|                 "\x1B[102m" => {} //bg_bright_green | ||||
|                 "\x1B[103m" => {} //bg_bright_yellow | ||||
|                 "\x1B[104m" => {} //bg_bright_blue | ||||
|                 "\x1B[105m" => {} //bg_bright_magenta | ||||
|                 "\x1B[106m" => {} //bg_bright_cyan | ||||
|                 "\x1B[107m" => {} //bg_bright_white | ||||
|                 "\x1B[49m" => {}  //bg_reset | ||||
|                 _ => {} | ||||
|             } | ||||
|             to_remove.push((start, end)); | ||||
|         } | ||||
|     } | ||||
|     to_remove.reverse(); | ||||
|     let mut modified = input.to_owned(); | ||||
|  | ||||
|     for (s, e) in to_remove { | ||||
|         let front = modified[..s].to_string(); | ||||
|         let back = modified[e + 1..].to_string(); | ||||
|         modified = front + &back; | ||||
|     } | ||||
|  | ||||
|     modified | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| use inline_colorization::*; | ||||
| use std::{fmt::Display, fs}; | ||||
| use std::fmt::Display; | ||||
| 
 | ||||
| use quick_xml::de::from_str; | ||||
| use serde::Deserialize; | ||||
| 
 | ||||
| const BOOKS_IN_ORDER: [&str; 66] = [ | ||||
| pub const BOOKS_IN_ORDER: [&str; 66] = [ | ||||
|     "Genesis", | ||||
|     "Exodus", | ||||
|     "Leviticus", | ||||
| @@ -13,12 +12,12 @@ const BOOKS_IN_ORDER: [&str; 66] = [ | ||||
|     "Joshua", | ||||
|     "Judges", | ||||
|     "Ruth", | ||||
|     "1 Samuel", | ||||
|     "2 Samuel", | ||||
|     "1 Kings", | ||||
|     "2 Kings", | ||||
|     "1 Chronicles", | ||||
|     "2 Chronicles", | ||||
|     "1-Samuel", | ||||
|     "2-Samuel", | ||||
|     "1-Kings", | ||||
|     "2-Kings", | ||||
|     "1-Chronicles", | ||||
|     "2-Chronicles", | ||||
|     "Ezra", | ||||
|     "Nehemiah", | ||||
|     "Esther", | ||||
| @@ -26,7 +25,7 @@ const BOOKS_IN_ORDER: [&str; 66] = [ | ||||
|     "Psalms", | ||||
|     "Proverbs", | ||||
|     "Ecclesiastes", | ||||
|     "Song of Solomon", | ||||
|     "Song-of-Solomon", | ||||
|     "Isaiah", | ||||
|     "Jeremiah", | ||||
|     "Lamentations", | ||||
| @@ -50,43 +49,53 @@ const BOOKS_IN_ORDER: [&str; 66] = [ | ||||
|     "John", | ||||
|     "Acts", | ||||
|     "Romans", | ||||
|     "1 Corinthians", | ||||
|     "2 Corinthians", | ||||
|     "1-Corinthians", | ||||
|     "2-Corinthians", | ||||
|     "Galations", | ||||
|     "Ephesians", | ||||
|     "Philippians", | ||||
|     "Colossians", | ||||
|     "1 Thessalonians", | ||||
|     "2 Thessalonians", | ||||
|     "1 Timothy", | ||||
|     "2 Timothy", | ||||
|     "1-Thessalonians", | ||||
|     "2-Thessalonians", | ||||
|     "1-Timothy", | ||||
|     "2-Timothy", | ||||
|     "Titus", | ||||
|     "Philemon", | ||||
|     "Hebrews", | ||||
|     "James", | ||||
|     "1 Peter", | ||||
|     "2 Peter", | ||||
|     "1 John", | ||||
|     "2 John", | ||||
|     "3 John", | ||||
|     "1-Peter", | ||||
|     "2-Peter", | ||||
|     "1-John", | ||||
|     "2-John", | ||||
|     "3-John", | ||||
|     "Jude", | ||||
|     "Revelation", | ||||
| ]; | ||||
| 
 | ||||
| // This would eventaully be a list built by the user
 | ||||
| const BIBLES: [&str; 2] = [ | ||||
|     "/usr/local/bible/EnglishNASBBible.xml", | ||||
|     "/usr/local/bible/EnglishNIVBible.xml", | ||||
| ]; | ||||
| pub fn search_for_word(word: &str, from_files: &Bible) -> Vec<String> { | ||||
|     let mut found = Vec::new(); | ||||
|     for test in &from_files.testaments { | ||||
|         for book in &test.books { | ||||
|             for chapter in &book.chapters { | ||||
|                 for verse in &chapter.verses { | ||||
|                     if verse.text.to_lowercase().contains(&word.to_lowercase()) { | ||||
|                         found.push(format!("{} {}:{}", BOOKS_IN_ORDER[book.number-1], chapter.number, verse.number)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     found | ||||
| } | ||||
| 
 | ||||
| pub fn get(query: &str, loc: &str) { | ||||
| pub fn get(book: &str, chap_and_ver: &str, bibles: Vec<&Bible>) -> Option<String> { | ||||
|     // expecting query to be in format:
 | ||||
|     // Gen 1:2
 | ||||
|     // Gen 1:2-5
 | ||||
|     // Gen 1
 | ||||
|     // The book name needs to be just long enough to be unique
 | ||||
| 
 | ||||
|     let mut splits = loc.split(':'); | ||||
|     let mut splits = chap_and_ver.split(':'); | ||||
|     let chapter = splits.next(); | ||||
|     let verse = splits.next(); | ||||
| 
 | ||||
| @@ -95,37 +104,38 @@ pub fn get(query: &str, loc: &str) { | ||||
|     let res = BOOKS_IN_ORDER | ||||
|         .iter() | ||||
|         .enumerate() | ||||
|         .filter(|(_, book)| book.to_lowercase().contains(&query.to_lowercase())) | ||||
|         .filter(|(_, lbook)| lbook | ||||
|             .to_lowercase() | ||||
|             .starts_with(&book | ||||
|                 .trim() | ||||
|                 .replace(" ", "-") | ||||
|                 .to_lowercase() | ||||
|                 ) | ||||
|             ) | ||||
|         .collect::<Vec<(usize, &&str)>>(); | ||||
| 
 | ||||
|     let (book_idx, book_name) = match res.len() { | ||||
|         1 => res[0], | ||||
|         2.. => { | ||||
|             eprintln!("Err: Ambigious input '{query}', could be any of:"); | ||||
|             eprintln!("Err: Ambigious input '{book}', could be any of:"); | ||||
|             for (_, i) in &res { | ||||
|                 eprintln!("\t{i}"); | ||||
|             } | ||||
|             return; | ||||
|             return None; | ||||
|         } | ||||
|         _ => { | ||||
|             eprintln!("'{query}' is not a book"); | ||||
|             return; | ||||
|             eprintln!("'{book}' is not a book"); | ||||
|             return None; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Load Bibles into memory
 | ||||
|     let bibles = BIBLES | ||||
|         .iter() | ||||
|         .filter_map(|path| fs::read_to_string(path).ok()) | ||||
|         .filter_map(|contents| from_str::<Bible>(&contents).ok()) | ||||
|         .collect::<Vec<Bible>>(); | ||||
|     // Select the book in each Bible
 | ||||
|     let books = bibles | ||||
|         .iter() | ||||
|         // Book are 1 indexed in the xml spec
 | ||||
|         .map(|bible| (bible, bible.get_book_by_index(book_idx + 1))) | ||||
|         .filter(|(_,e)| e.is_some()) | ||||
|         .map(|(bible,book)| (bible,book.unwrap())) | ||||
|         .map(|(bible,book)| (*bible,book.unwrap())) | ||||
|         .collect::<Vec<(&Bible,&Book)>>(); | ||||
| 
 | ||||
|     // Select the chapter in each Bible
 | ||||
| @@ -138,10 +148,10 @@ pub fn get(query: &str, loc: &str) { | ||||
|                 .map(|(bible,book,chapter)| (*bible,*book,chapter.unwrap())) | ||||
|                 .collect::<Vec<(&Bible, &Book, &Chapter)>>() | ||||
|         } else { | ||||
|             return; | ||||
|             return None; | ||||
|         } | ||||
|     } else { | ||||
|         return; | ||||
|         return None; | ||||
|     }; | ||||
| 
 | ||||
|     // Get the verse in each Bible
 | ||||
| @@ -155,46 +165,54 @@ pub fn get(query: &str, loc: &str) { | ||||
|                 // range of verses
 | ||||
|                 (Some(sn), Some(en)) => { | ||||
|                     if let (Ok(start), Ok(end)) = (sn.parse::<usize>(), en.parse::<usize>()) { | ||||
|                         let mut buf = String::new(); | ||||
|                         for num in start..=end { | ||||
|                             for (bible, _book, chapter) in &chapters { | ||||
|                                 if let Some(verse) = chapter.get_verse_by_index(num) { | ||||
|                                     println!( | ||||
|                                         "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}", | ||||
|                                     buf += &format!( | ||||
|                                         "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n", | ||||
|                                         bible.translation_name, chapter.number, verse.number | ||||
|                                     ); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         return Some(buf); | ||||
|                     } | ||||
|                     return None; | ||||
|                 } | ||||
|                 // only one verse
 | ||||
|                 (Some(ver_num), None) => { | ||||
|                     if let Ok(num) = ver_num.parse::<usize>() { | ||||
|                         let mut buf = String::new(); | ||||
|                         for (bible, _book, chapter) in chapters { | ||||
|                             if let Some(verse) = chapter.get_verse_by_index(num) { | ||||
|                                 println!( | ||||
|                                     "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}", | ||||
|                                 buf += &format!( | ||||
|                                     "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n", | ||||
|                                     bible.translation_name, chapter.number, verse.number | ||||
|                                 ); | ||||
|                             } | ||||
|                         } | ||||
|                         return Some(buf); | ||||
|                     } | ||||
|                     return None; | ||||
|                 } | ||||
|                 _ => { | ||||
|                     // couldn't parse verse
 | ||||
|                     return; | ||||
|                     return None; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         None => { | ||||
|             // only chapter
 | ||||
|             for (bible, _book, chapter) in chapters { | ||||
|                 println!( | ||||
|                     "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:", | ||||
|             let mut buf = String::new(); | ||||
|             for (bible, _, chapter) in chapters { | ||||
|                 buf += &format!( | ||||
|                     "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:\n", | ||||
|                     bible.translation_name, chapter.number | ||||
|                 ); | ||||
|                 print!("{}", chapter); | ||||
|                 buf += &format!("{}", chapter); | ||||
|             } | ||||
|             return Some(buf); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -235,12 +253,17 @@ impl Chapter { | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct Bible { | ||||
| pub struct Bible { | ||||
|     #[serde(rename = "@translation")] | ||||
|     translation_name: String, | ||||
|     #[serde(rename = "testament")] | ||||
|     testaments: Vec<Testament>, | ||||
| } | ||||
| impl Display for Bible { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "{}",self.translation_name) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct Testament { | ||||
							
								
								
									
										2
									
								
								src/texts/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/texts/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| pub mod bible; | ||||
| pub mod strong; | ||||
		Reference in New Issue
	
	Block a user