1use core::{fmt, str::FromStr};
4
5macro_rules! define_books {
6 (
7 $(
8 $variant:ident => {
9 abbreviation: $abbreviation:literal,
10 full_name: $full_name:literal,
11 osis: $osis:literal,
12 usfm: $usfm:literal,
13 }
14 ),+ $(,)?
15 ) => {
16 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
21 #[repr(u8)]
22 pub enum Book {
23 $(
24 #[doc = $full_name]
25 $variant
26 ),+
27 }
28
29 impl Book {
30 pub const ALL: &'static [Self] = &[$(Self::$variant),+];
32
33 #[must_use]
35 pub const fn abbreviation(self) -> &'static str {
36 match self {
37 $(Self::$variant => $abbreviation),+
38 }
39 }
40
41 #[must_use]
43 pub const fn full_name(self) -> &'static str {
44 match self {
45 $(Self::$variant => $full_name),+
46 }
47 }
48
49 #[must_use]
51 pub const fn osis(self) -> &'static str {
52 match self {
53 $(Self::$variant => $osis),+
54 }
55 }
56
57 #[must_use]
59 pub const fn usfm(self) -> &'static str {
60 match self {
61 $(Self::$variant => $usfm),+
62 }
63 }
64
65 #[must_use]
69 pub fn from_abbreviation(value: &str) -> Option<Self> {
70 Self::ALL
71 .iter()
72 .copied()
73 .find(|book| book.abbreviation().eq_ignore_ascii_case(value))
74 }
75
76 #[must_use]
78 pub fn from_osis(value: &str) -> Option<Self> {
79 match value {
80 $($osis => Some(Self::$variant),)+
81 _ => None,
82 }
83 }
84
85 #[must_use]
87 pub fn from_usfm(value: &str) -> Option<Self> {
88 match value {
89 $($usfm => Some(Self::$variant),)+
90 _ => None,
91 }
92 }
93
94 fn from_full_name(value: &str) -> Option<Self> {
95 Self::ALL
96 .iter()
97 .copied()
98 .find(|book| book.full_name().eq_ignore_ascii_case(value))
99 }
100 }
101 };
102}
103
104define_books! {
105 Genesis => {
106 abbreviation: "gn",
107 full_name: "Genesis",
108 osis: "Gen",
109 usfm: "GEN",
110 },
111 Exodus => {
112 abbreviation: "ex",
113 full_name: "Exodus",
114 osis: "Exod",
115 usfm: "EXO",
116 },
117 Leviticus => {
118 abbreviation: "lv",
119 full_name: "Leviticus",
120 osis: "Lev",
121 usfm: "LEV",
122 },
123 Numbers => {
124 abbreviation: "nm",
125 full_name: "Numbers",
126 osis: "Num",
127 usfm: "NUM",
128 },
129 Deuteronomy => {
130 abbreviation: "dt",
131 full_name: "Deuteronomy",
132 osis: "Deut",
133 usfm: "DEU",
134 },
135 Joshua => {
136 abbreviation: "js",
137 full_name: "Joshua",
138 osis: "Josh",
139 usfm: "JOS",
140 },
141 Judges => {
142 abbreviation: "jud",
143 full_name: "Judges",
144 osis: "Judg",
145 usfm: "JDG",
146 },
147 Ruth => {
148 abbreviation: "rt",
149 full_name: "Ruth",
150 osis: "Ruth",
151 usfm: "RUT",
152 },
153 FirstSamuel => {
154 abbreviation: "1sm",
155 full_name: "1 Samuel",
156 osis: "1Sam",
157 usfm: "1SA",
158 },
159 SecondSamuel => {
160 abbreviation: "2sm",
161 full_name: "2 Samuel",
162 osis: "2Sam",
163 usfm: "2SA",
164 },
165 FirstKings => {
166 abbreviation: "1kgs",
167 full_name: "1 Kings",
168 osis: "1Kgs",
169 usfm: "1KI",
170 },
171 SecondKings => {
172 abbreviation: "2kgs",
173 full_name: "2 Kings",
174 osis: "2Kgs",
175 usfm: "2KI",
176 },
177 FirstChronicles => {
178 abbreviation: "1ch",
179 full_name: "1 Chronicles",
180 osis: "1Chr",
181 usfm: "1CH",
182 },
183 SecondChronicles => {
184 abbreviation: "2ch",
185 full_name: "2 Chronicles",
186 osis: "2Chr",
187 usfm: "2CH",
188 },
189 Ezra => {
190 abbreviation: "ezr",
191 full_name: "Ezra",
192 osis: "Ezra",
193 usfm: "EZR",
194 },
195 Nehemiah => {
196 abbreviation: "ne",
197 full_name: "Nehemiah",
198 osis: "Neh",
199 usfm: "NEH",
200 },
201 Esther => {
202 abbreviation: "et",
203 full_name: "Esther",
204 osis: "Esth",
205 usfm: "EST",
206 },
207 Job => {
208 abbreviation: "job",
209 full_name: "Job",
210 osis: "Job",
211 usfm: "JOB",
212 },
213 Psalms => {
214 abbreviation: "ps",
215 full_name: "Psalms",
216 osis: "Ps",
217 usfm: "PSA",
218 },
219 Proverbs => {
220 abbreviation: "prv",
221 full_name: "Proverbs",
222 osis: "Prov",
223 usfm: "PRO",
224 },
225 Ecclesiastes => {
226 abbreviation: "ec",
227 full_name: "Ecclesiastes",
228 osis: "Eccl",
229 usfm: "ECC",
230 },
231 SongOfSolomon => {
232 abbreviation: "so",
233 full_name: "Song of Solomon",
234 osis: "Song",
235 usfm: "SNG",
236 },
237 Isaiah => {
238 abbreviation: "is",
239 full_name: "Isaiah",
240 osis: "Isa",
241 usfm: "ISA",
242 },
243 Jeremiah => {
244 abbreviation: "jr",
245 full_name: "Jeremiah",
246 osis: "Jer",
247 usfm: "JER",
248 },
249 Lamentations => {
250 abbreviation: "lm",
251 full_name: "Lamentations",
252 osis: "Lam",
253 usfm: "LAM",
254 },
255 Ezekiel => {
256 abbreviation: "ez",
257 full_name: "Ezekiel",
258 osis: "Ezek",
259 usfm: "EZK",
260 },
261 Daniel => {
262 abbreviation: "dn",
263 full_name: "Daniel",
264 osis: "Dan",
265 usfm: "DAN",
266 },
267 Hosea => {
268 abbreviation: "ho",
269 full_name: "Hosea",
270 osis: "Hos",
271 usfm: "HOS",
272 },
273 Joel => {
274 abbreviation: "jl",
275 full_name: "Joel",
276 osis: "Joel",
277 usfm: "JOL",
278 },
279 Amos => {
280 abbreviation: "am",
281 full_name: "Amos",
282 osis: "Amos",
283 usfm: "AMO",
284 },
285 Obadiah => {
286 abbreviation: "ob",
287 full_name: "Obadiah",
288 osis: "Obad",
289 usfm: "OBA",
290 },
291 Jonah => {
292 abbreviation: "jn",
293 full_name: "Jonah",
294 osis: "Jonah",
295 usfm: "JON",
296 },
297 Micah => {
298 abbreviation: "mi",
299 full_name: "Micah",
300 osis: "Mic",
301 usfm: "MIC",
302 },
303 Nahum => {
304 abbreviation: "na",
305 full_name: "Nahum",
306 osis: "Nah",
307 usfm: "NAM",
308 },
309 Habakkuk => {
310 abbreviation: "hk",
311 full_name: "Habakkuk",
312 osis: "Hab",
313 usfm: "HAB",
314 },
315 Zephaniah => {
316 abbreviation: "zp",
317 full_name: "Zephaniah",
318 osis: "Zeph",
319 usfm: "ZEP",
320 },
321 Haggai => {
322 abbreviation: "hg",
323 full_name: "Haggai",
324 osis: "Hag",
325 usfm: "HAG",
326 },
327 Zechariah => {
328 abbreviation: "zc",
329 full_name: "Zechariah",
330 osis: "Zech",
331 usfm: "ZEC",
332 },
333 Malachi => {
334 abbreviation: "ml",
335 full_name: "Malachi",
336 osis: "Mal",
337 usfm: "MAL",
338 },
339 Matthew => {
340 abbreviation: "mt",
341 full_name: "Matthew",
342 osis: "Matt",
343 usfm: "MAT",
344 },
345 Mark => {
346 abbreviation: "mk",
347 full_name: "Mark",
348 osis: "Mark",
349 usfm: "MRK",
350 },
351 Luke => {
352 abbreviation: "lk",
353 full_name: "Luke",
354 osis: "Luke",
355 usfm: "LUK",
356 },
357 John => {
358 abbreviation: "jo",
359 full_name: "John",
360 osis: "John",
361 usfm: "JHN",
362 },
363 Acts => {
364 abbreviation: "act",
365 full_name: "Acts",
366 osis: "Acts",
367 usfm: "ACT",
368 },
369 Romans => {
370 abbreviation: "rm",
371 full_name: "Romans",
372 osis: "Rom",
373 usfm: "ROM",
374 },
375 FirstCorinthians => {
376 abbreviation: "1co",
377 full_name: "1 Corinthians",
378 osis: "1Cor",
379 usfm: "1CO",
380 },
381 SecondCorinthians => {
382 abbreviation: "2co",
383 full_name: "2 Corinthians",
384 osis: "2Cor",
385 usfm: "2CO",
386 },
387 Galatians => {
388 abbreviation: "gl",
389 full_name: "Galatians",
390 osis: "Gal",
391 usfm: "GAL",
392 },
393 Ephesians => {
394 abbreviation: "eph",
395 full_name: "Ephesians",
396 osis: "Eph",
397 usfm: "EPH",
398 },
399 Philippians => {
400 abbreviation: "ph",
401 full_name: "Philippians",
402 osis: "Phil",
403 usfm: "PHP",
404 },
405 Colossians => {
406 abbreviation: "cl",
407 full_name: "Colossians",
408 osis: "Col",
409 usfm: "COL",
410 },
411 FirstThessalonians => {
412 abbreviation: "1ts",
413 full_name: "1 Thessalonians",
414 osis: "1Thess",
415 usfm: "1TH",
416 },
417 SecondThessalonians => {
418 abbreviation: "2ts",
419 full_name: "2 Thessalonians",
420 osis: "2Thess",
421 usfm: "2TH",
422 },
423 FirstTimothy => {
424 abbreviation: "1tm",
425 full_name: "1 Timothy",
426 osis: "1Tim",
427 usfm: "1TI",
428 },
429 SecondTimothy => {
430 abbreviation: "2tm",
431 full_name: "2 Timothy",
432 osis: "2Tim",
433 usfm: "2TI",
434 },
435 Titus => {
436 abbreviation: "tt",
437 full_name: "Titus",
438 osis: "Titus",
439 usfm: "TIT",
440 },
441 Philemon => {
442 abbreviation: "phm",
443 full_name: "Philemon",
444 osis: "Phlm",
445 usfm: "PHM",
446 },
447 Hebrews => {
448 abbreviation: "hb",
449 full_name: "Hebrews",
450 osis: "Heb",
451 usfm: "HEB",
452 },
453 James => {
454 abbreviation: "jm",
455 full_name: "James",
456 osis: "Jas",
457 usfm: "JAS",
458 },
459 FirstPeter => {
460 abbreviation: "1pe",
461 full_name: "1 Peter",
462 osis: "1Pet",
463 usfm: "1PE",
464 },
465 SecondPeter => {
466 abbreviation: "2pe",
467 full_name: "2 Peter",
468 osis: "2Pet",
469 usfm: "2PE",
470 },
471 FirstJohn => {
472 abbreviation: "1jo",
473 full_name: "1 John",
474 osis: "1John",
475 usfm: "1JN",
476 },
477 SecondJohn => {
478 abbreviation: "2jo",
479 full_name: "2 John",
480 osis: "2John",
481 usfm: "2JN",
482 },
483 ThirdJohn => {
484 abbreviation: "3jo",
485 full_name: "3 John",
486 osis: "3John",
487 usfm: "3JN",
488 },
489 Jude => {
490 abbreviation: "jd",
491 full_name: "Jude",
492 osis: "Jude",
493 usfm: "JUD",
494 },
495 Revelation => {
496 abbreviation: "re",
497 full_name: "Revelation",
498 osis: "Rev",
499 usfm: "REV",
500 },
501 Tobit => {
502 abbreviation: "tb",
503 full_name: "Tobit",
504 osis: "Tob",
505 usfm: "TOB",
506 },
507 Judith => {
508 abbreviation: "jdt",
509 full_name: "Judith",
510 osis: "Jdt",
511 usfm: "JDT",
512 },
513 Wisdom => {
514 abbreviation: "ws",
515 full_name: "Wisdom",
516 osis: "Wis",
517 usfm: "WIS",
518 },
519 Sirach => {
520 abbreviation: "sir",
521 full_name: "Sirach",
522 osis: "Sir",
523 usfm: "SIR",
524 },
525 Baruch => {
526 abbreviation: "bar",
527 full_name: "Baruch",
528 osis: "Bar",
529 usfm: "BAR",
530 },
531 FirstMaccabees => {
532 abbreviation: "1mc",
533 full_name: "1 Maccabees",
534 osis: "1Macc",
535 usfm: "1MA",
536 },
537 SecondMaccabees => {
538 abbreviation: "2mc",
539 full_name: "2 Maccabees",
540 osis: "2Macc",
541 usfm: "2MA",
542 },
543 EstherAdditions => {
544 abbreviation: "etg",
545 full_name: "Esther (Greek)",
546 osis: "AddEsth",
547 usfm: "ADE",
548 },
549 DanielSongOfThree => {
550 abbreviation: "dn3",
551 full_name: "Daniel (Song of Three)",
552 osis: "PrAzar",
553 usfm: "S3Y",
554 },
555 DanielSusanna => {
556 abbreviation: "dns",
557 full_name: "Daniel (Susanna)",
558 osis: "Sus",
559 usfm: "SUS",
560 },
561 DanielBelAndTheDragon => {
562 abbreviation: "dnb",
563 full_name: "Daniel (Bel and the Dragon)",
564 osis: "Bel",
565 usfm: "BEL",
566 },
567 FirstEsdras => {
568 abbreviation: "1es",
569 full_name: "1 Esdras",
570 osis: "1Esd",
571 usfm: "1ES",
572 },
573 SecondEsdras => {
574 abbreviation: "2es",
575 full_name: "2 Esdras",
576 osis: "2Esd",
577 usfm: "2ES",
578 },
579 PrayerOfManasseh => {
580 abbreviation: "pmn",
581 full_name: "Prayer of Manasseh",
582 osis: "PrMan",
583 usfm: "MAN",
584 },
585 Psalm151 => {
586 abbreviation: "ps151",
587 full_name: "Psalm 151",
588 osis: "AddPs",
589 usfm: "PS2",
590 },
591 ThirdMaccabees => {
592 abbreviation: "3mc",
593 full_name: "3 Maccabees",
594 osis: "3Macc",
595 usfm: "3MA",
596 },
597 FourthMaccabees => {
598 abbreviation: "4mc",
599 full_name: "4 Maccabees",
600 osis: "4Macc",
601 usfm: "4MA",
602 },
603}
604
605impl fmt::Display for Book {
606 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
607 formatter.write_str(self.full_name())
608 }
609}
610
611#[derive(Clone, Debug, Eq, PartialEq)]
613pub struct ParseBookError {
614 input: String,
615}
616
617impl ParseBookError {
618 #[must_use]
620 pub fn input(&self) -> &str {
621 &self.input
622 }
623}
624
625impl fmt::Display for ParseBookError {
626 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
627 write!(formatter, "unrecognized Bible book: {:?}", self.input)
628 }
629}
630
631impl std::error::Error for ParseBookError {}
632
633impl FromStr for Book {
634 type Err = ParseBookError;
635
636 fn from_str(value: &str) -> Result<Self, Self::Err> {
637 Self::from_abbreviation(value)
640 .or_else(|| Self::from_full_name(value))
641 .or_else(|| Self::from_osis(value))
642 .or_else(|| Self::from_usfm(value))
643 .ok_or_else(|| ParseBookError {
644 input: value.to_owned(),
645 })
646 }
647}
648
649#[cfg(test)]
650#[path = "../tests/unit/book.rs"]
651mod tests;