fzy_score 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6f6146dd91063312cd225fe2d7c73f76fa5a3ddb4859138a5a37740320ce1d6
4
+ data.tar.gz: 3ab14c673a173547b8e8e41a1f6678468394da630a38cb84572121aa5ad809d1
5
+ SHA512:
6
+ metadata.gz: 46e4486951489bc057893837456250c2f3e17e4c2f4b48ad3768bf2e04cee2419b7140f298120ba772ae6363ff60e3eb013a171bae7a052655c3112994d50712
7
+ data.tar.gz: bc864f1b8639a15d3ee41820e16dfd93c9bff85d47d127fe237adc4918949466f55c0a405d31330595b158e095e56ff3f01046a516c49235a743b9100cf6cd38
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-05-28
11
+
12
+ ### Added
13
+ - Initial release.
14
+ - `FzyScore.score(needle, haystack)` — relevance score (higher is better).
15
+ - `FzyScore.match(needle, haystack, positions:)` — score plus matched positions
16
+ for highlighting, returned as a `FzyScore::Match`.
17
+ - `FzyScore.match?(needle, haystack)` — O(n) boolean pre-filter.
18
+ - `FzyScore.filter(needle, candidates, positions:, key:)` — rank a list,
19
+ best-first, with stable tie-breaking and an optional key extractor.
20
+ - Faithful port of fzy's scoring constants from `config.def.h`.
21
+
22
+ [Unreleased]: https://github.com/tachyurgy/fzy_score/compare/v0.1.0...HEAD
23
+ [0.1.0]: https://github.com/tachyurgy/fzy_score/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,40 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Levelbrook Consulting
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The scoring algorithm and constants in this gem are ported from fzy
26
+ (https://github.com/jhawthorn/fzy), which is also MIT licensed:
27
+
28
+ Copyright (c) 2014 John Hawthorn
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining a copy
31
+ of this software and associated documentation files (the "Software"), to deal
32
+ in the Software without restriction, including without limitation the rights
33
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
+ copies of the Software, and to permit persons to whom the Software is
35
+ furnished to do so, subject to the following conditions:
36
+
37
+ The above copyright notice and this permission notice shall be included in all
38
+ copies or substantial portions of the Software.
39
+
40
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # fzy_score
2
+
3
+ A tiny, dependency-free Ruby port of the [fzy](https://github.com/jhawthorn/fzy)
4
+ fuzzy-matching **scoring** algorithm — the same family of algorithm used by
5
+ [fzf](https://github.com/junegunn/fzf) and [fzf-for-js](https://github.com/ajitid/fzf-for-js).
6
+
7
+ Unlike Ruby's existing fuzzy gems (which solve *record linkage* with
8
+ Levenshtein/Dice/Jaro, or only answer the boolean "does it match?"),
9
+ `fzy_score` returns **both a relevance score and the matched character
10
+ positions** — exactly what you need to build:
11
+
12
+ - a command palette / quick-open
13
+ - an autocomplete dropdown
14
+ - a CLI picker with highlighted matches
15
+
16
+ It's pure Ruby, has zero runtime dependencies, and ports fzy's published
17
+ scoring constants verbatim so ranking matches the reference tool.
18
+
19
+ ## Installation
20
+
21
+ ```ruby
22
+ # Gemfile
23
+ gem "fzy_score"
24
+ ```
25
+
26
+ ```sh
27
+ gem install fzy_score
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Score a single candidate
33
+
34
+ ```ruby
35
+ require "fzy_score"
36
+
37
+ FzyScore.score("amf", "app/models/foo.rb") # => 3.58 (higher is better)
38
+ FzyScore.score("zzz", "app/models/foo.rb") # => -Infinity (no match)
39
+ FzyScore.score("abc", "abc") # => Infinity (exact match)
40
+ ```
41
+
42
+ ### Get matched positions for highlighting
43
+
44
+ ```ruby
45
+ m = FzyScore.match("amu", "app/models/user.rb")
46
+ m.score # => Float
47
+ m.positions # => [0, 4, 11] indices into the haystack to highlight
48
+ m.matched? # => true
49
+
50
+ # Highlight in a terminal:
51
+ def highlight(haystack, positions)
52
+ set = positions.to_set
53
+ haystack.each_char.with_index.map { |c, i| set.include?(i) ? "\e[33m#{c}\e[0m" : c }.join
54
+ end
55
+ puts highlight("app/models/user.rb", m.positions)
56
+ ```
57
+
58
+ ### Filter and rank a list (best first)
59
+
60
+ ```ruby
61
+ files = ["spec/match_spec.rb", "src/match.rb", "README.md"]
62
+
63
+ FzyScore.filter("srcmatch", files)
64
+ # => [["src/match.rb", 6.97, nil]] (non-matches dropped, sorted best-first)
65
+
66
+ # Want positions too?
67
+ FzyScore.filter("srcmatch", files, positions: true)
68
+ # => [["src/match.rb", 6.97, [0,1,2,4,5,6,7,8]]]
69
+
70
+ # Filtering objects? Pass a key extractor — the original object comes back.
71
+ people = [{ name: "Alice" }, { name: "Bob" }, { name: "Albert" }]
72
+ FzyScore.filter("al", people, key: ->(p) { p[:name] })
73
+ # => [[{name: "Albert"}, ...], [{name: "Alice"}, ...]]
74
+ ```
75
+
76
+ ### Cheap pre-filter
77
+
78
+ If you only need to know *whether* something matches (no scoring), use the
79
+ O(n) predicate:
80
+
81
+ ```ruby
82
+ FzyScore.match?("amf", "app/models/foo.rb") # => true
83
+ ```
84
+
85
+ `filter` already uses this internally to skip the DP for non-matches.
86
+
87
+ ## How scoring works
88
+
89
+ `fzy_score` implements fzy's modified Smith–Waterman dynamic program. Matches
90
+ earn bonuses for landing at "good" places and pay small penalties for gaps:
91
+
92
+ | Situation | Bonus / penalty |
93
+ |---|---|
94
+ | Consecutive characters | `+1.0` |
95
+ | First char after `/` (path component) | `+0.9` |
96
+ | First char after `-`, `_`, space (word start) | `+0.8` |
97
+ | Uppercase after lowercase (camelCase) | `+0.7` |
98
+ | First char after `.` (file extension) | `+0.6` |
99
+ | Leading / trailing gap | `-0.005` per char |
100
+ | Inner gap | `-0.01` per char |
101
+
102
+ An exact (case-insensitive) match returns `Float::INFINITY`; a non-match
103
+ returns `-Float::INFINITY` so it sorts last. These are the exact constants
104
+ from fzy's `config.def.h`.
105
+
106
+ ## Comparison with other Ruby gems
107
+
108
+ | Gem | What it does | Returns a score? | Returns positions? |
109
+ |---|---|---|---|
110
+ | `fuzzy_match`, `amatch` | record linkage (Levenshtein/Dice/Jaro) | similarity | no |
111
+ | `fuzzyfinder` | boolean filter | no | no |
112
+ | **`fzy_score`** | **fzf/fzy-style ranking** | **yes** | **yes** |
113
+
114
+ ## Development
115
+
116
+ ```sh
117
+ bundle install
118
+ bundle exec rake test
119
+ ```
120
+
121
+ ## License
122
+
123
+ MIT © Levelbrook Consulting. See [LICENSE](LICENSE).
124
+
125
+ The scoring algorithm and constants are ported from
126
+ [jhawthorn/fzy](https://github.com/jhawthorn/fzy) (MIT).
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FzyScore
4
+ # Result of a scored fuzzy match.
5
+ #
6
+ # +score+ Float relevance score. Higher is better. {SCORE_MAX} for an
7
+ # exact (case-insensitive) match, {SCORE_MIN} for no match / empty
8
+ # needle / oversized candidate.
9
+ # +positions+ Array<Integer> of the indices in +haystack+ that the needle
10
+ # matched, suitable for highlighting. +nil+ unless positions were
11
+ # requested.
12
+ Match = Struct.new(:score, :positions) do
13
+ # @return [Boolean] true when the candidate actually matched.
14
+ def matched?
15
+ score > SCORE_MIN
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FzyScore
4
+ VERSION = "0.1.0"
5
+ end
data/lib/fzy_score.rb ADDED
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fzy_score/version"
4
+ require_relative "fzy_score/match"
5
+
6
+ # FzyScore is a faithful, dependency-free Ruby port of the {https://github.com/jhawthorn/fzy
7
+ # fzy} fuzzy-matching scoring algorithm (the same family of algorithm used by
8
+ # {https://github.com/junegunn/fzf fzf} and {https://github.com/ajitid/fzf-for-js fzf-for-js}).
9
+ #
10
+ # Unlike Ruby's existing fuzzy gems (which do Levenshtein/Dice record linkage, or a
11
+ # boolean "does it match" filter), FzyScore returns BOTH a relevance *score* and the
12
+ # matched character *positions* — exactly what you need to build a command palette,
13
+ # quick-open, autocomplete, or CLI picker with highlighting.
14
+ #
15
+ # @example Quick scoring
16
+ # FzyScore.score("amf", "app/models/foo.rb") # => Float
17
+ #
18
+ # @example Ranking candidates
19
+ # FzyScore.filter("srcmtch", ["src/match.rb", "spec/match_spec.rb", "README.md"])
20
+ # # => [["src/match.rb", <score>, [0,1,2,4,5,6,7]], ...] (best first)
21
+ #
22
+ # @example Highlighting
23
+ # m = FzyScore.match("mr", "app/models/user.rb", positions: true)
24
+ # m.positions # => indices to highlight
25
+ module FzyScore
26
+ # Scoring constants, taken verbatim from fzy's config.def.h so that ranking
27
+ # matches the reference implementation.
28
+ SCORE_GAP_LEADING = -0.005
29
+ SCORE_GAP_TRAILING = -0.005
30
+ SCORE_GAP_INNER = -0.01
31
+ SCORE_MATCH_CONSECUTIVE = 1.0
32
+ SCORE_MATCH_SLASH = 0.9
33
+ SCORE_MATCH_WORD = 0.8
34
+ SCORE_MATCH_CAPITAL = 0.7
35
+ SCORE_MATCH_DOT = 0.6
36
+
37
+ SCORE_MAX = Float::INFINITY
38
+ SCORE_MIN = -Float::INFINITY
39
+
40
+ # Candidates longer than this are not scored with the full DP (treated as
41
+ # SCORE_MIN), matching fzy's behaviour of not penalising the rest of the UI
42
+ # for one unreasonably large entry.
43
+ MATCH_MAX_LEN = 1024
44
+
45
+ # Bonus awarded to a character based on the character that precedes it.
46
+ # Mirrors fzy's bonus_states table.
47
+ WORD_BREAK = { "-" => SCORE_MATCH_WORD, "_" => SCORE_MATCH_WORD, " " => SCORE_MATCH_WORD }.freeze
48
+
49
+ module_function
50
+
51
+ # Does +needle+ fuzzily match +haystack+ at all (case-insensitive, in order)?
52
+ #
53
+ # This is the cheap O(n) pre-filter; it does not compute a score.
54
+ #
55
+ # @param needle [String]
56
+ # @param haystack [String]
57
+ # @return [Boolean]
58
+ def match?(needle, haystack)
59
+ n = needle.downcase
60
+ h = haystack.downcase
61
+ j = 0
62
+ n.each_char do |ch|
63
+ j = h.index(ch, j)
64
+ return false if j.nil?
65
+
66
+ j += 1
67
+ end
68
+ true
69
+ end
70
+
71
+ # Score how well +needle+ matches +haystack+. Returns {SCORE_MIN} when there
72
+ # is no match (so it sorts last). Higher is better.
73
+ #
74
+ # @param needle [String]
75
+ # @param haystack [String]
76
+ # @return [Float]
77
+ def score(needle, haystack)
78
+ do_match(needle, haystack, positions: false).score
79
+ end
80
+
81
+ # Score +needle+ against +haystack+ and (optionally) return the matched
82
+ # positions for highlighting.
83
+ #
84
+ # @param needle [String]
85
+ # @param haystack [String]
86
+ # @param positions [Boolean] also compute the matched indices (slightly more work)
87
+ # @return [FzyScore::Match]
88
+ def match(needle, haystack, positions: true)
89
+ do_match(needle, haystack, positions: positions)
90
+ end
91
+
92
+ # Filter and rank a list of candidates against +needle+, best first.
93
+ #
94
+ # Each returned row is +[candidate, score, positions]+. Candidates that do
95
+ # not match are dropped. Sorting is stable on ties (preserves input order),
96
+ # matching the intuition users expect from a picker.
97
+ #
98
+ # @param needle [String]
99
+ # @param candidates [Array<#to_s>]
100
+ # @param positions [Boolean] include matched positions in each row
101
+ # @param key [Proc, nil] extract the string to match from each candidate
102
+ # @return [Array<Array>] rows of [candidate, score, positions_or_nil]
103
+ def filter(needle, candidates, positions: false, key: nil)
104
+ rows = []
105
+ candidates.each_with_index do |candidate, idx|
106
+ str = key ? key.call(candidate) : candidate.to_s
107
+ next unless match?(needle, str)
108
+
109
+ m = do_match(needle, str, positions: positions)
110
+ rows << [candidate, m.score, m.positions, idx]
111
+ end
112
+ # Stable sort: higher score first, original index breaks ties.
113
+ rows.sort_by! { |row| [-row[1], row[3]] }
114
+ rows.map { |candidate, sc, pos, _| [candidate, sc, pos] }
115
+ end
116
+
117
+ # --- internal -------------------------------------------------------------
118
+
119
+ # @api private
120
+ def do_match(needle, haystack, positions:)
121
+ return Match.new(SCORE_MIN, nil) if needle.nil? || needle.empty?
122
+ return Match.new(SCORE_MIN, nil) unless match?(needle, haystack)
123
+
124
+ n = needle.length
125
+ m = haystack.length
126
+
127
+ if m > MATCH_MAX_LEN || n > m
128
+ return Match.new(SCORE_MIN, nil)
129
+ elsif n == m
130
+ # Same length AND it matched => identical (case-insensitive).
131
+ return Match.new(SCORE_MAX, positions ? (0...n).to_a : nil)
132
+ end
133
+
134
+ lower_needle = needle.downcase
135
+ lower_haystack = haystack.downcase
136
+ match_bonus = precompute_bonus(haystack)
137
+
138
+ if positions
139
+ compute_with_positions(lower_needle, lower_haystack, match_bonus, n, m)
140
+ else
141
+ Match.new(compute_score(lower_needle, lower_haystack, match_bonus, n, m), nil)
142
+ end
143
+ end
144
+
145
+ # Per-position bonus based on the *preceding* character (word starts, slashes,
146
+ # dots, camelCase transitions). The first character is treated as if preceded
147
+ # by a slash, matching fzy.
148
+ # @api private
149
+ def precompute_bonus(haystack)
150
+ bonus = Array.new(haystack.length, 0.0)
151
+ last_ch = "/"
152
+ haystack.each_char.with_index do |ch, i|
153
+ bonus[i] = compute_bonus(last_ch, ch)
154
+ last_ch = ch
155
+ end
156
+ bonus
157
+ end
158
+
159
+ # @api private
160
+ def compute_bonus(last_ch, ch)
161
+ # Bonus only applies to alphanumeric current characters.
162
+ return 0.0 unless ch.match?(/[a-z0-9]/i)
163
+
164
+ case last_ch
165
+ when "/"
166
+ SCORE_MATCH_SLASH
167
+ when "-", "_", " "
168
+ SCORE_MATCH_WORD
169
+ when "."
170
+ SCORE_MATCH_DOT
171
+ else
172
+ # camelCase: an uppercase current char after a lowercase previous char.
173
+ if ch.match?(/[A-Z]/) && last_ch.match?(/[a-z]/)
174
+ SCORE_MATCH_CAPITAL
175
+ else
176
+ 0.0
177
+ end
178
+ end
179
+ end
180
+
181
+ # Score-only DP. Two rolling rows (D and M), matching fzy's match().
182
+ # @api private
183
+ def compute_score(needle, haystack, match_bonus, n, m)
184
+ d_last = Array.new(m, SCORE_MIN)
185
+ m_last = Array.new(m, SCORE_MIN)
186
+ d_curr = Array.new(m, SCORE_MIN)
187
+ m_curr = Array.new(m, SCORE_MIN)
188
+
189
+ n.times do |i|
190
+ match_row(i, n, needle, haystack, match_bonus, m, d_curr, m_curr, d_last, m_last)
191
+ d_curr, d_last = d_last, d_curr
192
+ m_curr, m_last = m_last, m_curr
193
+ end
194
+
195
+ # After the swap, the last computed row is in *_last.
196
+ m_last[m - 1]
197
+ end
198
+
199
+ # Full DP keeping every row so positions can be backtracked. Mirrors fzy's
200
+ # match_positions().
201
+ # @api private
202
+ def compute_with_positions(needle, haystack, match_bonus, n, m)
203
+ d = Array.new(n) { Array.new(m, SCORE_MIN) }
204
+ mm = Array.new(n) { Array.new(m, SCORE_MIN) }
205
+
206
+ match_row(0, n, needle, haystack, match_bonus, m, d[0], mm[0], d[0], mm[0])
207
+ (1...n).each do |i|
208
+ match_row(i, n, needle, haystack, match_bonus, m, d[i], mm[i], d[i - 1], mm[i - 1])
209
+ end
210
+
211
+ positions = Array.new(n, 0)
212
+ match_required = false
213
+ j = m - 1
214
+ i = n - 1
215
+ while i >= 0
216
+ while j >= 0
217
+ if d[i][j] != SCORE_MIN && (match_required || d[i][j] == mm[i][j])
218
+ match_required =
219
+ i.positive? && j.positive? &&
220
+ mm[i][j] == d[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE
221
+ positions[i] = j
222
+ j -= 1
223
+ break
224
+ end
225
+ j -= 1
226
+ end
227
+ i -= 1
228
+ end
229
+
230
+ Match.new(mm[n - 1][m - 1], positions)
231
+ end
232
+
233
+ # Compute one row of the DP. Faithful translation of fzy's match_row().
234
+ # @api private
235
+ def match_row(i, n, needle, haystack, match_bonus, m, d_curr, m_curr, d_last, m_last)
236
+ prev_score = SCORE_MIN
237
+ gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER
238
+ needle_ch = needle[i]
239
+
240
+ j = 0
241
+ while j < m
242
+ if needle_ch == haystack[j]
243
+ score = SCORE_MIN
244
+ if i.zero?
245
+ score = (j * SCORE_GAP_LEADING) + match_bonus[j]
246
+ elsif j.positive?
247
+ consecutive = d_last[j - 1] + SCORE_MATCH_CONSECUTIVE
248
+ via_bonus = m_last[j - 1] + match_bonus[j]
249
+ score = via_bonus > consecutive ? via_bonus : consecutive
250
+ end
251
+ d_curr[j] = score
252
+ candidate = prev_score + gap_score
253
+ m_curr[j] = prev_score = score > candidate ? score : candidate
254
+ else
255
+ d_curr[j] = SCORE_MIN
256
+ m_curr[j] = prev_score = prev_score + gap_score
257
+ end
258
+ j += 1
259
+ end
260
+ end
261
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fzy_score
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Levelbrook Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.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: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ description: |-
42
+ A tiny, dependency-free fuzzy matcher that returns both a relevance score and the
43
+ matched character positions (for highlighting) — the same algorithm family used by
44
+ fzy, fzf, and fzf-for-js. Unlike Ruby's record-linkage fuzzy gems, fzy_score is built
45
+ for command palettes, quick-open, autocomplete, and CLI pickers.
46
+ email:
47
+ - levelbrookteam@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - CHANGELOG.md
53
+ - LICENSE
54
+ - README.md
55
+ - lib/fzy_score.rb
56
+ - lib/fzy_score/match.rb
57
+ - lib/fzy_score/version.rb
58
+ homepage: https://consulting.levelbrook.com
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://consulting.levelbrook.com
63
+ source_code_uri: https://github.com/tachyurgy/fzy_score
64
+ changelog_uri: https://github.com/tachyurgy/fzy_score/blob/main/CHANGELOG.md
65
+ rubygems_mfa_required: 'true'
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.0.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.22
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Faithful Ruby port of the fzy/fzf fuzzy-matching scoring algorithm.
85
+ test_files: []