Compare commits
	
		
			18 Commits
		
	
	
		
			94e9b38552
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d9941b38b5 | |||
| 7442a14a8f | |||
| 
						 | 
					165d946e0d | ||
| 
						 | 
					d9150c302f | ||
| 
						 | 
					46e4030f46 | ||
| 
						 | 
					9c756f3550 | ||
| 
						 | 
					035bd21466 | ||
| 
						 | 
					ba260b02bc | ||
| 
						 | 
					141da7c17c | ||
| 
						 | 
					e487bb9a69 | ||
| 2e6eb194f8 | |||
| 82de9a07cc | |||
| 
						 | 
					8a7804e780 | ||
| 
						 | 
					acb49d7a4d | ||
| 
						 | 
					727b2f0b51 | ||
| 789c593f7e | |||
| 6d24b304ce | |||
| 11eb29df4f | 
							
								
								
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -3,4 +3,4 @@
 | 
			
		||||
	url = https://github.com/openscriptures/HebrewLexicon.git
 | 
			
		||||
[submodule "Holy-Bible-XML-Format"]
 | 
			
		||||
	path = Holy-Bible-XML-Format
 | 
			
		||||
	url = https://github.com/Beblia/Holy-Bible-XML-Format.git
 | 
			
		||||
	url = https://github.com/Rushmore75/Holy-Bible-XML-Format.git
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										39
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -48,6 +48,15 @@ dependencies = [
 | 
			
		||||
 "zerocopy 0.7.35",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "aho-corasick"
 | 
			
		||||
version = "1.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "allocator-api2"
 | 
			
		||||
version = "0.2.21"
 | 
			
		||||
@@ -288,6 +297,7 @@ dependencies = [
 | 
			
		||||
 "iced",
 | 
			
		||||
 "inline_colorization",
 | 
			
		||||
 "quick-xml",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -2546,6 +2556,35 @@ dependencies = [
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex"
 | 
			
		||||
version = "1.11.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "regex-automata",
 | 
			
		||||
 "regex-syntax",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-automata"
 | 
			
		||||
version = "0.4.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "regex-syntax",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.8.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "renderdoc-sys"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,4 +7,5 @@ edition = "2024"
 | 
			
		||||
iced = "0.13.1"
 | 
			
		||||
inline_colorization = "0.1.6"
 | 
			
		||||
quick-xml = { version="0.37.2", features=["serialize"] }
 | 
			
		||||
regex = "1.11.1"
 | 
			
		||||
serde = { version = "1.0.219", features = ["derive"] }
 | 
			
		||||
 
 | 
			
		||||
 Submodule Holy-Bible-XML-Format updated: 69ac8b94c9...7413606dd5
									
								
							
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@@ -1,13 +1,13 @@
 | 
			
		||||
# cli bible
 | 
			
		||||
# Bible
 | 
			
		||||
 | 
			
		||||
## Important Links
 | 
			
		||||
 | 
			
		||||
[Tanach.us](https://tanach.us)
 | 
			
		||||
 | 
			
		||||
[XML bible in 190+ languages](https://github.com/Beblia/Holy-Bible-XML-Format)
 | 
			
		||||
[XML Bibles in 190+ languages](https://github.com/Beblia/Holy-Bible-XML-Format)
 | 
			
		||||
 | 
			
		||||
[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]
 | 
			
		||||
## Could be useful later links
 | 
			
		||||
 | 
			
		||||
[??, [, 1, m, [, E, n,,,]
 | 
			
		||||
[Tanach.us](https://tanach.us)
 | 
			
		||||
 | 
			
		||||
[AndBible's formats](https://github.com/AndBible/and-bible/wiki/Supported-3rd-party-module-formats)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										372
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,14 +1,19 @@
 | 
			
		||||
#![feature(iter_map_windows)]
 | 
			
		||||
use iced::widget::scrollable;
 | 
			
		||||
use iced::keyboard::key::Named;
 | 
			
		||||
use iced::keyboard::{Key, Modifiers};
 | 
			
		||||
use iced::{event, Event, Subscription};
 | 
			
		||||
use iced::widget::{scrollable, text_editor};
 | 
			
		||||
use iced::{
 | 
			
		||||
    Color, Element, Length, Task, Theme, clipboard, color,
 | 
			
		||||
    Element, Length, Task, Theme, clipboard, color,
 | 
			
		||||
    widget::{
 | 
			
		||||
        self, button, column, combo_box, row,
 | 
			
		||||
        button, column, combo_box, row,
 | 
			
		||||
        scrollable::{Direction, Scrollbar},
 | 
			
		||||
        text, text_input,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use std::fs::OpenOptions;
 | 
			
		||||
use std::io::{BufWriter, Write};
 | 
			
		||||
use std::{env, fs, path::PathBuf};
 | 
			
		||||
use texts::*;
 | 
			
		||||
 | 
			
		||||
@@ -29,10 +34,10 @@ fn main() -> iced::Result {
 | 
			
		||||
        if let Some(second_query) = arg.get(2) {
 | 
			
		||||
            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(
 | 
			
		||||
                    if let Ok(f) = bible::get(
 | 
			
		||||
                        query,
 | 
			
		||||
                        second_query,
 | 
			
		||||
                        vec![&bible],
 | 
			
		||||
                        &bible,
 | 
			
		||||
                    ) {
 | 
			
		||||
                        println!("{}", f);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -43,38 +48,16 @@ fn main() -> iced::Result {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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)
 | 
			
		||||
        .subscription(State::subscription)
 | 
			
		||||
        .theme(State::theme)
 | 
			
		||||
        .run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
enum Message {
 | 
			
		||||
    CycleTheme,
 | 
			
		||||
    BibleSearchInput(usize, String),
 | 
			
		||||
    BibleSelected(usize, String),
 | 
			
		||||
    BibleSearchSubmit(usize),
 | 
			
		||||
@@ -82,26 +65,56 @@ enum Message {
 | 
			
		||||
    Clear(usize),
 | 
			
		||||
    WordSearchInput(usize, String),
 | 
			
		||||
    SubmitWordSearch(usize),
 | 
			
		||||
    QuickSearch(usize, String),
 | 
			
		||||
    QuickSearch(usize, String, usize),
 | 
			
		||||
    AddColRight,
 | 
			
		||||
    DeleteColumn(usize),
 | 
			
		||||
    SetErrorMessage(usize, String),
 | 
			
		||||
    RecievedEvent(Event),
 | 
			
		||||
    NoteInput(usize, text_editor::Action),
 | 
			
		||||
    Save,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Message {
 | 
			
		||||
    fn get_col_idx(&self) -> Option<usize> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Message::SetErrorMessage(i, _) |
 | 
			
		||||
            Message::BibleSearchInput(i, _) |
 | 
			
		||||
            Message::BibleSelected(i, _) |
 | 
			
		||||
            Message::BibleSearchSubmit(i) |
 | 
			
		||||
            Message::CopyText(i) |
 | 
			
		||||
            Message::Clear(i) |
 | 
			
		||||
            Message::WordSearchInput(i, _) |
 | 
			
		||||
            Message::SubmitWordSearch(i) |
 | 
			
		||||
            Message::QuickSearch(i, _, _) |
 | 
			
		||||
            Message::DeleteColumn(i) |
 | 
			
		||||
            Message::NoteInput(i, _)
 | 
			
		||||
            => {
 | 
			
		||||
                Some(*i)
 | 
			
		||||
            },
 | 
			
		||||
            _ => None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ColState {
 | 
			
		||||
    selected_file: Option<String>,
 | 
			
		||||
    file: Option<String>,
 | 
			
		||||
    bible_selected: Option<bible::Bible>,
 | 
			
		||||
 | 
			
		||||
    bible_search: String,
 | 
			
		||||
    scripture_body: Option<String>,
 | 
			
		||||
 | 
			
		||||
    word_search: String,
 | 
			
		||||
    word_search_results: Option<Vec<String>>,
 | 
			
		||||
    word_search_results: Option<Vec<(String, bool)>>,
 | 
			
		||||
 | 
			
		||||
    notes: text_editor::Content,
 | 
			
		||||
 | 
			
		||||
    error: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ColState {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            selected_file: None,
 | 
			
		||||
            file: None,
 | 
			
		||||
            bible_selected: None,
 | 
			
		||||
 | 
			
		||||
            bible_search: String::new(),
 | 
			
		||||
@@ -109,6 +122,10 @@ impl Default for ColState {
 | 
			
		||||
 | 
			
		||||
            word_search: String::new(),
 | 
			
		||||
            word_search_results: None,
 | 
			
		||||
 | 
			
		||||
            notes: text_editor::Content::new(),
 | 
			
		||||
 | 
			
		||||
            error: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -116,7 +133,12 @@ impl Default for ColState {
 | 
			
		||||
struct State {
 | 
			
		||||
    files: combo_box::State<String>,
 | 
			
		||||
    states: Vec<ColState>,
 | 
			
		||||
    cols: usize,
 | 
			
		||||
    // cols: usize,
 | 
			
		||||
    tab_index: Option<i32>,
 | 
			
		||||
 | 
			
		||||
    theme_idx: usize,
 | 
			
		||||
    themes: [Theme; 3],
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for State {
 | 
			
		||||
@@ -146,38 +168,159 @@ impl Default for State {
 | 
			
		||||
        Self {
 | 
			
		||||
            files: combo_box::State::new(files),
 | 
			
		||||
            states: vec![ColState::default()],
 | 
			
		||||
            cols: 1,
 | 
			
		||||
            // cols: 1,
 | 
			
		||||
            tab_index: None,
 | 
			
		||||
            // THEMES
 | 
			
		||||
            //    Dark - KanagawaDragon
 | 
			
		||||
            //    Default - Nord
 | 
			
		||||
            //    Light - TokyoNightLight
 | 
			
		||||
            theme_idx: 0,
 | 
			
		||||
            themes: [
 | 
			
		||||
                Theme::KanagawaDragon,
 | 
			
		||||
                Theme::Nord,
 | 
			
		||||
                Theme::TokyoNightLight
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl State {
 | 
			
		||||
    fn theme(&self) -> Theme {
 | 
			
		||||
        self.themes[self.theme_idx].clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn subscription(&self) -> Subscription<Message> {
 | 
			
		||||
        event::listen().map(Message::RecievedEvent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn update(&mut self, msg: Message) -> Task<Message> {
 | 
			
		||||
        // clear error messages
 | 
			
		||||
        if let Some(idx) = msg.get_col_idx() {
 | 
			
		||||
            self.states[idx].error = None
 | 
			
		||||
        }
 | 
			
		||||
        // match normal messages
 | 
			
		||||
        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::Save => {
 | 
			
		||||
                let opt = OpenOptions::new()
 | 
			
		||||
                    // create if doesn't already exist
 | 
			
		||||
                    .create(true)
 | 
			
		||||
                    .append(false)
 | 
			
		||||
                    // clear existing file
 | 
			
		||||
                    .truncate(true)
 | 
			
		||||
                    .write(true)
 | 
			
		||||
                    .open("save.md")
 | 
			
		||||
                    ;
 | 
			
		||||
                match opt {
 | 
			
		||||
                    Ok(file) => {
 | 
			
		||||
                        let mut buffer = BufWriter::new(file);
 | 
			
		||||
 | 
			
		||||
                        // the toml heading at the top of md files
 | 
			
		||||
                        let _ = buffer.write_all("---\n---\n".as_bytes());
 | 
			
		||||
 | 
			
		||||
                        for col in &self.states {
 | 
			
		||||
                            // separate each col with hr
 | 
			
		||||
                            let _ = buffer.write_all("\n---\n".as_bytes());
 | 
			
		||||
                            if let Some(scripture) = &col.scripture_body {
 | 
			
		||||
                                // code block for scripture
 | 
			
		||||
                                let _ = buffer.write_all("```\n".as_bytes());
 | 
			
		||||
                                let _ = buffer.write_all(parse(scripture).as_bytes());
 | 
			
		||||
                                let _ = buffer.write_all("```\n".as_bytes());
 | 
			
		||||
                            }
 | 
			
		||||
                            let _ = buffer.write_all(&col.notes.text().as_bytes());
 | 
			
		||||
                        }
 | 
			
		||||
                        let _ = buffer.flush();
 | 
			
		||||
                    },
 | 
			
		||||
                    Err(err) => {
 | 
			
		||||
                        eprintln!("{err}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Message::NoteInput(i, action) => {
 | 
			
		||||
                let notes = &mut self.states[i].notes;
 | 
			
		||||
                notes.perform(action);
 | 
			
		||||
            },
 | 
			
		||||
            Message::CycleTheme => {
 | 
			
		||||
                let len = self.themes.len();
 | 
			
		||||
                let i = &mut self.theme_idx;
 | 
			
		||||
                if *i == len-1 {
 | 
			
		||||
                    *i = 0;
 | 
			
		||||
                } else {
 | 
			
		||||
                    *i += 1;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Message::RecievedEvent(event) => {
 | 
			
		||||
                if let Event::Keyboard(kbd) = event {
 | 
			
		||||
                    if let iced::keyboard::Event::KeyReleased {key, modifiers, ..} = kbd {
 | 
			
		||||
                        if let Key::Named(n) = key {
 | 
			
		||||
                            if let Named::Tab = n {
 | 
			
		||||
                                if let Some(idx) = self.tab_index {
 | 
			
		||||
                                    let new_idx = if modifiers == Modifiers::SHIFT { idx-1 } else { idx+1 };
 | 
			
		||||
 | 
			
		||||
                                    // 2 because two buttons on each col
 | 
			
		||||
                                    let new_idx = new_idx.clamp(0, (self.states.len()*2) as i32 -1);
 | 
			
		||||
                                    self.tab_index = Some(new_idx);
 | 
			
		||||
 | 
			
		||||
                                    let id = format!("tabId-{}", new_idx);
 | 
			
		||||
                                    return text_input::focus(id);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    if self.states.len() > 0 {
 | 
			
		||||
                                        self.tab_index = Some(0);
 | 
			
		||||
                                        return text_input::focus("tabId-0");
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Message::QuickSearch(i, s) => {
 | 
			
		||||
            Message::SetErrorMessage(i, m) => {
 | 
			
		||||
                self.states[i].error = Some(m);
 | 
			
		||||
            }
 | 
			
		||||
            Message::DeleteColumn(idx) => {
 | 
			
		||||
                // self.cols -= 1;
 | 
			
		||||
                self.states.remove(idx);
 | 
			
		||||
            }
 | 
			
		||||
            Message::AddColRight => {
 | 
			
		||||
                self.states.push(ColState::default());
 | 
			
		||||
 | 
			
		||||
                // Add the rightmost opened Bible to the new column
 | 
			
		||||
                if let Some(last) = self.states.get(self.states.len()-2) {
 | 
			
		||||
                    let file = &last.file;
 | 
			
		||||
                    if let Some(file) = file {
 | 
			
		||||
                        return self.update(Message::BibleSelected(self.states.len()-1, file.to_string())); 
 | 
			
		||||
                    }
 | 
			
		||||
                }; 
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
            Message::BibleSelected(i, file) => {
 | 
			
		||||
                match fs::read_to_string(&file) {
 | 
			
		||||
                    Ok(contents) => {
 | 
			
		||||
                        match quick_xml::de::from_str::<bible::Bible>(&contents) {
 | 
			
		||||
                            Ok(bible)  => {
 | 
			
		||||
                                // State is held technically in this variable, although
 | 
			
		||||
                                // the Bible is held in memory in it's own variable.
 | 
			
		||||
                                self.states[i].bible_selected = Some(bible);
 | 
			
		||||
                                self.states[i].file = Some(file);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                            Err(err) => {
 | 
			
		||||
                                eprintln!("{}", err);
 | 
			
		||||
                                return self.update(Message::SetErrorMessage(i, err.to_string()));
 | 
			
		||||
                            },
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    Err(err) => {
 | 
			
		||||
                        eprintln!("{}", err);
 | 
			
		||||
                        return self.update(Message::SetErrorMessage(i, err.to_string()));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Message::QuickSearch(i, s, result_index) => {
 | 
			
		||||
                self.states[i].bible_search = s;
 | 
			
		||||
                if let Some(wsr) = &mut self.states[i].word_search_results {
 | 
			
		||||
                    // set "visited" for this link
 | 
			
		||||
                    wsr[result_index].1 = true;
 | 
			
		||||
                }
 | 
			
		||||
                return self.update(Message::BibleSearchSubmit(i));
 | 
			
		||||
            }
 | 
			
		||||
            Message::BibleSearchInput(i, query) => self.states[i].bible_search = query,
 | 
			
		||||
@@ -190,13 +333,35 @@ impl State {
 | 
			
		||||
                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]);
 | 
			
		||||
                    self.states[i].scripture_body = match bible::get(book, chap_and_ver, bible) {
 | 
			
		||||
                        Ok(s) => {
 | 
			
		||||
                            Some(s)
 | 
			
		||||
                        },
 | 
			
		||||
                        Err(s) => {
 | 
			
		||||
                            return self.update(Message::SetErrorMessage(i, s)); 
 | 
			
		||||
                        },
 | 
			
		||||
                    };
 | 
			
		||||
                } else {
 | 
			
		||||
                    if let None = book {
 | 
			
		||||
                        return self.update(Message::SetErrorMessage(i, format!("Error selecting book {:?}", book)));
 | 
			
		||||
                    }
 | 
			
		||||
                    if let None = location {
 | 
			
		||||
                        return self.update(Message::SetErrorMessage(i, format!("Error getting chapter and/or verse {:?}", location)));
 | 
			
		||||
                    }
 | 
			
		||||
                    if let None = &self.states[i].bible_selected {
 | 
			
		||||
                        return self.update(Message::SetErrorMessage(i, format!("Error getting selected 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);
 | 
			
		||||
                    self.states[i].word_search_results = Some(
 | 
			
		||||
                        res
 | 
			
		||||
                            .into_iter()
 | 
			
		||||
                            .map(|f| (f, false))
 | 
			
		||||
                            .collect()
 | 
			
		||||
                        );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Message::CopyText(i) => {
 | 
			
		||||
@@ -209,37 +374,55 @@ impl State {
 | 
			
		||||
                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(
 | 
			
		||||
 | 
			
		||||
        // TODO make this dynamic
 | 
			
		||||
        // max height that the scripture body can be
 | 
			
		||||
        // max height that the search resutls can be
 | 
			
		||||
        let max_height = 600;
 | 
			
		||||
 | 
			
		||||
        // Header bar, static, doesn't scroll
 | 
			
		||||
        column![
 | 
			
		||||
            row![
 | 
			
		||||
                row((0..self.cols).map(|col_index| {
 | 
			
		||||
                button("Cycle Themes")
 | 
			
		||||
                    .on_press(Message::CycleTheme)
 | 
			
		||||
                    .style(button::secondary)
 | 
			
		||||
                    ,
 | 
			
		||||
                button("Save")
 | 
			
		||||
                    .on_press(Message::Save)
 | 
			
		||||
                    .style(button::secondary)
 | 
			
		||||
                    ,
 | 
			
		||||
            ],
 | 
			
		||||
       scrollable(
 | 
			
		||||
            row![
 | 
			
		||||
                row((0..self.states.len()).map(|col_index| {
 | 
			
		||||
                    column![
 | 
			
		||||
                        // header
 | 
			
		||||
                        button("Delete Column")
 | 
			
		||||
                            .on_press_with(move || Message::DeleteColumn(col_index))
 | 
			
		||||
                            .width(Length::Fill)
 | 
			
		||||
                            .style(button::danger),
 | 
			
		||||
                        // Top of Column, text entry boxes
 | 
			
		||||
                        combo_box(
 | 
			
		||||
                            &self.files,
 | 
			
		||||
                            "Select Bible",
 | 
			
		||||
                            self.states[col_index].selected_file.as_ref(),
 | 
			
		||||
                            // Some(&"IDK where this text is".to_string()),
 | 
			
		||||
                            match &self.states[col_index].bible_selected {
 | 
			
		||||
                                Some(b) => Some(&b.translation_name),
 | 
			
		||||
                                None => None,
 | 
			
		||||
                            },
 | 
			
		||||
                            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)
 | 
			
		||||
                        text_input("Search query, ie: Gen 1:1", &self.states[col_index].bible_search)
 | 
			
		||||
                            .id(format!("tabId-{}", (col_index*2)+0))
 | 
			
		||||
                            .on_input(move |s| Message::BibleSearchInput(col_index, s))
 | 
			
		||||
                            .on_submit(Message::BibleSearchSubmit(col_index)),
 | 
			
		||||
                        text_input("Word Search", &self.states[col_index].word_search)
 | 
			
		||||
                            .id(format!("tabId-{}", (col_index*2)+1))
 | 
			
		||||
                            .on_input(move |s| Message::WordSearchInput(col_index, s))
 | 
			
		||||
                            .on_submit(Message::SubmitWordSearch(col_index)),
 | 
			
		||||
 | 
			
		||||
                        // Horizontal Row just above scripture
 | 
			
		||||
                        row![
 | 
			
		||||
                            button("Clear All")
 | 
			
		||||
                                .on_press_with(move || Message::Clear(col_index))
 | 
			
		||||
@@ -247,8 +430,14 @@ impl State {
 | 
			
		||||
                            button("Copy Scripture")
 | 
			
		||||
                                .on_press_with(move || Message::CopyText(col_index))
 | 
			
		||||
                                .style(button::primary),
 | 
			
		||||
                            button("Delete Column")
 | 
			
		||||
                                .on_press_with(move || Message::DeleteColumn(col_index))
 | 
			
		||||
                                .style(button::danger),
 | 
			
		||||
                            text(format!("{}", self.states[col_index].error.clone().unwrap_or(String::new())))
 | 
			
		||||
                                .style(text::danger),
 | 
			
		||||
                        ]
 | 
			
		||||
                        .spacing(5),
 | 
			
		||||
                        // Body
 | 
			
		||||
                        row![
 | 
			
		||||
                            // Word search results
 | 
			
		||||
                            scrollable(
 | 
			
		||||
@@ -258,20 +447,32 @@ impl State {
 | 
			
		||||
                                        .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())
 | 
			
		||||
                                        .enumerate()
 | 
			
		||||
                                        .map(|(idx,i)| {
 | 
			
		||||
                                            let style = if i.1 {
 | 
			
		||||
                                                button::secondary
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                button::primary
 | 
			
		||||
                                            };
 | 
			
		||||
                                            button(text(i.0.clone()))
 | 
			
		||||
                                                .style(style)
 | 
			
		||||
                                                .on_press_with(move || Message::QuickSearch(
 | 
			
		||||
                                                    col_index,
 | 
			
		||||
                                                    i.0.to_owned(),
 | 
			
		||||
                                                    idx
 | 
			
		||||
                                                ))
 | 
			
		||||
                                                .into()
 | 
			
		||||
                                        })
 | 
			
		||||
                                )
 | 
			
		||||
                                .padding([5, 5])
 | 
			
		||||
                            )
 | 
			
		||||
                            .height(max_height)
 | 
			
		||||
                            .spacing(5),
 | 
			
		||||
                            // Body
 | 
			
		||||
                            // Scripture
 | 
			
		||||
                            scrollable(
 | 
			
		||||
                                if let Some(body) = &self.states[col_index].scripture_body {
 | 
			
		||||
                                    column(body.split("\n").enumerate().map(|(i, msg)| {
 | 
			
		||||
                                        // Color every other verse slightly differently
 | 
			
		||||
                                        let msg = parse(msg);
 | 
			
		||||
                                        if i & 1 == 0 {
 | 
			
		||||
                                            text(msg.to_owned()).color(color![0xFFFFFF]).into()
 | 
			
		||||
@@ -287,13 +488,21 @@ impl State {
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            )
 | 
			
		||||
                            .height(max_height)
 | 
			
		||||
                            .spacing(5)
 | 
			
		||||
                        ],
 | 
			
		||||
                        // Notes
 | 
			
		||||
                        text_editor(&self.states[col_index].notes)
 | 
			
		||||
                            .placeholder("Notes...")
 | 
			
		||||
                            .on_action(move |a| Message::NoteInput(col_index, a))
 | 
			
		||||
                            .height(100)
 | 
			
		||||
                            // .id(format!("tabId-{}", (col_index*2)+2))
 | 
			
		||||
                    ]
 | 
			
		||||
                    .spacing(5)
 | 
			
		||||
                    .width(800.)
 | 
			
		||||
                    .into()
 | 
			
		||||
                })),
 | 
			
		||||
                // Right most vertical buttons
 | 
			
		||||
                button(text("+").center())
 | 
			
		||||
                    .height(Length::Fixed(200.))
 | 
			
		||||
                    .style(button::primary)
 | 
			
		||||
@@ -305,6 +514,7 @@ impl State {
 | 
			
		||||
            .spacing(5),
 | 
			
		||||
        )
 | 
			
		||||
        .direction(Direction::Horizontal(Scrollbar::new()))
 | 
			
		||||
        ]
 | 
			
		||||
        .into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
use inline_colorization::*;
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
@@ -74,11 +75,24 @@ pub const BOOKS_IN_ORDER: [&str; 66] = [
 | 
			
		||||
 | 
			
		||||
pub fn search_for_word(word: &str, from_files: &Bible) -> Vec<String> {
 | 
			
		||||
    let mut found = Vec::new();
 | 
			
		||||
    let regex = match Regex::new(&word.to_lowercase()) {
 | 
			
		||||
        Ok(re) => re,
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            eprintln!("{}", err);
 | 
			
		||||
 | 
			
		||||
            #[cfg(debug_assertions)]
 | 
			
		||||
            todo!("IDK what to do yet if building regex engine fails");
 | 
			
		||||
 | 
			
		||||
            #[cfg(not(debug_assertions))]
 | 
			
		||||
            return 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()) {
 | 
			
		||||
                    if regex.is_match(&verse.text.to_lowercase()) {
 | 
			
		||||
                        found.push(format!("{} {}:{}", BOOKS_IN_ORDER[book.number-1], chapter.number, verse.number));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -88,19 +102,9 @@ pub fn search_for_word(word: &str, from_files: &Bible) -> Vec<String> {
 | 
			
		||||
    found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = chap_and_ver.split(':');
 | 
			
		||||
    let chapter = splits.next();
 | 
			
		||||
    let verse = splits.next();
 | 
			
		||||
 | 
			
		||||
    // ###########################################
 | 
			
		||||
    // Figure out what book they are talking about
 | 
			
		||||
fn parse_book(book: &str) -> Result<(usize, &str), String> {
 | 
			
		||||
    // Go thru the list of books and see which
 | 
			
		||||
    // match the passed book str
 | 
			
		||||
    let res = BOOKS_IN_ORDER
 | 
			
		||||
        .iter()
 | 
			
		||||
        .enumerate()
 | 
			
		||||
@@ -115,47 +119,66 @@ pub fn get(book: &str, chap_and_ver: &str, bibles: Vec<&Bible>) -> Option<String
 | 
			
		||||
        .collect::<Vec<(usize, &&str)>>();
 | 
			
		||||
 | 
			
		||||
    let (book_idx, book_name) = match res.len() {
 | 
			
		||||
        // there is one option for what book it is
 | 
			
		||||
        1 => res[0],
 | 
			
		||||
        // there are >1 options for what book it is
 | 
			
		||||
        2.. => {
 | 
			
		||||
            eprintln!("Err: Ambigious input '{book}', could be any of:");
 | 
			
		||||
            for (_, i) in &res {
 | 
			
		||||
                eprintln!("\t{i}");
 | 
			
		||||
            }
 | 
			
		||||
            return None;
 | 
			
		||||
            return Err(format!("Ambigious input '{book}'"));
 | 
			
		||||
        }
 | 
			
		||||
        _ => {
 | 
			
		||||
            eprintln!("'{book}' is not a book");
 | 
			
		||||
            return None;
 | 
			
		||||
            return Err(format!("'{book}' is not a book"));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    Ok((book_idx, book_name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // 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()))
 | 
			
		||||
        .collect::<Vec<(&Bible,&Book)>>();
 | 
			
		||||
 | 
			
		||||
    // Select the chapter in each Bible
 | 
			
		||||
    let chapters = if let Some(chapter) = chapter {
 | 
			
		||||
        if let Ok(ch_num) = chapter.parse::<usize>() {
 | 
			
		||||
            books
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|(bible,book)| (bible, book, book.get_chapter_by_index(ch_num)))
 | 
			
		||||
                .filter(|(_,_,book)| book.is_some())
 | 
			
		||||
                .map(|(bible,book,chapter)| (*bible,*book,chapter.unwrap()))
 | 
			
		||||
                .collect::<Vec<(&Bible, &Book, &Chapter)>>()
 | 
			
		||||
fn parse_chapter<'a>(chapter_num: &str, book: &'a Book) -> Result<&'a Chapter, String> {
 | 
			
		||||
    let chapter = if let Ok(idx) = chapter_num.parse::<usize>() {
 | 
			
		||||
        if let Some(chapter) = book.get_chapter_by_index(idx) {
 | 
			
		||||
            chapter
 | 
			
		||||
        } else {
 | 
			
		||||
            return None;
 | 
			
		||||
            return Err(format!("Chapter {idx} doesn't exist in book {}", BOOKS_IN_ORDER[book.number-1]));
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        return None;
 | 
			
		||||
        return Err(format!("Chapter number could not be parsed from {chapter_num}"));
 | 
			
		||||
    };
 | 
			
		||||
    Ok(chapter)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get(book: &str, chap_and_ver: &str, bible: &Bible) -> Result<String, 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 = chap_and_ver.split(':');
 | 
			
		||||
    let chapter_num = splits.next();
 | 
			
		||||
    let verse_num = splits.next();
 | 
			
		||||
    // ###########################################
 | 
			
		||||
 | 
			
		||||
    // Figure out what book they are talking about
 | 
			
		||||
    let (book_idx, book_name) = parse_book(book)?;
 | 
			
		||||
 | 
			
		||||
    let book = match bible.get_book_by_index(book_idx+1) {
 | 
			
		||||
        Some(s) => s,
 | 
			
		||||
        None => return Err(format!("Selected Bible {} doesn't contain book {book_name}", bible.translation_name)),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Figure out what chapter they are talking about
 | 
			
		||||
    let chapter = if let Some(ch) = chapter_num {
 | 
			
		||||
        parse_chapter(ch, book)?
 | 
			
		||||
    } else {
 | 
			
		||||
        return Err("No chapter number was passed".to_string());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Get the verse in each Bible
 | 
			
		||||
    match verse {
 | 
			
		||||
    match verse_num {
 | 
			
		||||
        Some(verse) => {
 | 
			
		||||
            let mut splits = verse.split("-");
 | 
			
		||||
            let start = splits.next();
 | 
			
		||||
@@ -166,53 +189,50 @@ pub fn get(book: &str, chap_and_ver: &str, bibles: Vec<&Bible>) -> Option<String
 | 
			
		||||
                (Some(sn), Some(en)) => {
 | 
			
		||||
                    if let (Ok(start), Ok(end)) = (sn.parse::<usize>(), en.parse::<usize>()) {
 | 
			
		||||
                        let mut buf = String::new();
 | 
			
		||||
 | 
			
		||||
                        let translation = &bible.translation_name;
 | 
			
		||||
 | 
			
		||||
                        buf += &format!("{style_bold}[{translation}] {style_underline}{book_name} {}:{start}-{end}{style_reset}:\n", chapter.number);
 | 
			
		||||
                        for num in start..=end {
 | 
			
		||||
                            for (bible, _book, chapter) in &chapters {
 | 
			
		||||
                                if let Some(verse) = chapter.get_verse_by_index(num) {
 | 
			
		||||
                                    buf += &format!(
 | 
			
		||||
                                        "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n",
 | 
			
		||||
                                        bible.translation_name, chapter.number, verse.number
 | 
			
		||||
                                    );
 | 
			
		||||
                                }
 | 
			
		||||
                            if let Some(verse) = chapter.get_verse_by_index(num) {
 | 
			
		||||
                                buf += &format!(
 | 
			
		||||
                                    "{}: {verse}\n", verse.number
 | 
			
		||||
                                );
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        return Some(buf);
 | 
			
		||||
                        return Ok(buf);
 | 
			
		||||
                    }
 | 
			
		||||
                    return None;
 | 
			
		||||
                    return Err(format!("Could not parse '{sn}' or '{en}' into numbers"));
 | 
			
		||||
                }
 | 
			
		||||
                // 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) {
 | 
			
		||||
                                buf += &format!(
 | 
			
		||||
                                    "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n",
 | 
			
		||||
                                    bible.translation_name, chapter.number, verse.number
 | 
			
		||||
                                );
 | 
			
		||||
                            }
 | 
			
		||||
                        if let Some(verse) = chapter.get_verse_by_index(num) {
 | 
			
		||||
                            buf += &format!(
 | 
			
		||||
                                "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}:\n{verse}\n",
 | 
			
		||||
                                bible.translation_name, chapter.number, verse.number
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
                        return Some(buf);
 | 
			
		||||
                        return Ok(buf);
 | 
			
		||||
                    }
 | 
			
		||||
                    return None;
 | 
			
		||||
                    return Err(format!("Could not parse '{ver_num}' into a number"));
 | 
			
		||||
                }
 | 
			
		||||
                _ => {
 | 
			
		||||
                    // couldn't parse verse
 | 
			
		||||
                    return None;
 | 
			
		||||
                    return Err(format!("Couldn't parse '{:?}' or '{:?}' into verse numbers", start, end));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        None => {
 | 
			
		||||
            // only chapter
 | 
			
		||||
            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
 | 
			
		||||
                );
 | 
			
		||||
                buf += &format!("{}", chapter);
 | 
			
		||||
            }
 | 
			
		||||
            return Some(buf);
 | 
			
		||||
            buf += &format!(
 | 
			
		||||
                "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:\n",
 | 
			
		||||
                bible.translation_name, chapter.number
 | 
			
		||||
            );
 | 
			
		||||
            buf += &format!("{}", chapter);
 | 
			
		||||
            return Ok(buf);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -255,15 +275,10 @@ impl Chapter {
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
pub struct Bible {
 | 
			
		||||
    #[serde(rename = "@translation")]
 | 
			
		||||
    translation_name: String,
 | 
			
		||||
    pub 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 {
 | 
			
		||||
@@ -277,6 +292,7 @@ struct Testament {
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
struct Book {
 | 
			
		||||
    #[serde(rename = "@number")]
 | 
			
		||||
    /// Genesis is book 1
 | 
			
		||||
    number: usize,
 | 
			
		||||
    #[serde(rename = "chapter")]
 | 
			
		||||
    chapters: Vec<Chapter>,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user