bible_bot 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +18 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/LICENSE +25 -0
- data/README.md +144 -0
- data/bible_bot.gemspec +22 -0
- data/lib/bible_bot.rb +7 -0
- data/lib/bible_bot/bible.rb +568 -0
- data/lib/bible_bot/book.rb +80 -0
- data/lib/bible_bot/errors.rb +17 -0
- data/lib/bible_bot/reference.rb +148 -0
- data/lib/bible_bot/reference_match.rb +141 -0
- data/lib/bible_bot/verse.rb +156 -0
- data/lib/bible_bot/version.rb +3 -0
- data/spec/lib/book_spec.rb +57 -0
- data/spec/lib/reference_match_spec.rb +181 -0
- data/spec/lib/reference_spec.rb +136 -0
- data/spec/lib/verse_spec.rb +76 -0
- data/spec/spec_helper.rb +14 -0
- metadata +96 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module BibleBot
|
2
|
+
# Represents one of the 66 books in the bible (Genesis - Revelation).
|
3
|
+
# You should never need to initialize a Book, they are initialized in {Bible}.
|
4
|
+
class Book
|
5
|
+
attr_reader :id # @return [Integer]
|
6
|
+
attr_reader :name # @return [String]
|
7
|
+
attr_reader :abbreviation # @return [String]
|
8
|
+
attr_reader :regex # @return [String]
|
9
|
+
attr_reader :chapters # @return [Array<Integer>]
|
10
|
+
attr_reader :testament # @return [String]
|
11
|
+
|
12
|
+
# Uses the same Regex pattern to match as we use in {Reference.parse}.
|
13
|
+
# So this supports the same book name abbreviations.
|
14
|
+
#
|
15
|
+
# @param name [String]
|
16
|
+
# @return [Book]
|
17
|
+
# @example
|
18
|
+
# Book.find_by_name("Genesis")
|
19
|
+
def self.find_by_name(name)
|
20
|
+
return nil if name.nil? || name.strip == ""
|
21
|
+
|
22
|
+
Bible.books.find { |book| name.match(Regexp.new('\b'+book.regex+'\b', Regexp::IGNORECASE)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Find by the Book ID defined in {Bible}.
|
26
|
+
#
|
27
|
+
# @param id [Integer]
|
28
|
+
# @return [Book]
|
29
|
+
def self.find_by_id(id)
|
30
|
+
Bible.books.find { |book| book.id == id }
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(id:, name:, abbreviation:, regex:, chapters: [] , testament:)
|
34
|
+
@id = id
|
35
|
+
@name = name
|
36
|
+
@abbreviation = abbreviation
|
37
|
+
@regex = regex
|
38
|
+
@chapters = chapters
|
39
|
+
@testament = testament
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def formatted_name
|
44
|
+
case name
|
45
|
+
when 'Psalms' then 'Psalm'
|
46
|
+
else name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Single chapter book like Jude
|
51
|
+
# @return [Boolean]
|
52
|
+
def single_chapter?
|
53
|
+
chapters.length == 1
|
54
|
+
end
|
55
|
+
|
56
|
+
# A reference containing the entire book
|
57
|
+
# @return [Reference]
|
58
|
+
def reference
|
59
|
+
@reference ||= Reference.new(start_verse: start_verse, end_verse: end_verse)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Verse]
|
63
|
+
def start_verse
|
64
|
+
@first_verse ||= Verse.from_id("#{id}001001".to_i)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Verse]
|
68
|
+
def end_verse
|
69
|
+
@last_verse ||= Verse.from_id(
|
70
|
+
"#{id}#{chapters.length.to_s.rjust(3, '0')}#{chapters.last.to_s.rjust(3, '0')}".to_i
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Book, nil]
|
75
|
+
def next_book
|
76
|
+
return @next_book if defined? @next_book
|
77
|
+
@next_book = Book.find_by_id(id + 1)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BibleBot
|
2
|
+
class BibleBotError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
# Raised if Reference is not valid.
|
6
|
+
# @example
|
7
|
+
# "Genesis 4-2"
|
8
|
+
class InvalidReferenceError < BibleBotError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised if Verse is not valid.
|
12
|
+
# In other words, if a chapter or verse are referenced that don't actually exist.
|
13
|
+
# @example
|
14
|
+
# "Genesis 100:2"
|
15
|
+
class InvalidVerseError < BibleBotError
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module BibleBot
|
2
|
+
# A Reference represents a range of verses.
|
3
|
+
class Reference
|
4
|
+
attr_reader :start_verse # @return [Verse]
|
5
|
+
attr_reader :end_verse # @return [Verse]
|
6
|
+
|
7
|
+
# Initialize a {Reference} from {Verse} IDs. If no end_verse_id is provided, it will
|
8
|
+
# set end_verse to equal start_verse.
|
9
|
+
#
|
10
|
+
# @param start_verse_id [Integer]
|
11
|
+
# @param end_verse_id [Integer]
|
12
|
+
# @return [Reference]
|
13
|
+
# @example
|
14
|
+
# BibleBot::Reference.from_verse_ids(1001001, 1001010) #=> (Gen 1:1-10)
|
15
|
+
def self.from_verse_ids(start_verse_id, end_verse_id=nil)
|
16
|
+
new(
|
17
|
+
start_verse: Verse.from_id(start_verse_id),
|
18
|
+
end_verse: Verse.from_id(end_verse_id || start_verse_id),
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parse text into an array of scripture References.
|
23
|
+
#
|
24
|
+
# @param text [String] ex: "John 1:1 is the first but Romans 8:9-10 is another."
|
25
|
+
# @param validate [Boolean, :raise_errors]
|
26
|
+
# * true - Skip invalid references (default)
|
27
|
+
# * false - Include invalid references
|
28
|
+
# * :raise_errors - Raise error if any references are invalid
|
29
|
+
# @return [Array<Reference>]
|
30
|
+
def self.parse(text, validate: true)
|
31
|
+
return [] if text.nil? || text.strip == ""
|
32
|
+
|
33
|
+
ReferenceMatch.scan(text).map(&:reference).select do |ref|
|
34
|
+
ref.validate! if validate == :raise_errors
|
35
|
+
|
36
|
+
!validate || ref.valid?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param start_verse [Verse]
|
41
|
+
# @param end_verse [Verse] Defaults to start_verse if no end_verse is provided
|
42
|
+
def initialize(start_verse:, end_verse: nil)
|
43
|
+
@start_verse = start_verse
|
44
|
+
@end_verse = end_verse || start_verse
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a formatted string of the {Reference}.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
# @example
|
51
|
+
# reference.formatted #=> "Genesis 2:4-5:9"
|
52
|
+
def formatted
|
53
|
+
formatted_verses = [start_verse.formatted(include_verse: !full_chapters?)]
|
54
|
+
|
55
|
+
if end_verse && end_verse > start_verse && !(same_start_and_end_chapter? && full_chapters?)
|
56
|
+
formatted_verses << end_verse.formatted(
|
57
|
+
include_book: !same_start_and_end_book?,
|
58
|
+
include_chapter: !same_start_and_end_chapter?,
|
59
|
+
include_verse: !full_chapters?,
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
formatted_verses.join('-')
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Boolean]
|
67
|
+
def same_start_and_end_book?
|
68
|
+
start_verse.book == end_verse&.book
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Boolean]
|
72
|
+
def same_start_and_end_chapter?
|
73
|
+
same_start_and_end_book? &&
|
74
|
+
start_verse.chapter_number == end_verse&.chapter_number
|
75
|
+
end
|
76
|
+
|
77
|
+
# One or multiple full chapters.
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
def full_chapters?
|
81
|
+
start_verse.verse_number == 1 && end_verse&.last_verse_in_chapter?
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [string]
|
85
|
+
def to_s
|
86
|
+
"BibleBot::Reference — #{formatted}"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if the given verse is within the start and end verse of the Reference.
|
90
|
+
#
|
91
|
+
# @param verse [Verse]
|
92
|
+
# @return [Boolean]
|
93
|
+
def includes_verse?(verse)
|
94
|
+
return false unless verse.is_a?(Verse)
|
95
|
+
|
96
|
+
start_verse <= verse && verse <= end_verse
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return true if the two references contain any of the same verses.
|
100
|
+
# @param other [Reference]
|
101
|
+
# @return [Boolean]
|
102
|
+
def intersects_reference?(other)
|
103
|
+
return false unless other.is_a?(Reference)
|
104
|
+
|
105
|
+
start_verse <= other.end_verse && end_verse >= other.start_verse
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns an array of all the verses contained in the Reference.
|
109
|
+
#
|
110
|
+
# @return [Array<Verse>]
|
111
|
+
def verses
|
112
|
+
return @verses if defined? @verses
|
113
|
+
|
114
|
+
@verses = []
|
115
|
+
return @verses unless valid?
|
116
|
+
|
117
|
+
verse = start_verse
|
118
|
+
|
119
|
+
loop do
|
120
|
+
@verses << verse
|
121
|
+
break if end_verse.nil? || verse == end_verse
|
122
|
+
verse = verse.next_verse
|
123
|
+
end
|
124
|
+
|
125
|
+
@verses
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Hash]
|
129
|
+
def inspect
|
130
|
+
{
|
131
|
+
start_verse: start_verse&.formatted,
|
132
|
+
end_verse: end_verse&.formatted,
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Boolean]
|
137
|
+
def valid?
|
138
|
+
start_verse&.valid? && end_verse&.valid? && end_verse >= start_verse
|
139
|
+
end
|
140
|
+
|
141
|
+
# Raises error if reference is invalid
|
142
|
+
def validate!
|
143
|
+
start_verse&.validate!
|
144
|
+
end_verse&.validate!
|
145
|
+
raise InvalidReferenceError.new "Reference is not vaild: #{inspect}" unless valid?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module BibleBot
|
2
|
+
# This class contains all the logic for mapping the different parts of a scripture Match into an actual {Reference}.
|
3
|
+
# It wraps the Match returned from the regular expression defined in {Bible.scripture_re}.
|
4
|
+
#
|
5
|
+
# A scripture reference can take many forms, but the least abbreviated form is:
|
6
|
+
#
|
7
|
+
# Genesis 1:1 - Genesis 1:2
|
8
|
+
#
|
9
|
+
# Internally, this class represents this form using the following variables:
|
10
|
+
#
|
11
|
+
# b1 c1:v1 - b2 c2:v2
|
12
|
+
#
|
13
|
+
# See Readme for list of supported abbreviation rules.
|
14
|
+
#
|
15
|
+
# == Advanced Use Cases
|
16
|
+
#
|
17
|
+
# This is a low level class used internally by {Reference.parse}.
|
18
|
+
# There are however some advanced use cases which you might want to use it for.
|
19
|
+
# For example, if you want to know where in the parsed String certain matches occur.
|
20
|
+
#
|
21
|
+
# For this there are a few convenience attributes:
|
22
|
+
#
|
23
|
+
# * {#match}
|
24
|
+
# * {#length}
|
25
|
+
# * {#offset}
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# matches = ReferenceMatch.scan("Mark 1:5 and another Romans 4:1")
|
29
|
+
# matches[0].match[0] #=> "Mark 1:5"
|
30
|
+
# matches[0].offset #=> 0
|
31
|
+
# matches[0].length #=> 8
|
32
|
+
# matches[0].match[0] #=> "Romans 4:1"
|
33
|
+
# matches[1].offset #=> 21
|
34
|
+
# matches[1].length #=> 10
|
35
|
+
#
|
36
|
+
# @note You shouldn't need to use this class directly. For the majority of use cases, just use {Reference.parse}.
|
37
|
+
class ReferenceMatch
|
38
|
+
attr_reader :match # @return [Match] The Match instance returned from the Regexp
|
39
|
+
attr_reader :length # @return [Integer] The length of the match in the text string
|
40
|
+
attr_reader :offset # @return [Integer] The starting position of the match in the text string
|
41
|
+
|
42
|
+
# Converts a string into an array of ReferenceMatches.
|
43
|
+
# Note: Does not validate References.
|
44
|
+
#
|
45
|
+
# @param text [String]
|
46
|
+
# @return [Array<ReferenceMatch>]
|
47
|
+
def self.scan(text)
|
48
|
+
scripture_reg = Bible.scripture_re
|
49
|
+
Array.new.tap do |matches|
|
50
|
+
text.scan(scripture_reg){ matches << self.new($~, $~.offset(0)[0]) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Reference] Note: Reference is not yet validated
|
55
|
+
def reference
|
56
|
+
@reference ||= Reference.new(
|
57
|
+
start_verse: Verse.new(book: start_book, chapter_number: start_chapter.to_i, verse_number: start_verse.to_i),
|
58
|
+
end_verse: Verse.new(book: end_book, chapter_number: end_chapter.to_i, verse_number: end_verse.to_i),
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :b1 # @return [String]
|
65
|
+
attr_reader :c1 # @return [String] Represents the number after the start Book name, could be either chapter or verse number.
|
66
|
+
attr_reader :v1 # @return [String, nil] Represents the number after the colon, will always be start_verse if present.
|
67
|
+
attr_reader :b2 # @return [String, nil]
|
68
|
+
attr_reader :c2 # @return [String, nil] Represents the number after the end Book name, could be either chapter or verse number.
|
69
|
+
attr_reader :v2 # @return [String, nil] Represents the number after the colon, will always be end_verse if present.
|
70
|
+
|
71
|
+
# @param match [Match]
|
72
|
+
# @param offset [Integer]
|
73
|
+
def initialize(match, offset)
|
74
|
+
@match = match
|
75
|
+
@length = match.to_s.length
|
76
|
+
@offset = offset
|
77
|
+
@b1 = match[:BookTitle]
|
78
|
+
@c1 = match[:ChapterNumber]
|
79
|
+
@v1 = match[:VerseNumber]
|
80
|
+
@b2 = match[:EndBookTitle]
|
81
|
+
@c2 = match[:EndChapterNumber]
|
82
|
+
@v2 = match[:EndVerseNumber]
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [Book]
|
86
|
+
def start_book
|
87
|
+
# There will always be a starting book.
|
88
|
+
Book.find_by_name(@b1)
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Book]
|
92
|
+
def end_book
|
93
|
+
# The end book is optional. If not provided, default to starting book.
|
94
|
+
Book.find_by_name(@b2) || start_book
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Integer]
|
98
|
+
def start_chapter
|
99
|
+
# Start chapter should always be provided, except in the case of single chapter books.
|
100
|
+
# Jude 5 for example, c1==5 but the chapter should actually be 1.
|
101
|
+
return 1 if start_book.single_chapter?
|
102
|
+
c1
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [Integer]
|
106
|
+
def start_verse
|
107
|
+
# If there is a number in the v1 position, it will always represent the starting verse.
|
108
|
+
# There are a few cases where the start_verse will be in a different position or inferred.
|
109
|
+
# * Jude 4 (start_verse is in the c1 position)
|
110
|
+
# * Genesis 5 (start_verse is inferred to be 1, and end_verse is the last verse in Genesis 5)
|
111
|
+
v1 || (start_book.single_chapter? ? c1 : 1)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Integer]
|
115
|
+
def end_chapter
|
116
|
+
return start_chapter if single_verse_ref? # Ex: Genesis 1:3 => "1"
|
117
|
+
return 1 if end_book.single_chapter? # Ex: Jude 2-4 => "1"
|
118
|
+
return c1 if !b2 && !v2 && v1 # Ex: Genesis 1:2-3 => "1"
|
119
|
+
c2 || # Ex: Genesis 1:1 - 2:4 => "4"
|
120
|
+
c1 # Ex: Genesis 5 => "5"
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Integer]
|
124
|
+
def end_verse
|
125
|
+
return start_verse if single_verse_ref? # Ex: Genesis 1:3 => "3"
|
126
|
+
v2 || # Ex: Genesis 1:4 - 2:5 => "5"
|
127
|
+
(
|
128
|
+
(v1 && !b2) ?
|
129
|
+
c2 : # Ex: Gen 1:4-8 => "8"
|
130
|
+
end_book.chapters[end_chapter.to_i - 1] # Genesis 1 => "31"
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [Boolean]
|
135
|
+
def single_verse_ref?
|
136
|
+
!b2 && !c2 && !v2 &&
|
137
|
+
(v1 || start_book.single_chapter?) # Ex: Genesis 5:1 || Jude 5
|
138
|
+
# Genesis 5 is not a single verse ref
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module BibleBot
|
2
|
+
# Verse represents a single verse in the bible.
|
3
|
+
class Verse
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_reader :book # @return [Book]
|
7
|
+
attr_reader :chapter_number # @return [Integer]
|
8
|
+
attr_reader :verse_number # @return [Integer]
|
9
|
+
|
10
|
+
# Turns an Inteter into a Verse
|
11
|
+
# For more details, see note above the `id` method.
|
12
|
+
#
|
13
|
+
# @param id [Integer]
|
14
|
+
# @return [Verse]
|
15
|
+
# @example
|
16
|
+
# Verse.from_id(19_105_001) #=> <Verse book="Psalms" chapter_number=105 verse_number=1>
|
17
|
+
def self.from_id(id)
|
18
|
+
return from_string_id(id) if id.is_a?(String)
|
19
|
+
return nil if id.nil?
|
20
|
+
raise BibleBot::InvalidVerseError unless id.is_a?(Integer)
|
21
|
+
|
22
|
+
book_id = id / 1_000_000
|
23
|
+
chapter_number = id / 1_000 % 1_000
|
24
|
+
verse_number = id % 1_000
|
25
|
+
book = BibleBot::Book.find_by_id(book_id)
|
26
|
+
|
27
|
+
new(book: book, chapter_number: chapter_number, verse_number: verse_number)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param book [Book]
|
31
|
+
# @param chapter_number [Integer]
|
32
|
+
# @param verse_number [Integer]
|
33
|
+
def initialize(book:, chapter_number:, verse_number:)
|
34
|
+
@book = book
|
35
|
+
@chapter_number = chapter_number
|
36
|
+
@verse_number = verse_number
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an Integer in the from of
|
40
|
+
#
|
41
|
+
# |- book.id
|
42
|
+
# | |- chapter_number
|
43
|
+
# | | |- verse_number
|
44
|
+
# XX_XXX_XXX
|
45
|
+
#
|
46
|
+
# Storing as an Integer makes it super convenient to store in a database
|
47
|
+
# and compare verses and verse ranges using simple database queries
|
48
|
+
#
|
49
|
+
# @return [Integer]
|
50
|
+
# @example
|
51
|
+
# verse.id #=> 19_105_001
|
52
|
+
# #-> this represents "Psalm 105:1"
|
53
|
+
def id
|
54
|
+
@id ||= "#{book.id}#{chapter_number.to_s.rjust(3, '0')}#{verse_number.to_s.rjust(3, '0')}".to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
# @deprecated Use {id} instead
|
58
|
+
# @return [String] ex: "psalms-023-001"
|
59
|
+
def string_id
|
60
|
+
"#{book.name.downcase.gsub(' ', '_')}-#{chapter_number.to_s.rjust(3, '0')}-#{verse_number.to_s.rjust(3, '0')}"
|
61
|
+
end
|
62
|
+
|
63
|
+
# The Comparable mixin uses this to define all the other comparable methods
|
64
|
+
#
|
65
|
+
# @param other [Verse]
|
66
|
+
# @return [Integer] Either -1, 0, or 1
|
67
|
+
# * -1: this verse is less than the other verse
|
68
|
+
# * 0: this verse is equal to the other verse
|
69
|
+
# * 1: this verse is greater than the other verse
|
70
|
+
def <=>(other)
|
71
|
+
id <=> other.id
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param include_book [Boolean]
|
75
|
+
# @param include_chapter [Boolean]
|
76
|
+
# @param include_verse [Boolean]
|
77
|
+
# @return [String]
|
78
|
+
# @example
|
79
|
+
# verse.formatted #=> "Genesis 5:23"
|
80
|
+
def formatted(include_book: true, include_chapter: true, include_verse: true)
|
81
|
+
str = String.new # Using String.new because string literals will be frozen in Ruby 3.0
|
82
|
+
str << "#{book.formatted_name} " if include_book
|
83
|
+
|
84
|
+
if book.single_chapter?
|
85
|
+
str << "#{verse_number}" if include_verse
|
86
|
+
else
|
87
|
+
str << "#{chapter_number}" if include_chapter
|
88
|
+
str << ":" if include_chapter && include_verse
|
89
|
+
str << "#{verse_number}" if include_verse
|
90
|
+
end
|
91
|
+
|
92
|
+
str.strip.freeze
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns next verse. It will reach into the next chapter or the next book
|
96
|
+
# until it gets to the last verse in the bible,
|
97
|
+
# at which point it will return nil.
|
98
|
+
#
|
99
|
+
# @return [Verse, nil]
|
100
|
+
def next_verse
|
101
|
+
return Verse.new(book: book, chapter_number: chapter_number, verse_number: verse_number + 1) unless last_verse_in_chapter?
|
102
|
+
return Verse.new(book: book, chapter_number: chapter_number + 1, verse_number: 1) unless last_chapter_in_book?
|
103
|
+
return Verse.new(book: book.next_book, chapter_number: 1, verse_number: 1) if book.next_book
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Boolean]
|
108
|
+
def last_verse_in_chapter?
|
109
|
+
verse_number == book.chapters[chapter_number - 1]
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Boolean]
|
113
|
+
def last_chapter_in_book?
|
114
|
+
chapter_number == book.chapters.length
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Hash]
|
118
|
+
def inspect
|
119
|
+
{
|
120
|
+
book: book&.name,
|
121
|
+
chapter_number: chapter_number,
|
122
|
+
verse_number: verse_number
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [Boolean]
|
127
|
+
def valid?
|
128
|
+
book.is_a?(BibleBot::Book) &&
|
129
|
+
chapter_number.is_a?(Integer) && chapter_number >= 1 && chapter_number <= book.chapters.length &&
|
130
|
+
verse_number.is_a?(Integer) && verse_number >= 1 && verse_number <= book.chapters[chapter_number-1]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Raises error if reference is invalid
|
134
|
+
def validate!
|
135
|
+
raise InvalidVerseError.new "Verse is not valid: #{inspect}" unless valid?
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# This gets called by {from_id} to allow it to be backwards compatible for a while.
|
141
|
+
# @deprecated Use {from_id} instead.
|
142
|
+
# @param verse_id [String] ex: "genesis-001-001"
|
143
|
+
# @return [Verse] ex: <Verse book="Genesis" chapter_number=1 verse_number=1>
|
144
|
+
def self.from_string_id(string_id)
|
145
|
+
parts = string_id.split( '-' )
|
146
|
+
|
147
|
+
book_name = parts[0].gsub( '_', ' ' )
|
148
|
+
chapter_number = parts[1].to_i
|
149
|
+
verse_number = parts[2].to_i
|
150
|
+
|
151
|
+
book = BibleBot::Bible.books.select{ |b| b.name.downcase == book_name }.first
|
152
|
+
|
153
|
+
new(book: book, chapter_number: chapter_number, verse_number: verse_number)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|