bible_bot 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|