wcc-pericope 1.1.0

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