wcc-pericope 1.1.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.
data/lib/pericope.rb ADDED
@@ -0,0 +1,247 @@
1
+ require "pericope/version"
2
+ require "pericope/data"
3
+ require "pericope/parsing"
4
+
5
+ class Pericope
6
+ extend Pericope::Parsing
7
+ include Enumerable
8
+
9
+ attr_reader :book, :original_string, :ranges
10
+
11
+
12
+ def initialize(arg)
13
+ case arg
14
+ when String
15
+ attributes = Pericope.match_one(arg)
16
+ raise ArgumentError, "no pericope found in #{arg}" if attributes.nil?
17
+
18
+ @original_string = attributes[:original_string]
19
+ @book = attributes[:book]
20
+ @ranges = attributes[:ranges]
21
+
22
+ when Array
23
+ @ranges = group_array_into_ranges(arg)
24
+ @book = @ranges.first.begin.book
25
+
26
+ else
27
+ @original_string = arg[:original_string]
28
+ @book = arg[:book]
29
+ @ranges = arg[:ranges]
30
+
31
+ end
32
+ raise ArgumentError, "must specify book" unless @book
33
+ end
34
+
35
+
36
+ def book_has_chapters?
37
+ book_chapter_count > 1
38
+ end
39
+
40
+ def book_name
41
+ @book_name ||= Pericope::BOOK_NAMES[@book]
42
+ end
43
+
44
+ def book_chapter_count
45
+ @book_chapter_count ||= Pericope::BOOK_CHAPTER_COUNTS[@book]
46
+ end
47
+
48
+
49
+ def to_s(options={})
50
+ ["#{book_name}", well_formatted_reference(options)].compact.join(" ")
51
+ end
52
+
53
+ def inspect
54
+ "Pericope(#{to_s})"
55
+ end
56
+
57
+
58
+ def ==(other)
59
+ other.is_a?(self.class) && [book, ranges] == [other.book, other.ranges]
60
+ end
61
+
62
+ def hash
63
+ [book, ranges].hash
64
+ end
65
+
66
+ def <=>(other)
67
+ to_a <=> other.to_a
68
+ end
69
+
70
+ def intersects?(other)
71
+ return false unless other.is_a?(Pericope)
72
+ return false unless book == other.book
73
+
74
+ ranges.each do |self_range|
75
+ other.ranges.each do |other_range|
76
+ return true if (self_range.end >= other_range.begin) and (self_range.begin <= other_range.end)
77
+ end
78
+ end
79
+
80
+ false
81
+ end
82
+
83
+
84
+ def each
85
+ return to_enum unless block_given?
86
+
87
+ ranges.each do |range|
88
+ range.each do |verse|
89
+ yield verse
90
+ end
91
+ end
92
+
93
+ self
94
+ end
95
+
96
+
97
+ class << self
98
+ attr_reader :max_letter
99
+
100
+ def max_letter=(value)
101
+ unless @max_letter == value
102
+ @max_letter = value.freeze
103
+ @_letters = nil
104
+ @_regexp = nil
105
+ @_normalizations = nil
106
+ @_letter_regexp = nil
107
+ @_fragment_regexp = nil
108
+ end
109
+ end
110
+
111
+ def book_has_chapters?(book)
112
+ BOOK_CHAPTER_COUNTS[book] > 1
113
+ end
114
+
115
+ def get_max_verse(book, chapter)
116
+ id = (book * 1000000) + (chapter * 1000)
117
+ CHAPTER_VERSE_COUNTS[id]
118
+ end
119
+
120
+ def get_max_chapter(book)
121
+ BOOK_CHAPTER_COUNTS[book]
122
+ end
123
+
124
+ def regexp
125
+ @_regexp ||= /#{book_pattern}\.?\s*(#{reference_pattern})/i
126
+ end
127
+
128
+ def normalizations
129
+ @_normalizations ||= [
130
+ [/(\d+)\s*[".]\s*(\d+)/, '\1:\2'], # 12"5 and 12.5 -> 12:5
131
+ [/[–—]/, '-'], # convert em dash and en dash to -
132
+ [/[^0-9,:;\-–—#{letters}]/, ''] ] # remove everything but recognized symbols
133
+ end
134
+
135
+ def letter_regexp
136
+ @_letter_regexp ||= /[#{letters}]$/
137
+ end
138
+
139
+ def fragment_regexp
140
+ @_fragment_regexp ||= /^(?:(?<chapter>\d{1,3}):)?(?<verse>\d{1,3})?(?<letter>[#{letters}])?$/
141
+ end
142
+
143
+ def book_regexp
144
+ @_book_regexp ||= /#{book_pattern}/i
145
+ end
146
+
147
+ private
148
+
149
+ def book_pattern
150
+ BOOK_PATTERN.source.gsub(/[ \n]/, "")
151
+ end
152
+
153
+ def reference_pattern
154
+ number = '\d{1,3}'
155
+ verse = "#{number}[#{letters}]?"
156
+ chapter_verse_separator = '\s*[:"\.]\s*'
157
+ list_or_range_separator = '\s*[\-–—,;]\s*'
158
+ chapter_and_verse = "(?:#{number + chapter_verse_separator})?" + verse + '\b'
159
+ chapter_and_verse_or_letter = "(?:#{chapter_and_verse}|[#{letters}]\\b)"
160
+ chapter_and_verse + "(?:#{list_or_range_separator + chapter_and_verse_or_letter})*"
161
+ end
162
+
163
+ def letters
164
+ @_letters ||= ("a"..max_letter).to_a.join
165
+ end
166
+
167
+ end
168
+
169
+
170
+ private
171
+
172
+ def well_formatted_reference(options={})
173
+ return nil if ranges.empty?
174
+
175
+ verse_range_separator = options.fetch(:verse_range_separator, "–") # en-dash
176
+ chapter_range_separator = options.fetch(:chapter_range_separator, "—") # em-dash
177
+ verse_list_separator = options.fetch(:verse_list_separator, ", ")
178
+ chapter_list_separator = options.fetch(:chapter_list_separator, "; ")
179
+ always_print_verse_range = options.fetch(:always_print_verse_range, false)
180
+ always_print_verse_range = true unless book_has_chapters?
181
+
182
+ recent_chapter = nil # e.g. in 12:1-8, remember that 12 is the chapter when we parse the 8
183
+ recent_chapter = 1 unless book_has_chapters?
184
+ recent_verse = nil
185
+
186
+ touched_chapters = [ recent_chapter ].compact
187
+
188
+ ranges.each_with_index.each_with_object("") do |(range, i), s|
189
+ if i > 0
190
+ if recent_chapter == range.begin.chapter
191
+ s << verse_list_separator
192
+ else
193
+ s << chapter_list_separator
194
+ end
195
+ end
196
+
197
+ last_verse = Pericope.get_max_verse(book, range.end.chapter)
198
+ if !always_print_verse_range && range.begin.verse == 1 && range.begin.whole? && (range.end.verse > last_verse || range.end.whole? && range.end.verse == last_verse) && (touched_chapters - [range.begin.chapter]).none?
199
+ s << range.begin.chapter.to_s
200
+ s << "#{chapter_range_separator}#{range.end.chapter}" if range.end.chapter > range.begin.chapter
201
+ else
202
+ if range.begin.partial? && range.begin.verse == recent_verse
203
+ s << range.begin.letter
204
+ else
205
+ s << range.begin.to_s(with_chapter: recent_chapter != range.begin.chapter)
206
+ end
207
+
208
+ if range.begin != range.end
209
+ if range.begin.chapter == range.end.chapter
210
+ s << "#{verse_range_separator}#{range.end}"
211
+ else
212
+ s << "#{chapter_range_separator}#{range.end.to_s(with_chapter: true)}"
213
+ end
214
+ end
215
+
216
+ recent_chapter = range.end.chapter
217
+ recent_verse = range.end.verse if range.end.partial?
218
+ touched_chapters |= [range.begin.chapter, range.end.chapter].uniq
219
+ end
220
+ end
221
+ end
222
+
223
+ def group_array_into_ranges(verses)
224
+ return [] if verses.nil? or verses.empty?
225
+
226
+ verses = verses.flatten.compact.sort.map { |verse| Verse.parse(verse) }
227
+
228
+ ranges = []
229
+ range_begin = verses.shift
230
+ range_end = range_begin
231
+ while verse = verses.shift
232
+ if verse > range_end.next
233
+ ranges << Range.new(range_begin, range_end)
234
+ range_begin = range_end = verse
235
+ elsif verse > range_end
236
+ range_end = verse
237
+ end
238
+
239
+ break if range_end.next.nil? # end of book
240
+ end
241
+
242
+ ranges << Range.new(range_begin, range_end)
243
+ end
244
+
245
+ end
246
+
247
+ Pericope.max_letter = "d"
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wcc-pericope
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Watermark Community Church
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: shoulda-context
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-focus
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest-reporters
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-reporters-turn_reporter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: It recognizes common abbreviations and misspellings for names of the
126
+ books of the Bible and a variety of ways of denoting ranges of chapters and verses.
127
+ It can parse pericopes singly or out of a block of text. It's useful for comparing
128
+ two pericopes for intersection and normalizing them into a well-formatted string.
129
+ email:
130
+ - dev@watermark.org
131
+ executables:
132
+ - pericope
133
+ extensions: []
134
+ extra_rdoc_files: []
135
+ files:
136
+ - README.mdown
137
+ - bin/console
138
+ - bin/pericope
139
+ - data/chapter_verse_count.txt
140
+ - lib/cli/base.rb
141
+ - lib/cli/command.rb
142
+ - lib/pericope.rb
143
+ - lib/pericope/cli.rb
144
+ - lib/pericope/data.rb
145
+ - lib/pericope/parsing.rb
146
+ - lib/pericope/range.rb
147
+ - lib/pericope/verse.rb
148
+ - lib/pericope/version.rb
149
+ homepage: https://github.com/watermarkchurch/wcc-pericope
150
+ licenses: []
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 3.4.8
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: Pericope is a gem for parsing Bible references.
171
+ test_files: []