1use std::cmp::Ordering;
2use std::fmt;
3use std::fs::File;
4use std::hash::Hash;
5use std::io::{self, BufRead, BufReader};
6use std::num::NonZeroU8;
7use std::str::FromStr;
8
9#[allow(non_camel_case_types)]
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
16pub enum Book {
17 Gen,
19 Ex,
21 Lev,
23 Num,
25 Deut,
27 Josh,
29 Judg,
31 Ruth,
33 I_Sam,
35 II_Sam,
37 I_Kings,
39 II_Kings,
41 I_Chr,
43 II_Chr,
45 Ezra,
47 Neh,
49 Esth,
51 Job,
53 Ps,
55 Prov,
57 Eccl,
59 Song,
61 Isa,
63 Jer,
65 Lam,
67 Ezek,
69 Dan,
71 Hos,
73 Joel,
75 Am,
77 Ob,
79 Jon,
81 Mic,
83 Nah,
85 Hab,
87 Zeph,
89 Hag,
91 Zech,
93 Mal,
95 Mt,
97 Mk,
99 Lk,
101 Jn,
103 Acts,
105 Rom,
107 I_Cor,
109 II_Cor,
111 Gal,
113 Eph,
115 Phil,
117 Col,
119 I_Thess,
121 II_Thess,
123 I_Tim,
125 II_Tim,
127 Titus,
129 Philem,
131 Heb,
133 Jas,
135 I_Pet,
137 II_Pet,
139 I_Jn,
141 II_Jn,
143 III_Jn,
145 Jude,
147 Rev,
149 Tob,
151 Jdt,
153 EsthGrk,
155 Wis,
157 Sir,
159 Bar,
161 LetJer,
163 SongOfThr,
165 Sus,
167 Bel,
169 I_Macc,
171 II_Macc,
173 III_Macc,
175 IV_Macc,
177 I_Esd,
179 II_Esd,
181 PrMan,
183 Ps151,
185 PsSol,
187 Odes,
189}
190
191impl Book {
192 fn new(s: &str) -> Option<Self> {
193 match s {
194 "01O" => Some(Self::Gen),
195 "02O" => Some(Self::Ex),
196 "03O" => Some(Self::Lev),
197 "04O" => Some(Self::Num),
198 "05O" => Some(Self::Deut),
199 "06O" => Some(Self::Josh),
200 "07O" => Some(Self::Judg),
201 "08O" => Some(Self::Ruth),
202 "09O" => Some(Self::I_Sam),
203 "10O" => Some(Self::II_Sam),
204 "11O" => Some(Self::I_Kings),
205 "12O" => Some(Self::II_Kings),
206 "13O" => Some(Self::I_Chr),
207 "14O" => Some(Self::II_Chr),
208 "15O" => Some(Self::Ezra),
209 "16O" => Some(Self::Neh),
210 "17O" => Some(Self::Esth),
211 "18O" => Some(Self::Job),
212 "19O" => Some(Self::Ps),
213 "20O" => Some(Self::Prov),
214 "21O" => Some(Self::Eccl),
215 "22O" => Some(Self::Song),
216 "23O" => Some(Self::Isa),
217 "24O" => Some(Self::Jer),
218 "25O" => Some(Self::Lam),
219 "26O" => Some(Self::Ezek),
220 "27O" => Some(Self::Dan),
221 "28O" => Some(Self::Hos),
222 "29O" => Some(Self::Joel),
223 "30O" => Some(Self::Am),
224 "31O" => Some(Self::Ob),
225 "32O" => Some(Self::Jon),
226 "33O" => Some(Self::Mic),
227 "34O" => Some(Self::Nah),
228 "35O" => Some(Self::Hab),
229 "36O" => Some(Self::Zeph),
230 "37O" => Some(Self::Hag),
231 "38O" => Some(Self::Zech),
232 "39O" => Some(Self::Mal),
233 "40N" => Some(Self::Mt),
234 "41N" => Some(Self::Mk),
235 "42N" => Some(Self::Lk),
236 "43N" => Some(Self::Jn),
237 "44N" => Some(Self::Acts),
238 "45N" => Some(Self::Rom),
239 "46N" => Some(Self::I_Cor),
240 "47N" => Some(Self::II_Cor),
241 "48N" => Some(Self::Gal),
242 "49N" => Some(Self::Eph),
243 "50N" => Some(Self::Phil),
244 "51N" => Some(Self::Col),
245 "52N" => Some(Self::I_Thess),
246 "53N" => Some(Self::II_Thess),
247 "54N" => Some(Self::I_Tim),
248 "55N" => Some(Self::II_Tim),
249 "56N" => Some(Self::Titus),
250 "57N" => Some(Self::Philem),
251 "58N" => Some(Self::Heb),
252 "59N" => Some(Self::Jas),
253 "60N" => Some(Self::I_Pet),
254 "61N" => Some(Self::II_Pet),
255 "62N" => Some(Self::I_Jn),
256 "63N" => Some(Self::II_Jn),
257 "64N" => Some(Self::III_Jn),
258 "65N" => Some(Self::Jude),
259 "66N" => Some(Self::Rev),
260 "67A" => Some(Self::Tob),
261 "68A" => Some(Self::Jdt),
262 "69A" => Some(Self::EsthGrk),
263 "70A" => Some(Self::Wis),
264 "71A" => Some(Self::Sir),
265 "72A" => Some(Self::Bar),
266 "73A" => Some(Self::LetJer),
267 "74A" => Some(Self::SongOfThr),
268 "75A" => Some(Self::Sus),
269 "76A" => Some(Self::Bel),
270 "77A" => Some(Self::I_Macc),
271 "78A" => Some(Self::II_Macc),
272 "79A" => Some(Self::III_Macc),
273 "80A" => Some(Self::IV_Macc),
274 "81A" => Some(Self::I_Esd),
275 "82A" => Some(Self::II_Esd),
276 "83A" => Some(Self::PrMan),
277 "84A" => Some(Self::Ps151),
278 "85A" => Some(Self::PsSol),
279 "86A" => Some(Self::Odes),
280 _ => None,
281 }
282 }
283
284 #[must_use]
285 pub const fn as_str(&self) -> &str {
286 match self {
287 Self::Gen => "Genesis",
288 Self::Ex => "Exodus",
289 Self::Lev => "Leviticus",
290 Self::Num => "Numbers",
291 Self::Deut => "Deuteronomy",
292 Self::Josh => "Joshua",
293 Self::Judg => "Judges",
294 Self::Ruth => "Ruth",
295 Self::I_Sam => "1 Samuel",
296 Self::II_Sam => "2 Samuel",
297 Self::I_Kings => "1 Kings",
298 Self::II_Kings => "2 Kings",
299 Self::I_Chr => "1 Chronicles",
300 Self::II_Chr => "2 Chronicles",
301 Self::Ezra => "Ezra",
302 Self::Neh => "Nehemiah",
303 Self::Esth => "Esther",
304 Self::Job => "Job",
305 Self::Ps => "Psalms",
306 Self::Prov => "Proverbs",
307 Self::Eccl => "Ecclesiastes",
308 Self::Song => "Song of Solomon",
309 Self::Isa => "Isaiah",
310 Self::Jer => "Jeremiah",
311 Self::Lam => "Lamentations",
312 Self::Ezek => "Ezekiel",
313 Self::Dan => "Daniel",
314 Self::Hos => "Hosea",
315 Self::Joel => "Joel",
316 Self::Am => "Amos",
317 Self::Ob => "Obadiah",
318 Self::Jon => "Jonah",
319 Self::Mic => "Micah",
320 Self::Nah => "Nahum",
321 Self::Hab => "Habakkuk",
322 Self::Zeph => "Zephaniah",
323 Self::Hag => "Haggai",
324 Self::Zech => "Zechariah",
325 Self::Mal => "Malachi",
326 Self::Mt => "Matthew",
327 Self::Mk => "Mark",
328 Self::Lk => "Luke",
329 Self::Jn => "John",
330 Self::Acts => "Acts",
331 Self::Rom => "Romans",
332 Self::I_Cor => "1 Corinthians",
333 Self::II_Cor => "2 Corinthians",
334 Self::Gal => "Galatians",
335 Self::Eph => "Ephesians",
336 Self::Phil => "Philippians",
337 Self::Col => "Colossians",
338 Self::I_Thess => "1 Thessalonians",
339 Self::II_Thess => "2 Thessalonians",
340 Self::I_Tim => "1 Timothy",
341 Self::II_Tim => "2 Timothy",
342 Self::Titus => "Titus",
343 Self::Philem => "Philemon",
344 Self::Heb => "Hebrews",
345 Self::Jas => "James",
346 Self::I_Pet => "1 Peter",
347 Self::II_Pet => "2 Peter",
348 Self::I_Jn => "1 John",
349 Self::II_Jn => "2 John",
350 Self::III_Jn => "3 John",
351 Self::Jude => "Jude",
352 Self::Rev => "Revelation",
353 Self::Tob => "Tobit",
354 Self::Jdt => "Judith",
355 Self::EsthGrk => "Esther, Greek",
356 Self::Wis => "Wisdom of Solomon",
357 Self::Sir => "Sirach",
358 Self::Bar => "Baruch",
359 Self::LetJer => "Epistle of Jeremiah",
360 Self::SongOfThr => "Prayer of Azariah",
361 Self::Sus => "Susanna",
362 Self::Bel => "Bel and the Dragon",
363 Self::I_Macc => "1 Maccabees",
364 Self::II_Macc => "2 Maccabees",
365 Self::III_Macc => "3 Maccabees",
366 Self::IV_Macc => "4 Maccabees",
367 Self::I_Esd => "1 Esdras",
368 Self::II_Esd => "2 Esdras",
369 Self::PrMan => "Prayer of Manasseh",
370 Self::Ps151 => "Psalm 151",
371 Self::PsSol => "Psalm of Solomon",
372 Self::Odes => "Odes",
373 }
374 }
375}
376
377impl fmt::Display for Book {
378 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
379 f.write_str(self.as_str())
380 }
381}
382
383#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
385pub struct PositionNrsv {
386 book: Book,
387 chap_no: u16,
388 vers_no: u16,
389}
390
391impl PositionNrsv {
392 #[must_use]
394 pub const fn book(&self) -> Book {
395 self.book
396 }
397
398 #[must_use]
400 pub const fn chap_no(&self) -> u16 {
401 self.chap_no
402 }
403
404 #[must_use]
406 pub const fn vers_no(&self) -> u16 {
407 self.vers_no
408 }
409}
410
411impl fmt::Display for PositionNrsv {
412 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
413 let Self { book, chap_no, vers_no } = *self;
414 if book == Book::Ps151 {
415 write!(f, "Psalm 151:{}", vers_no)
416 } else {
417 write!(f, "{} {}:{}", book, chap_no, vers_no)
418 }
419 }
420}
421
422#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
423enum PositionMeta {
424 None,
425 Subverse(NonZeroU8),
426 VerseRange(NonZeroU8),
427}
428
429impl PositionMeta {
430 fn new(s: &str) -> Option<Self> {
431 if s.is_empty() {
432 Some(Self::None)
433 } else if let Some(stripped) = s.strip_prefix('-') {
434 let x = NonZeroU8::from_str(stripped)
435 .unwrap_or_else(|_e| panic!("cannot parse {:?} as verse range", s));
436 Some(Self::VerseRange(x))
437 } else if let Some(stripped) = s.strip_prefix('.') {
438 let x = NonZeroU8::from_str(stripped)
439 .unwrap_or_else(|_e| panic!("cannot parse {:?} as subverse", s));
440 Some(Self::Subverse(x))
441 } else {
442 let x = match s {
443 "a" | "EndA" => 1,
444 "b" | "EndB" => 2,
445 "c" => 3,
446 "d" => 4,
447 "e" => 5,
448 "f" => 6,
449 "g" => 7,
450 "h" => 8,
451 "i" => 9,
452 "j" => 10,
453 "k" => 11,
454 "l" => 12,
455 "m" => 13,
456 "n" => 14,
457 "o" => 15,
458 "p" => 16,
459 "q" => 17,
460 "r" => 18,
461 "s" => 19,
462 "t" => 20,
463 "u" => 21,
464 "v" => 22,
465 "w" => 23,
466 "x" => 24,
467 "y" => 25,
468 "z" => 26,
469 "aa" => 27,
470 "bb" => 28,
471 "cc" => 29,
472 "dd" => 30,
473 "ee" => 31,
474 "ff" => 32,
475 "gg" => 33,
476 "hh" => 34,
477 "ii" => 35,
478 "jj" => 36,
479 "kk" => 37,
480 _ => return None,
481 };
482 Some(Self::Subverse(NonZeroU8::new(x).unwrap()))
483 }
484 }
485}
486
487impl fmt::Display for PositionMeta {
488 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
489 match self {
490 PositionMeta::Subverse(x) => write!(f, ".{x}"),
491 PositionMeta::VerseRange(x) => write!(f, "-{x}"),
492 PositionMeta::None => Ok(()),
493 }
494 }
495}
496
497#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
504pub struct Position {
505 book: Book,
506 chap_no: u16,
507 vers_no: u16,
508 meta: PositionMeta,
509}
510
511impl Position {
512 #[must_use]
514 pub const fn book(&self) -> Book {
515 self.book
516 }
517
518 #[must_use]
520 pub const fn chap_no(&self) -> u16 {
521 self.chap_no
522 }
523
524 #[must_use]
529 pub const fn vers_beg(&self) -> u16 {
530 self.vers_no
531 }
532
533 #[must_use]
538 pub const fn vers_end(&self) -> u16 {
539 let Self { vers_no, meta, .. } = *self;
540 let delta = if let PositionMeta::VerseRange(delta) = meta {
541 delta.get() as _
542 } else {
543 0
544 };
545 vers_no + delta
546 }
547
548 #[must_use]
554 pub const fn subverse(&self) -> Option<NonZeroU8> {
555 let Self { meta, .. } = *self;
556 if let PositionMeta::Subverse(x) = meta {
557 Some(x)
558 } else {
559 None
560 }
561 }
562}
563
564impl fmt::Display for Position {
565 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
566 let Self { book, chap_no, vers_no, meta } = *self;
567 let (book, chap_no) = if book == Book::Ps151 {
568 ("Psalm 151", 151)
569 } else {
570 (book.as_str(), chap_no)
571 };
572 write!(f, "{} {}:{}{}", book, chap_no, vers_no, meta)
573 }
574}
575
576impl PartialOrd for Position {
577 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
578 let Self { book, chap_no, vers_no, meta } = *self;
579 let ordering = book
580 .cmp(&rhs.book)
581 .then(chap_no.cmp(&rhs.chap_no))
582 .then(vers_no.cmp(&rhs.vers_no));
583 match (meta, rhs.meta) {
584 (PositionMeta::None, PositionMeta::None) => Some(ordering),
585 (PositionMeta::Subverse(ref lhs), PositionMeta::Subverse(ref rhs))
586 | (PositionMeta::VerseRange(ref lhs), PositionMeta::VerseRange(ref rhs)) => {
587 Some(ordering.then(lhs.cmp(rhs)))
588 }
589 _ => None,
590 }
591 }
592}
593
594#[derive(Clone, Debug)]
600pub struct Verse {
601 pos_nrsv: Option<PositionNrsv>,
602 pos_orig: Position,
603 text: String,
604}
605
606impl Verse {
607 #[must_use]
609 pub fn text(&self) -> &str {
610 &*self.text
611 }
612
613 #[must_use]
615 pub const fn pos(&self) -> Position {
616 self.pos_orig
617 }
618
619 #[must_use]
621 pub const fn pos_nrsv(&self) -> Option<PositionNrsv> {
622 self.pos_nrsv
623 }
624}
625
626pub struct Verses<B>(io::Lines<B>);
634
635impl<B: BufRead> Iterator for Verses<B> {
636 type Item = io::Result<Verse>;
637
638 fn next(&mut self) -> Option<Self::Item> {
639 loop {
640 let row = match self.0.next()? {
641 Ok(row) => row,
642 Err(e) => return Some(Err(e)),
643 };
644 if row.trim().is_empty() || row.starts_with('#') {
645 continue;
646 }
647 let mut cols = row.split('\t');
648 let pos_nrsv = match (
649 cols.next().unwrap(),
650 cols.next().unwrap(),
651 cols.next().unwrap(),
652 ) {
653 ("", "", "") => None,
654 (x, y, z) => Some(PositionNrsv {
655 book: Book::new(x).unwrap(),
656 chap_no: u16::from_str(y).unwrap(),
657 vers_no: u16::from_str(z).unwrap(),
658 }),
659 };
660 let (col4, col5, col6, col7, col8) = (
661 cols.next().unwrap(),
662 cols.next().unwrap(),
663 cols.next().unwrap(),
664 cols.next().unwrap(),
665 cols.next().unwrap(),
666 );
667 if Ok(0) == u8::from_str(col8) {
668 continue;
669 }
670 let pos_orig = Position {
671 book: Book::new(col4).unwrap(),
672 chap_no: u16::from_str(col5).unwrap(),
673 vers_no: u16::from_str(col6).unwrap(),
674 meta: PositionMeta::new(col7).unwrap(),
675 };
676 match cols.next() {
677 Some(x) if !x.is_empty() => {
678 assert!(cols.next().is_none());
679 return Some(Ok(Verse {
680 pos_nrsv,
681 pos_orig,
682 text: x.into(),
683 }));
684 }
685 _ => continue,
686 }
687 }
688 }
689}
690
691impl<B: BufRead> Verses<B> {
692 fn new(b: B) -> Self {
693 Self(b.lines())
694 }
695}
696
697#[must_use]
699pub fn from_str(s: &str) -> Verses<&[u8]> {
700 Verses::new(s.as_ref())
701}
702
703#[must_use]
705pub fn from_file(f: File) -> Verses<BufReader<File>> {
706 Verses::new(BufReader::new(f))
707}