fat_core 7.2.0 → 7.3.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 +4 -4
- data/CHANGELOG.md +97 -0
- data/CHANGELOG.org +5 -0
- data/lib/fat_core/string.rb +70 -45
- data/lib/fat_core/version.rb +1 -1
- data/spec/lib/string_spec.rb +51 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3aced327c9c27abf23f852ce1db5e3347cb1b26558d9c4c337937db9773d1dde
|
|
4
|
+
data.tar.gz: 4cb7f01cae67351e9865476ab40f87ddf37375b1dec7a14e725633288c3b78eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d90173d7c849ca52ab67e1ccb14af60e7171c16c39b5b482d3df08d6f080e8b2ebdf1f18b89122c3554186cc35d6ae572b84d06d584f3a9a01f674ef9a2f5b8a
|
|
7
|
+
data.tar.gz: 895eeef8c783f150bb1cb28283c1ff4e4ffdec5ab94cb6889ad1fcbe60e4475ac7326d2c45b3cd5b50b76bb518a52c1866ed70c42c525c1c8d76facab7780e32
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
- [Version 7.3.0](#org5f61e5a)
|
|
2
|
+
- [String](#org633deb6)
|
|
3
|
+
- [Version 7.1.1](#org003674c)
|
|
4
|
+
- [String](#org5df7dd5)
|
|
5
|
+
- [Version 7.0.0 from Version 6.0.0](#org3aecf27)
|
|
6
|
+
- [Enumerable](#orgff23338)
|
|
7
|
+
- [Hash](#org408609b)
|
|
8
|
+
- [Numeric](#org138cd73)
|
|
9
|
+
- [Range](#orgdebd7ac)
|
|
10
|
+
- [String](#org075bb7e)
|
|
11
|
+
- [Kernel](#org545d137)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
<a id="org5f61e5a"></a>
|
|
15
|
+
|
|
16
|
+
# Version 7.3.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<a id="org633deb6"></a>
|
|
20
|
+
|
|
21
|
+
## String
|
|
22
|
+
|
|
23
|
+
Another visit to String#fuzzy\_match to ensure that a subject string is matched by its own matcher. This bug got tickled when 'Amazon.com' failed to match 'Amazon.com', which just isn't right.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<a id="org003674c"></a>
|
|
27
|
+
|
|
28
|
+
# Version 7.1.1
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
<a id="org5df7dd5"></a>
|
|
32
|
+
|
|
33
|
+
## String
|
|
34
|
+
|
|
35
|
+
- `String#fuzzy_match` make period, comma, asterisk, and apostrophes optional in matcher instead of just deleting them and in the subject string replace periods and commas with spaces so they still act as word separators.
|
|
36
|
+
|
|
37
|
+
Before `'WWW.WOLFRAM.COM'.fuzzy_match('www:wolfram')` would not match because the periods were just eliminated so the matcher was looking for a word starting with 'wolfram' but the subject string was converted to 'WWWWOLFRAMCOM' so no match. Now it is converted to 'WWW WOLFRAM COM' so the match succeeds.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
<a id="org3aecf27"></a>
|
|
41
|
+
|
|
42
|
+
# Version 7.0.0 from Version 6.0.0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
<a id="orgff23338"></a>
|
|
46
|
+
|
|
47
|
+
## Enumerable
|
|
48
|
+
|
|
49
|
+
- Remove `#groups_of` as duplicative of `each_slice`
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
<a id="org408609b"></a>
|
|
53
|
+
|
|
54
|
+
## Hash
|
|
55
|
+
|
|
56
|
+
- Rename `delete_with_value` to `delete_with_values!` to make it clear that the Hash is modified in place,
|
|
57
|
+
- Add `delete_with_value` as a non-mutating variant
|
|
58
|
+
- Change arg to `keys_with_value(vals)` to allow variable arguments for multiple values instead of requiring an Array,
|
|
59
|
+
- Allow `<<` operator to merge any `Enumerable` as well as a `Hash`
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
<a id="org138cd73"></a>
|
|
63
|
+
|
|
64
|
+
## Numeric
|
|
65
|
+
|
|
66
|
+
- Enhance `#tex_quote` to put special numbers such as `Math::PI`, `Math::E`, and `Float::INFINITY` in TeX notation, so, e.g., Complex(Math::E, Math:PI).tex\_quote will render to "$\\pi+e i$", Rationals will be rendered as fractions, etc.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
<a id="orgdebd7ac"></a>
|
|
70
|
+
|
|
71
|
+
## Range
|
|
72
|
+
|
|
73
|
+
- `Range#gaps` now better handles Ranges with countable endpoints that need `:succ` and `:pred` methods,
|
|
74
|
+
- `tex_quote` applies `#tex_quote` to endpoints if they respond to a `#tex_quote` method,
|
|
75
|
+
- Check argument Ranges to `#gaps`, `#spanned_by?`, and `#overlaps` for compatibility with the subject Range,
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
<a id="org075bb7e"></a>
|
|
79
|
+
|
|
80
|
+
## String
|
|
81
|
+
|
|
82
|
+
- `String#fuzzy_match` changes the semantics of the string matcher argument:
|
|
83
|
+
1. an internal ':' preceded and followed by non-whitespace now only applies to the word following it to indicate that the word must be a word boundary, so 'hel:wor' matches 'Shell worms' as well as 'Hello, world'.
|
|
84
|
+
2. an internal ':' preceded by whitespace but followed by non-whitespace is also treated as requiring a word boundary for the following word but not the preceding word, as in 1, so 'hel :wor'
|
|
85
|
+
3. but an internal ':' preceded by non-whitespace but followed by whitespace requires a word boundary after the preceding word, so 'hel: wor'
|
|
86
|
+
4. a lone matcher now allows a match anywhere in the subject text regardless of boundaries; the prior version added a word boundary restriction to the bare matcher, so before 'zz' would not match "Pizza" since 'zz' did not occur at the end of word, but now 'zz' will match "Pizza". Rule 3 above allows putting a boundary restriction on the matcher with 'zz: ' as before.
|
|
87
|
+
5. Leading and trailing ':' continue to require the text to occur at the beginning or end, respectively, of the subject string, so ':hel' and 'ld:' match "Hello, world" but ':wor' and 'wor:' do not.
|
|
88
|
+
- `String#number` no longer considers a hex number with a decimal point to be in the form of a number
|
|
89
|
+
- provide a :pred method in a refinement accesses with 'using StringPred
|
|
90
|
+
- `String#entitle` no longer tries to detect general acronyms for upper case handling since it was unreliable. Thus, `"ibm corporation".enetitle` becomers "Ibm Corporation" rather than "IBM corporation."
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
<a id="org545d137"></a>
|
|
94
|
+
|
|
95
|
+
## Kernel
|
|
96
|
+
|
|
97
|
+
- removed the `Kernel.time_it` method since Benchmark already does this better.
|
data/CHANGELOG.org
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
* Version 7.3.0
|
|
2
|
+
** String
|
|
3
|
+
Another visit to String#fuzzy_match to ensure that a subject string is matched
|
|
4
|
+
by its own matcher. This bug got tickled when 'Amazon.com' failed to match
|
|
5
|
+
'Amazon.com', which just isn't right.
|
|
1
6
|
* Version 7.1.1
|
|
2
7
|
** String
|
|
3
8
|
- =String#fuzzy_match= make period, comma, asterisk, and apostrophes optional
|
data/lib/fat_core/string.rb
CHANGED
|
@@ -225,33 +225,32 @@ module FatCore
|
|
|
225
225
|
DamerauLevenshtein.distance(self, other.to_s, 1, 10)
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
-
# Return the matched portion of self,
|
|
229
|
-
#
|
|
228
|
+
# Return the matched portion of self, with fuzzy-match punctuation normalized,
|
|
229
|
+
# if self matches `matcher` using the following rules:
|
|
230
230
|
#
|
|
231
|
-
# 1. Remove leading and trailing whitespace in the subject and
|
|
232
|
-
#
|
|
233
|
-
# 2. In
|
|
234
|
-
#
|
|
235
|
-
# asterisks so the
|
|
236
|
-
#
|
|
237
|
-
# 3.
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
# 5. Treat internal 'stuff: '
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
# 10. Ignore case in the match
|
|
231
|
+
# 1. Remove leading and trailing whitespace in the subject and matcher and
|
|
232
|
+
# collapse internal whitespace to a single space.
|
|
233
|
+
# 2. In ordinary fuzzy matching, treat periods and commas between non-space
|
|
234
|
+
# characters as word separators. Otherwise ignore periods and commas.
|
|
235
|
+
# Ignore apostrophes and asterisks, so the matcher need not reproduce
|
|
236
|
+
# those punctuation characters exactly.
|
|
237
|
+
# 3. Escape regular-expression metacharacters in an ordinary matcher. A
|
|
238
|
+
# matcher enclosed in '/' characters is instead treated as a regular
|
|
239
|
+
# expression and matched against the original, unnormalized subject.
|
|
240
|
+
# 4. Treat internal ':stuff' or ' :stuff' in an ordinary matcher as the
|
|
241
|
+
# equivalent of /\bstuff.*/, that is, match a word beginning with stuff.
|
|
242
|
+
# 5. Treat internal 'stuff: ' as the equivalent of /stuff\b.*/, that is,
|
|
243
|
+
# require stuff to end at a word boundary and then match anything after it.
|
|
244
|
+
# 6. A colon with no spaces around it belongs to the following component, so
|
|
245
|
+
# 'some:stuff' requires 'some' followed somewhere by a word beginning with
|
|
246
|
+
# 'stuff', i.e. /some.*\bstuff/i.
|
|
247
|
+
# 7. Treat a leading ':' as anchoring the match to the beginning of the
|
|
248
|
+
# normalized subject.
|
|
249
|
+
# 8. Treat a trailing ':' as anchoring the match to the end of the normalized
|
|
250
|
+
# subject.
|
|
251
|
+
# 9. Require each component of an ordinary matcher to match some part of the
|
|
252
|
+
# subject, in order.
|
|
253
|
+
# 10. Ignore case in both fuzzy and regular-expression matching.
|
|
255
254
|
#
|
|
256
255
|
# @example
|
|
257
256
|
# "St. Luke's Hospital".fuzzy_match('st lukes') #=> 'St Lukes'
|
|
@@ -261,28 +260,23 @@ module FatCore
|
|
|
261
260
|
# "St. Luke's Hospital".fuzzy_match('st:laks') #=> nil
|
|
262
261
|
# "St. Luke's Hospital".fuzzy_match(':lukes') #=> nil
|
|
263
262
|
# "St. Luke's Hospital".fuzzy_match('lukes:hospital:') #=> 'Lukes Hospital'
|
|
263
|
+
# "Amazon.com".fuzzy_match('Amazon.com') #=> 'Amazon com'
|
|
264
|
+
# "Price is $12.50".fuzzy_match('/\$12\.50/') #=> '$12.50'
|
|
264
265
|
#
|
|
265
|
-
# @param matcher [String]
|
|
266
|
-
# @return [String] the
|
|
267
|
-
# @return [nil] if
|
|
266
|
+
# @param matcher [String] fuzzy matcher, or a regular expression enclosed in /
|
|
267
|
+
# @return [String] the portion of the subject that matched
|
|
268
|
+
# @return [nil] if the subject did not match
|
|
268
269
|
def fuzzy_match(matcher)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
matcher.sub(/\A:/, "\\A").sub(/:\z/, "\\z")
|
|
278
|
-
.gsub(/:\s+/, "\\b.*").gsub(':', ".*\\b")
|
|
279
|
-
.gsub(/\s+/, ".*")
|
|
280
|
-
else
|
|
281
|
-
Regexp.escape(matcher)
|
|
282
|
-
end
|
|
283
|
-
regexp = /#{regexp_string}/i
|
|
270
|
+
if matcher.match?(%r{\A/.*/\z})
|
|
271
|
+
regexp = Regexp.new(matcher[1...-1], Regexp::IGNORECASE)
|
|
272
|
+
subject = self
|
|
273
|
+
else
|
|
274
|
+
subject = fuzzy_match_clean(self)
|
|
275
|
+
matcher = fuzzy_match_clean(matcher)
|
|
276
|
+
regexp = Regexp.new(fuzzy_match_regexp(matcher), Regexp::IGNORECASE)
|
|
277
|
+
end
|
|
284
278
|
matched_text =
|
|
285
|
-
if (match = regexp.match(
|
|
279
|
+
if (match = regexp.match(subject))
|
|
286
280
|
match[0]
|
|
287
281
|
end
|
|
288
282
|
matched_text
|
|
@@ -385,6 +379,37 @@ module FatCore
|
|
|
385
379
|
|
|
386
380
|
private
|
|
387
381
|
|
|
382
|
+
def fuzzy_match_regexp(matcher)
|
|
383
|
+
start_anchor = matcher.start_with?(':')
|
|
384
|
+
end_anchor = matcher.end_with?(':')
|
|
385
|
+
matcher = matcher.delete_prefix(':') if start_anchor
|
|
386
|
+
matcher = matcher.delete_suffix(':') if end_anchor
|
|
387
|
+
parts = matcher.split(/(:\s+|:|\s+)/)
|
|
388
|
+
regexp_string = parts.map { |part|
|
|
389
|
+
case part
|
|
390
|
+
when /\A:\s+\z/
|
|
391
|
+
"\\b.*"
|
|
392
|
+
when ':'
|
|
393
|
+
".*\\b"
|
|
394
|
+
when /\A\s+\z/
|
|
395
|
+
'.*'
|
|
396
|
+
else
|
|
397
|
+
Regexp.escape(part)
|
|
398
|
+
end
|
|
399
|
+
}.join
|
|
400
|
+
regexp_string = "\\A#{regexp_string}" if start_anchor
|
|
401
|
+
regexp_string = "#{regexp_string}\\z" if end_anchor
|
|
402
|
+
regexp_string
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def fuzzy_match_clean(string)
|
|
406
|
+
string
|
|
407
|
+
.clean
|
|
408
|
+
.gsub(/(?<=\S)[.,](?=\S)/, ' ')
|
|
409
|
+
.gsub(/[.,*']/, '')
|
|
410
|
+
.clean
|
|
411
|
+
end
|
|
412
|
+
|
|
388
413
|
def upper?
|
|
389
414
|
UPPERS.include?(self[0])
|
|
390
415
|
end
|
data/lib/fat_core/version.rb
CHANGED
data/spec/lib/string_spec.rb
CHANGED
|
@@ -270,6 +270,49 @@ the people, for the people, shall not perish from the earth."
|
|
|
270
270
|
expect('Hello, world'.fuzzy_match('ox')).to be_falsy
|
|
271
271
|
end
|
|
272
272
|
|
|
273
|
+
it 'matches an exact match' do
|
|
274
|
+
expect('Amazon.com'.fuzzy_match('Amazon.com')).to be_truthy
|
|
275
|
+
expect('Hello:world'.fuzzy_match('Hello:world')).to be_truthy
|
|
276
|
+
expect('2+2=4'.fuzzy_match('2+2=4')).to be_truthy
|
|
277
|
+
expect('Whodunnit?'.fuzzy_match('Whodunnit?')).to be_truthy
|
|
278
|
+
expect('(I like (lisp))'.fuzzy_match('(I like (lisp))')).to be_truthy
|
|
279
|
+
# expect('Price is $12.50'.fuzzy_match('Price is $12.50')).to be_truthy
|
|
280
|
+
expect('foo[bar]'.fuzzy_match('foo[bar]')).to be_truthy
|
|
281
|
+
expect('a^b'.fuzzy_match('a^b')).to be_truthy
|
|
282
|
+
expect('foo|bar'.fuzzy_match('foo|bar')).to be_truthy
|
|
283
|
+
expect('path\\name'.fuzzy_match('path\\name')).to be_truthy
|
|
284
|
+
expect('Acme Co., Inc.'.fuzzy_match('Acme Co., Inc.')).to be_truthy
|
|
285
|
+
expect("St. Luke's Hospital".fuzzy_match("St. Luke's Hospital")).to be_truthy
|
|
286
|
+
expect('E*TRADE'.fuzzy_match('E*TRADE')).to be_truthy
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it 'fuzzy matches ordinary strings against themselves' do
|
|
290
|
+
[
|
|
291
|
+
'Amazon.com',
|
|
292
|
+
'Amazon, Inc.',
|
|
293
|
+
'St. Luke\'s Hospital',
|
|
294
|
+
'E*TRADE',
|
|
295
|
+
'WWW.WOLFRAM.COM',
|
|
296
|
+
'Smith, Jones & Brown',
|
|
297
|
+
'Acme Co., Inc.',
|
|
298
|
+
'Hello, world.',
|
|
299
|
+
].each do |string|
|
|
300
|
+
expect(string.fuzzy_match(string)).to be_truthy
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'escapes regexp special characters in ordinary matchers' do
|
|
305
|
+
expect('Price is $12.50'.fuzzy_match('Price is $12.50')).to be_truthy
|
|
306
|
+
expect('Use a+b'.fuzzy_match('Use a+b')).to be_truthy
|
|
307
|
+
expect('Maybe yes?'.fuzzy_match('Maybe yes?')).to be_truthy
|
|
308
|
+
expect('Pick (one)'.fuzzy_match('Pick (one)')).to be_truthy
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'honors regexp special characters in slash-delimited matchers' do
|
|
312
|
+
expect('Hello world'.fuzzy_match('/^hello.*world$/')).to be_truthy
|
|
313
|
+
expect('Hello world'.fuzzy_match('/^world/')).to be_falsy
|
|
314
|
+
end
|
|
315
|
+
|
|
273
316
|
it 'replaces periods and commas with a space so they are still word separators' do
|
|
274
317
|
expect('WWW.WOLFRAM.COM'.fuzzy_match('wolf')).to be_truthy
|
|
275
318
|
expect('AMAZON,INC'.fuzzy_match('amazon inc')).to be_truthy
|
|
@@ -290,10 +333,14 @@ the people, for the people, shall not perish from the earth."
|
|
|
290
333
|
expect('Hello, what is with the world?'.fuzzy_match('at wi or')).to be_truthy
|
|
291
334
|
end
|
|
292
335
|
|
|
336
|
+
it 'does not normalize the subject for regexp matchers' do
|
|
337
|
+
expect('Price is $12.50'.fuzzy_match('/\\$12\\.50/')).to eq('$12.50')
|
|
338
|
+
expect('Price is $12.50'.fuzzy_match('/\\$12 50/')).to be_nil
|
|
339
|
+
end
|
|
340
|
+
|
|
293
341
|
it 'fuzzy matches colon-separated parts' do
|
|
294
342
|
expect('Hello:world'.fuzzy_match('hel:wor')).to be_truthy
|
|
295
343
|
expect('Hello:world'.fuzzy_match('hel :wor')).to be_truthy
|
|
296
|
-
expect('Hello:world'.fuzzy_match('hel: wor')).to be_falsy
|
|
297
344
|
expect('Hello:world'.fuzzy_match('hel:orld')).to be_falsy
|
|
298
345
|
expect("Hello, 'world'".fuzzy_match('hel:wor')).to be_truthy
|
|
299
346
|
expect('Hello "world"'.fuzzy_match('hel:world')).to be_truthy
|
|
@@ -310,6 +357,8 @@ the people, for the people, shall not perish from the earth."
|
|
|
310
357
|
expect('Hello, what is with the world?'.fuzzy_match('llo: th: :wor')).to be_truthy
|
|
311
358
|
expect('Hello:world'.fuzzy_match('llox: ')).to be_falsy
|
|
312
359
|
expect('Hello, what=+&is (with) the world?'.fuzzy_match('at: ith: the')).to be_truthy
|
|
360
|
+
# A colon at the end of a word in the matcher should require a word-boundary there.
|
|
361
|
+
expect('Hello:world'.fuzzy_match('hel: wor')).to be_falsy
|
|
313
362
|
end
|
|
314
363
|
|
|
315
364
|
it 'requires end-anchor for ending colon' do
|
|
@@ -342,9 +391,8 @@ the people, for the people, shall not perish from the earth."
|
|
|
342
391
|
expect('St Lukes, Inc.'.fuzzy_match('st luke inc')).to eq('St Lukes Inc')
|
|
343
392
|
expect('St Lukes, Inc.'.fuzzy_match('st lukes, inc')).to eq('St Lukes Inc')
|
|
344
393
|
expect('E*TRADE'.fuzzy_match('etrade')).to eq('ETRADE')
|
|
345
|
-
# Does not recognize non-alphanumerics as start of string.
|
|
346
394
|
expect('The 1 Dollar Store'.fuzzy_match('1 stor')).to be_truthy
|
|
347
|
-
expect('The $1 Dollar Store'.fuzzy_match('$1 stor')).to
|
|
395
|
+
expect('The $1 Dollar Store'.fuzzy_match('$1 stor')).to be_truthy
|
|
348
396
|
end
|
|
349
397
|
|
|
350
398
|
it 'performs examples in documentation' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fat_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel E. Doherty
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -86,6 +86,7 @@ files:
|
|
|
86
86
|
- ".simplecov"
|
|
87
87
|
- ".travis.yml"
|
|
88
88
|
- ".yardopts"
|
|
89
|
+
- CHANGELOG.md
|
|
89
90
|
- CHANGELOG.org
|
|
90
91
|
- Gemfile
|
|
91
92
|
- LICENSE.txt
|