use topos_bible::data::books::BookId;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Book(pub u8);
pub const BOOKS: &[(u8, &str)] = &[
(1, "GEN"),
(2, "EXO"),
(3, "LEV"),
(4, "NUM"),
(5, "DEU"),
(6, "JOS"),
(7, "JDG"),
(8, "RUT"),
(9, "1SA"),
(10, "2SA"),
(11, "1KI"),
(12, "2KI"),
(13, "1CH"),
(14, "2CH"),
(15, "EZR"),
(16, "NEH"),
(17, "EST"),
(18, "JOB"),
(19, "PSA"),
(20, "PRO"),
(21, "ECC"),
(22, "SNG"),
(23, "ISA"),
(24, "JER"),
(25, "LAM"),
(26, "EZK"),
(27, "DAN"),
(28, "HOS"),
(29, "JOL"),
(30, "AMO"),
(31, "OBA"),
(32, "JON"),
(33, "MIC"),
(34, "NAM"),
(35, "HAB"),
(36, "ZEP"),
(37, "HAG"),
(38, "ZEC"),
(39, "MAL"),
(40, "MAT"),
(41, "MRK"),
(42, "LUK"),
(43, "JHN"),
(44, "ACT"),
(45, "ROM"),
(46, "1CO"),
(47, "2CO"),
(48, "GAL"),
(49, "EPH"),
(50, "PHP"),
(51, "COL"),
(52, "1TH"),
(53, "2TH"),
(54, "1TI"),
(55, "2TI"),
(56, "TIT"),
(57, "PHM"),
(58, "HEB"),
(59, "JAS"),
(60, "1PE"),
(61, "2PE"),
(62, "1JN"),
(63, "2JN"),
(64, "3JN"),
(65, "JUD"),
(66, "REV"),
(67, "TOB"), (68, "JDT"), (69, "ESG"), (71, "WIS"), (72, "SIR"), (73, "BAR"), (74, "LJE"), (75, "S3Y"), (76, "SUS"), (77, "BEL"), (79, "1MA"), (80, "2MA"), (81, "1ES"), (82, "MAN"), (83, "PS2"), (84, "3MA"), (85, "2ES"), (86, "4MA"), (87, "ODA"), (88, "PSS"), (89, "ENO"), (90, "LAO"), (91, "DAG"), (92, "PS3"), (93, "2BA"), (94, "LBA"), (95, "JUB"), (96, "1MQ"), (97, "2MQ"), (98, "3MQ"), (99, "REP"), (100, "4BA"), (101, "EZA"), (102, "5EZ"), (103, "6EZ"), (200, "FRT"), (201, "INT"), (202, "BAK"), (203, "CNC"), (204, "GLO"), (205, "TDX"), (206, "NDX"), (207, "OTH"), (208, "XXA"), (209, "XXB"),
(210, "XXC"),
(211, "XXD"),
(212, "XXE"),
(213, "XXF"),
(214, "XXG"),
];
impl Book {
pub fn from_usfm(code: &str) -> Option<Book> {
(BOOKS.iter())
.find(|(_, c)| c.eq_ignore_ascii_case(code))
.map(|&(n, _)| Book(n))
}
pub fn usfm(self) -> Option<&'static str> {
(BOOKS.iter()).find(|&&(n, _)| n == self.0).map(|&(_, c)| c)
}
pub fn from_book_id(id: BookId) -> Option<Book> {
Book::from_usfm(crate::bible_data().books().get_usfm(id)?)
}
pub fn book_id(self) -> Option<BookId> {
crate::bible_data().books().search_usfm(self.usfm()?)
}
pub fn name(self) -> String {
self.usfm()
.map_or_else(|| self.0.to_string(), str::to_string)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SNAPSHOT: &str = "\
1 GEN 2 EXO 3 LEV 4 NUM 5 DEU 6 JOS 7 JDG 8 RUT 9 1SA 10 2SA 11 1KI 12 2KI 13 1CH 14 2CH \
15 EZR 16 NEH 17 EST 18 JOB 19 PSA 20 PRO 21 ECC 22 SNG 23 ISA 24 JER 25 LAM 26 EZK \
27 DAN 28 HOS 29 JOL 30 AMO 31 OBA 32 JON 33 MIC 34 NAM 35 HAB 36 ZEP 37 HAG 38 ZEC \
39 MAL 40 MAT 41 MRK 42 LUK 43 JHN 44 ACT 45 ROM 46 1CO 47 2CO 48 GAL 49 EPH 50 PHP \
51 COL 52 1TH 53 2TH 54 1TI 55 2TI 56 TIT 57 PHM 58 HEB 59 JAS 60 1PE 61 2PE 62 1JN \
63 2JN 64 3JN 65 JUD 66 REV \
67 TOB 68 JDT 69 ESG 71 WIS 72 SIR 73 BAR 74 LJE 75 S3Y 76 SUS 77 BEL 79 1MA 80 2MA \
81 1ES 82 MAN 83 PS2 84 3MA 85 2ES 86 4MA 87 ODA 88 PSS 89 ENO 90 LAO \
91 DAG 92 PS3 93 2BA 94 LBA 95 JUB 96 1MQ 97 2MQ 98 3MQ 99 REP 100 4BA 101 EZA 102 5EZ \
103 6EZ \
200 FRT 201 INT 202 BAK 203 CNC 204 GLO 205 TDX 206 NDX 207 OTH 208 XXA 209 XXB 210 XXC \
211 XXD 212 XXE 213 XXF 214 XXG";
#[test]
fn numbers_never_change() {
let snapshot: Vec<&str> = SNAPSHOT.split_whitespace().collect();
let snapshot: Vec<String> = (snapshot.chunks(2)).map(|p| p.join(" ")).collect();
let table: Vec<String> = (BOOKS.iter()).map(|(n, c)| format!("{n} {c}")).collect();
for entry in &snapshot {
assert!(table.contains(entry), "{entry} was changed or removed");
}
assert!(BOOKS.windows(2).all(|w| w[0].0 < w[1].0), "in number order");
assert!(
(BOOKS.iter()).all(|&(n, _)| n != 0 && n != 70 && n != 78 && !(104..200).contains(&n))
);
let mut codes: Vec<&str> = BOOKS.iter().map(|(_, c)| *c).collect();
codes.sort();
codes.dedup();
assert_eq!(codes.len(), BOOKS.len(), "each code once");
}
#[test]
fn every_book_topos_knows_has_a_number() {
let books = crate::bible_data().books();
for id in books.ids() {
let Some(code) = books.get_usfm(id) else {
continue;
};
let book = Book::from_usfm(code).unwrap_or_else(|| panic!("{code} has no number"));
assert_eq!(book.book_id(), Some(id));
assert_eq!(Book::from_book_id(id), Some(book));
}
for n in 1..=66 {
assert_eq!(Book(n).book_id(), Some(BookId(n)));
}
}
#[test]
fn codes_and_numbers() {
assert_eq!(Book::from_usfm("jhn"), Some(Book(43)));
assert_eq!(Book(43).usfm(), Some("JHN"));
assert_eq!(Book::from_usfm("JUB"), Some(Book(95)));
assert_eq!(Book::from_usfm("XXG"), Some(Book(214)));
assert_eq!(Book(70).usfm(), None);
assert_eq!(Book(150).name(), "150");
assert_eq!(Book::from_usfm("ZZZ"), None);
for &(n, code) in BOOKS {
assert_eq!(Book::from_usfm(code).and_then(|b| b.usfm()), Some(code));
assert_eq!(Book(n).usfm().and_then(Book::from_usfm), Some(Book(n)));
}
}
}