fat_core 7.2.0 → 7.4.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/.gitignore +1 -1
- data/CHANGELOG.md +97 -0
- data/CHANGELOG.org +5 -0
- data/Gemfile.lock +173 -0
- data/README.md +2 -2
- data/README.org +7 -4
- data/fat_core.gemspec +0 -1
- data/lib/fat_core/string.rb +75 -50
- data/lib/fat_core/version.rb +1 -1
- data/spec/lib/string_spec.rb +66 -4
- metadata +5 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab67f4ddf21be6773a44d0361acdc01257e4f390222c0a5da8434dadc80b108c
|
|
4
|
+
data.tar.gz: 33f3e511b3d2e1262d979f9909d8ad5a0a3c8b638756fb1a25dddaf302dcef65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e78ef4bf3001f91e8c43b7dd6216f5edc65aa8945db4dd79e77d75506a8233d30fafdc01e5e27099f53ee01daffe12fdeb06d7da04cbc9f28d5bce65005e502
|
|
7
|
+
data.tar.gz: a6dcf793b1d698e667570d40d89d2cced098b08d81d70d42e7a908eed1f787bfffcbd8cd0e1d6c1c732fe3472f2f84f9cf5d5fc2aa51c9e69b2a2417ce1a6497
|
data/.gitignore
CHANGED
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/Gemfile.lock
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
GIT
|
|
2
|
+
remote: https://github.com/ddoherty03/rubocop-ddoherty.git
|
|
3
|
+
revision: 6b28e9614f18f94b684179c488acca8ac6cabdc6
|
|
4
|
+
branch: master
|
|
5
|
+
specs:
|
|
6
|
+
rubocop-ddoherty (0.1.2)
|
|
7
|
+
rubocop-performance (~> 1.0)
|
|
8
|
+
rubocop-rake (>= 0.7)
|
|
9
|
+
rubocop-rspec (~> 3.0)
|
|
10
|
+
rubocop-shopify (~> 2.0)
|
|
11
|
+
|
|
12
|
+
PATH
|
|
13
|
+
remote: .
|
|
14
|
+
specs:
|
|
15
|
+
fat_core (7.4.0)
|
|
16
|
+
activesupport
|
|
17
|
+
ostruct
|
|
18
|
+
stringio (>= 3.1.2)
|
|
19
|
+
|
|
20
|
+
GEM
|
|
21
|
+
remote: https://rubygems.org/
|
|
22
|
+
specs:
|
|
23
|
+
activesupport (8.1.1)
|
|
24
|
+
base64
|
|
25
|
+
bigdecimal
|
|
26
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
27
|
+
connection_pool (>= 2.2.5)
|
|
28
|
+
drb
|
|
29
|
+
i18n (>= 1.6, < 2)
|
|
30
|
+
json
|
|
31
|
+
logger (>= 1.4.2)
|
|
32
|
+
minitest (>= 5.1)
|
|
33
|
+
securerandom (>= 0.3)
|
|
34
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
35
|
+
uri (>= 0.13.1)
|
|
36
|
+
ast (2.4.3)
|
|
37
|
+
base64 (0.3.0)
|
|
38
|
+
bigdecimal (4.0.1)
|
|
39
|
+
coderay (1.1.3)
|
|
40
|
+
concurrent-ruby (1.3.6)
|
|
41
|
+
connection_pool (3.0.2)
|
|
42
|
+
date (3.5.1)
|
|
43
|
+
debug (1.11.1)
|
|
44
|
+
irb (~> 1.10)
|
|
45
|
+
reline (>= 0.3.8)
|
|
46
|
+
diff-lcs (1.6.2)
|
|
47
|
+
docile (1.4.1)
|
|
48
|
+
drb (2.2.3)
|
|
49
|
+
erb (6.0.1)
|
|
50
|
+
gem_docs (0.2.0)
|
|
51
|
+
rake
|
|
52
|
+
yard
|
|
53
|
+
i18n (1.14.8)
|
|
54
|
+
concurrent-ruby (~> 1.0)
|
|
55
|
+
io-console (0.8.2)
|
|
56
|
+
irb (1.16.0)
|
|
57
|
+
pp (>= 0.6.0)
|
|
58
|
+
rdoc (>= 4.0.0)
|
|
59
|
+
reline (>= 0.4.2)
|
|
60
|
+
json (2.18.0)
|
|
61
|
+
language_server-protocol (3.17.0.5)
|
|
62
|
+
lint_roller (1.1.0)
|
|
63
|
+
logger (1.7.0)
|
|
64
|
+
method_source (1.1.0)
|
|
65
|
+
minitest (6.0.1)
|
|
66
|
+
prism (~> 1.5)
|
|
67
|
+
ostruct (0.6.3)
|
|
68
|
+
parallel (1.27.0)
|
|
69
|
+
parser (3.3.10.0)
|
|
70
|
+
ast (~> 2.4.1)
|
|
71
|
+
racc
|
|
72
|
+
pp (0.6.3)
|
|
73
|
+
prettyprint
|
|
74
|
+
prettyprint (0.2.0)
|
|
75
|
+
prism (1.7.0)
|
|
76
|
+
pry (0.16.0)
|
|
77
|
+
coderay (~> 1.1)
|
|
78
|
+
method_source (~> 1.0)
|
|
79
|
+
reline (>= 0.6.0)
|
|
80
|
+
pry-doc (1.6.0)
|
|
81
|
+
pry (~> 0.11)
|
|
82
|
+
yard (~> 0.9.11)
|
|
83
|
+
psych (5.3.1)
|
|
84
|
+
date
|
|
85
|
+
stringio
|
|
86
|
+
racc (1.8.1)
|
|
87
|
+
rainbow (3.1.1)
|
|
88
|
+
rake (13.3.1)
|
|
89
|
+
rdoc (7.0.3)
|
|
90
|
+
erb
|
|
91
|
+
psych (>= 4.0.0)
|
|
92
|
+
tsort
|
|
93
|
+
redcarpet (3.6.1)
|
|
94
|
+
regexp_parser (2.11.3)
|
|
95
|
+
reline (0.6.3)
|
|
96
|
+
io-console (~> 0.5)
|
|
97
|
+
rspec (3.13.2)
|
|
98
|
+
rspec-core (~> 3.13.0)
|
|
99
|
+
rspec-expectations (~> 3.13.0)
|
|
100
|
+
rspec-mocks (~> 3.13.0)
|
|
101
|
+
rspec-core (3.13.6)
|
|
102
|
+
rspec-support (~> 3.13.0)
|
|
103
|
+
rspec-expectations (3.13.5)
|
|
104
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
105
|
+
rspec-support (~> 3.13.0)
|
|
106
|
+
rspec-mocks (3.13.7)
|
|
107
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
108
|
+
rspec-support (~> 3.13.0)
|
|
109
|
+
rspec-support (3.13.6)
|
|
110
|
+
rubocop (1.82.1)
|
|
111
|
+
json (~> 2.3)
|
|
112
|
+
language_server-protocol (~> 3.17.0.2)
|
|
113
|
+
lint_roller (~> 1.1.0)
|
|
114
|
+
parallel (~> 1.10)
|
|
115
|
+
parser (>= 3.3.0.2)
|
|
116
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
117
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
118
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
119
|
+
ruby-progressbar (~> 1.7)
|
|
120
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
121
|
+
rubocop-ast (1.48.0)
|
|
122
|
+
parser (>= 3.3.7.2)
|
|
123
|
+
prism (~> 1.4)
|
|
124
|
+
rubocop-performance (1.26.1)
|
|
125
|
+
lint_roller (~> 1.1)
|
|
126
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
127
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
128
|
+
rubocop-rake (0.7.1)
|
|
129
|
+
lint_roller (~> 1.1)
|
|
130
|
+
rubocop (>= 1.72.1)
|
|
131
|
+
rubocop-rspec (3.8.0)
|
|
132
|
+
lint_roller (~> 1.1)
|
|
133
|
+
rubocop (~> 1.81)
|
|
134
|
+
rubocop-shopify (2.18.0)
|
|
135
|
+
rubocop (~> 1.62)
|
|
136
|
+
ruby-progressbar (1.13.0)
|
|
137
|
+
securerandom (0.4.1)
|
|
138
|
+
simplecov (0.22.0)
|
|
139
|
+
docile (~> 1.1)
|
|
140
|
+
simplecov-html (~> 0.11)
|
|
141
|
+
simplecov_json_formatter (~> 0.1)
|
|
142
|
+
simplecov-html (0.13.2)
|
|
143
|
+
simplecov_json_formatter (0.1.4)
|
|
144
|
+
stringio (3.2.0)
|
|
145
|
+
tsort (0.2.0)
|
|
146
|
+
tzinfo (2.0.6)
|
|
147
|
+
concurrent-ruby (~> 1.0)
|
|
148
|
+
unicode-display_width (3.2.0)
|
|
149
|
+
unicode-emoji (~> 4.1)
|
|
150
|
+
unicode-emoji (4.2.0)
|
|
151
|
+
uri (1.1.1)
|
|
152
|
+
yard (0.9.38)
|
|
153
|
+
|
|
154
|
+
PLATFORMS
|
|
155
|
+
ruby
|
|
156
|
+
x86_64-linux
|
|
157
|
+
|
|
158
|
+
DEPENDENCIES
|
|
159
|
+
bundler
|
|
160
|
+
debug (>= 1.0.0)
|
|
161
|
+
fat_core!
|
|
162
|
+
gem_docs (>= 0.2.0)
|
|
163
|
+
pry
|
|
164
|
+
pry-doc
|
|
165
|
+
rake
|
|
166
|
+
redcarpet
|
|
167
|
+
rspec
|
|
168
|
+
rubocop
|
|
169
|
+
rubocop-ddoherty!
|
|
170
|
+
simplecov
|
|
171
|
+
|
|
172
|
+
BUNDLED WITH
|
|
173
|
+
2.6.7
|
data/README.md
CHANGED
|
@@ -768,7 +768,7 @@ result
|
|
|
768
768
|
|
|
769
769
|
## String
|
|
770
770
|
|
|
771
|
-
FatCore::String has methods for performing matching of one string with another (`#matches_with`, `#fuzzy_match`), for converting a string to title-case as might by used in the title of a book (`#entitle`), for converting a String into a useable Symbol (`#as_sym`) and vice-versa (`#as_str` also `Symbol#as_str`), for wrapping with an optional hanging indent (`#wrap`), cleaning up errant spaces (`#clean`), and computing the
|
|
771
|
+
FatCore::String has methods for performing matching of one string with another (`#matches_with`, `#fuzzy_match`), for converting a string to title-case as might by used in the title of a book (`#entitle`), for converting a String into a useable Symbol (`#as_sym`) and vice-versa (`#as_str` also `Symbol#as_str`), for wrapping with an optional hanging indent (`#wrap`), cleaning up errant spaces (`#clean`), and computing the Levenshtein distance between strings (`#distance`). And several others.
|
|
772
772
|
|
|
773
773
|
|
|
774
774
|
<a id="org63111cb"></a>
|
|
@@ -883,7 +883,7 @@ result
|
|
|
883
883
|
|
|
884
884
|
### Method `#distance`
|
|
885
885
|
|
|
886
|
-
`FatCore` provides `distance` as a simple wrapper around the
|
|
886
|
+
`FatCore` provides `distance` as a simple wrapper around the Levenshtein distance function in Ruby's bundled `DidYouMean` library. Insertions, deletions, and substitutions each count as one edit; swapping adjacent letters counts as two. Distances above 10 return 11, preserving the existing ceiling. No native extension is needed.
|
|
887
887
|
|
|
888
888
|
```ruby
|
|
889
889
|
result = []
|
data/README.org
CHANGED
|
@@ -175,7 +175,8 @@ Array are included in the result /unless/ they also appear in any of the
|
|
|
175
175
|
| hammers | glue | bolts | hammers |
|
|
176
176
|
#+end_example
|
|
177
177
|
|
|
178
|
-
** BigDecimal
|
|
178
|
+
** BigDecimal
|
|
179
|
+
*** Method ~#inspect~
|
|
179
180
|
~FatCore~ provides nothing but a better ~#inspect~ method for the ~BigDecimal~
|
|
180
181
|
class since the default inspect method is not very readable.
|
|
181
182
|
|
|
@@ -718,7 +719,7 @@ FatCore::String has methods for performing matching of one string with another
|
|
|
718
719
|
might by used in the title of a book (~#entitle~), for converting a String
|
|
719
720
|
into a useable Symbol (~#as_sym~) and vice-versa (~#as_str~ also
|
|
720
721
|
~Symbol#as_str~), for wrapping with an optional hanging indent (~#wrap~),
|
|
721
|
-
cleaning up errant spaces (~#clean~), and computing the
|
|
722
|
+
cleaning up errant spaces (~#clean~), and computing the Levenshtein
|
|
722
723
|
distance between strings (~#distance~). And several others.
|
|
723
724
|
|
|
724
725
|
*** Method ~#fuzzy_match~
|
|
@@ -851,8 +852,10 @@ something like "In the Heat of the Night".
|
|
|
851
852
|
|
|
852
853
|
*** Method ~#distance~
|
|
853
854
|
~FatCore~ provides ~distance~ as a simple wrapper around the
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
Levenshtein distance function in Ruby's bundled ~DidYouMean~ library.
|
|
856
|
+
Insertions, deletions, and substitutions each count as one edit; swapping
|
|
857
|
+
adjacent letters counts as two. Distances above 10 return 11, preserving
|
|
858
|
+
the existing ceiling. No native extension is needed.
|
|
856
859
|
|
|
857
860
|
#+begin_src ruby
|
|
858
861
|
result = []
|
data/fat_core.gemspec
CHANGED
data/lib/fat_core/string.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'bigdecimal'
|
|
4
4
|
require 'fat_core/patches'
|
|
5
|
-
require '
|
|
5
|
+
require 'did_you_mean/levenshtein'
|
|
6
6
|
require 'active_support/core_ext/regexp'
|
|
7
7
|
require_relative 'numeric'
|
|
8
8
|
|
|
@@ -215,43 +215,42 @@ module FatCore
|
|
|
215
215
|
# @group Matching
|
|
216
216
|
# :section: Matching
|
|
217
217
|
|
|
218
|
-
# Return the
|
|
219
|
-
#
|
|
220
|
-
#
|
|
218
|
+
# Return the Levenshtein distance between self and another string.
|
|
219
|
+
# Distances above 10 return 11, preserving the existing ceiling.
|
|
220
|
+
# Swapping adjacent characters counts as two edits.
|
|
221
221
|
#
|
|
222
222
|
# @param other [#to_s] string to compute self's distance from
|
|
223
223
|
# @return [Integer] the distance between self and other
|
|
224
224
|
def distance(other)
|
|
225
|
-
|
|
225
|
+
[DidYouMean::Levenshtein.distance(self, other.to_s), 11].min
|
|
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
|
@@ -246,7 +246,7 @@ the people, for the people, shall not perish from the earth."
|
|
|
246
246
|
end
|
|
247
247
|
|
|
248
248
|
it 'computes its Levenshtein distance from another string' do
|
|
249
|
-
expect('Something'.distance('Smoething')).to eq
|
|
249
|
+
expect('Something'.distance('Smoething')).to eq 2
|
|
250
250
|
expect('Something'.distance('meSothing')).to eq 4
|
|
251
251
|
expect('SomethingElse'.distance('Encyclopedia')).to eq 11
|
|
252
252
|
expect('SomethingElseUnrelated'.distance('EncyclopediaBritanica'))
|
|
@@ -254,6 +254,20 @@ the people, for the people, shall not perish from the earth."
|
|
|
254
254
|
expect('SomethingElseUnrelated'.distance('EncyclopediaBritanica')).to eq 11
|
|
255
255
|
end
|
|
256
256
|
|
|
257
|
+
it 'handles identical, empty, Unicode, and non-string inputs' do
|
|
258
|
+
expect('same'.distance('same')).to eq 0
|
|
259
|
+
expect(''.distance('abc')).to eq 3
|
|
260
|
+
expect('abc'.distance('')).to eq 3
|
|
261
|
+
expect('café'.distance('cafe')).to eq 1
|
|
262
|
+
expect('123'.distance(123)).to eq 0
|
|
263
|
+
expect(''.distance(nil)).to eq 0
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'caps distances above ten at eleven' do
|
|
267
|
+
expect(('a' * 10).distance('')).to eq 10
|
|
268
|
+
expect(('a' * 12).distance('')).to eq 11
|
|
269
|
+
end
|
|
270
|
+
|
|
257
271
|
describe 'Quoting' do
|
|
258
272
|
it 'quotes special TeX characters' do
|
|
259
273
|
expect('$10,000'.tex_quote).to eq('\\$10,000')
|
|
@@ -270,6 +284,49 @@ the people, for the people, shall not perish from the earth."
|
|
|
270
284
|
expect('Hello, world'.fuzzy_match('ox')).to be_falsy
|
|
271
285
|
end
|
|
272
286
|
|
|
287
|
+
it 'matches an exact match' do
|
|
288
|
+
expect('Amazon.com'.fuzzy_match('Amazon.com')).to be_truthy
|
|
289
|
+
expect('Hello:world'.fuzzy_match('Hello:world')).to be_truthy
|
|
290
|
+
expect('2+2=4'.fuzzy_match('2+2=4')).to be_truthy
|
|
291
|
+
expect('Whodunnit?'.fuzzy_match('Whodunnit?')).to be_truthy
|
|
292
|
+
expect('(I like (lisp))'.fuzzy_match('(I like (lisp))')).to be_truthy
|
|
293
|
+
# expect('Price is $12.50'.fuzzy_match('Price is $12.50')).to be_truthy
|
|
294
|
+
expect('foo[bar]'.fuzzy_match('foo[bar]')).to be_truthy
|
|
295
|
+
expect('a^b'.fuzzy_match('a^b')).to be_truthy
|
|
296
|
+
expect('foo|bar'.fuzzy_match('foo|bar')).to be_truthy
|
|
297
|
+
expect('path\\name'.fuzzy_match('path\\name')).to be_truthy
|
|
298
|
+
expect('Acme Co., Inc.'.fuzzy_match('Acme Co., Inc.')).to be_truthy
|
|
299
|
+
expect("St. Luke's Hospital".fuzzy_match("St. Luke's Hospital")).to be_truthy
|
|
300
|
+
expect('E*TRADE'.fuzzy_match('E*TRADE')).to be_truthy
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it 'fuzzy matches ordinary strings against themselves' do
|
|
304
|
+
[
|
|
305
|
+
'Amazon.com',
|
|
306
|
+
'Amazon, Inc.',
|
|
307
|
+
'St. Luke\'s Hospital',
|
|
308
|
+
'E*TRADE',
|
|
309
|
+
'WWW.WOLFRAM.COM',
|
|
310
|
+
'Smith, Jones & Brown',
|
|
311
|
+
'Acme Co., Inc.',
|
|
312
|
+
'Hello, world.',
|
|
313
|
+
].each do |string|
|
|
314
|
+
expect(string.fuzzy_match(string)).to be_truthy
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'escapes regexp special characters in ordinary matchers' do
|
|
319
|
+
expect('Price is $12.50'.fuzzy_match('Price is $12.50')).to be_truthy
|
|
320
|
+
expect('Use a+b'.fuzzy_match('Use a+b')).to be_truthy
|
|
321
|
+
expect('Maybe yes?'.fuzzy_match('Maybe yes?')).to be_truthy
|
|
322
|
+
expect('Pick (one)'.fuzzy_match('Pick (one)')).to be_truthy
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
it 'honors regexp special characters in slash-delimited matchers' do
|
|
326
|
+
expect('Hello world'.fuzzy_match('/^hello.*world$/')).to be_truthy
|
|
327
|
+
expect('Hello world'.fuzzy_match('/^world/')).to be_falsy
|
|
328
|
+
end
|
|
329
|
+
|
|
273
330
|
it 'replaces periods and commas with a space so they are still word separators' do
|
|
274
331
|
expect('WWW.WOLFRAM.COM'.fuzzy_match('wolf')).to be_truthy
|
|
275
332
|
expect('AMAZON,INC'.fuzzy_match('amazon inc')).to be_truthy
|
|
@@ -290,10 +347,14 @@ the people, for the people, shall not perish from the earth."
|
|
|
290
347
|
expect('Hello, what is with the world?'.fuzzy_match('at wi or')).to be_truthy
|
|
291
348
|
end
|
|
292
349
|
|
|
350
|
+
it 'does not normalize the subject for regexp matchers' do
|
|
351
|
+
expect('Price is $12.50'.fuzzy_match('/\\$12\\.50/')).to eq('$12.50')
|
|
352
|
+
expect('Price is $12.50'.fuzzy_match('/\\$12 50/')).to be_nil
|
|
353
|
+
end
|
|
354
|
+
|
|
293
355
|
it 'fuzzy matches colon-separated parts' do
|
|
294
356
|
expect('Hello:world'.fuzzy_match('hel:wor')).to be_truthy
|
|
295
357
|
expect('Hello:world'.fuzzy_match('hel :wor')).to be_truthy
|
|
296
|
-
expect('Hello:world'.fuzzy_match('hel: wor')).to be_falsy
|
|
297
358
|
expect('Hello:world'.fuzzy_match('hel:orld')).to be_falsy
|
|
298
359
|
expect("Hello, 'world'".fuzzy_match('hel:wor')).to be_truthy
|
|
299
360
|
expect('Hello "world"'.fuzzy_match('hel:world')).to be_truthy
|
|
@@ -310,6 +371,8 @@ the people, for the people, shall not perish from the earth."
|
|
|
310
371
|
expect('Hello, what is with the world?'.fuzzy_match('llo: th: :wor')).to be_truthy
|
|
311
372
|
expect('Hello:world'.fuzzy_match('llox: ')).to be_falsy
|
|
312
373
|
expect('Hello, what=+&is (with) the world?'.fuzzy_match('at: ith: the')).to be_truthy
|
|
374
|
+
# A colon at the end of a word in the matcher should require a word-boundary there.
|
|
375
|
+
expect('Hello:world'.fuzzy_match('hel: wor')).to be_falsy
|
|
313
376
|
end
|
|
314
377
|
|
|
315
378
|
it 'requires end-anchor for ending colon' do
|
|
@@ -342,9 +405,8 @@ the people, for the people, shall not perish from the earth."
|
|
|
342
405
|
expect('St Lukes, Inc.'.fuzzy_match('st luke inc')).to eq('St Lukes Inc')
|
|
343
406
|
expect('St Lukes, Inc.'.fuzzy_match('st lukes, inc')).to eq('St Lukes Inc')
|
|
344
407
|
expect('E*TRADE'.fuzzy_match('etrade')).to eq('ETRADE')
|
|
345
|
-
# Does not recognize non-alphanumerics as start of string.
|
|
346
408
|
expect('The 1 Dollar Store'.fuzzy_match('1 stor')).to be_truthy
|
|
347
|
-
expect('The $1 Dollar Store'.fuzzy_match('$1 stor')).to
|
|
409
|
+
expect('The $1 Dollar Store'.fuzzy_match('$1 stor')).to be_truthy
|
|
348
410
|
end
|
|
349
411
|
|
|
350
412
|
it 'performs examples in documentation' do
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fat_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel E. Doherty
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -24,20 +23,6 @@ dependencies:
|
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: damerau-levenshtein
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
26
|
- !ruby/object:Gem::Dependency
|
|
42
27
|
name: ostruct
|
|
43
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,8 +71,10 @@ files:
|
|
|
86
71
|
- ".simplecov"
|
|
87
72
|
- ".travis.yml"
|
|
88
73
|
- ".yardopts"
|
|
74
|
+
- CHANGELOG.md
|
|
89
75
|
- CHANGELOG.org
|
|
90
76
|
- Gemfile
|
|
77
|
+
- Gemfile.lock
|
|
91
78
|
- LICENSE.txt
|
|
92
79
|
- README.md
|
|
93
80
|
- README.org
|
|
@@ -124,7 +111,6 @@ licenses:
|
|
|
124
111
|
- MIT
|
|
125
112
|
metadata:
|
|
126
113
|
yard.run: yri
|
|
127
|
-
post_install_message:
|
|
128
114
|
rdoc_options: []
|
|
129
115
|
require_paths:
|
|
130
116
|
- lib
|
|
@@ -139,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
125
|
- !ruby/object:Gem::Version
|
|
140
126
|
version: '0'
|
|
141
127
|
requirements: []
|
|
142
|
-
rubygems_version: 3.
|
|
143
|
-
signing_key:
|
|
128
|
+
rubygems_version: 3.6.2
|
|
144
129
|
specification_version: 4
|
|
145
130
|
summary: some useful core extensions
|
|
146
131
|
test_files: []
|