minifts 1.0.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 +7 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +302 -0
- data/Rakefile +36 -0
- data/lib/minifts/searchable_map.rb +401 -0
- data/lib/minifts/version.rb +5 -0
- data/lib/minifts.rb +944 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4da0fc15a39db6e325bc33fae8d81c0c03f1d916d5a6b200a309fdccfdb2ddd3
|
|
4
|
+
data.tar.gz: ec3f3dbce15dd997865a0c520dd13d5df80ec5e71792ad93cff1d32f5ebf3cdd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 658d33e390731ffa0ad1783d7f86006155b20d22df882ca3d069808ff33b9aaa6e794f909158baf4a6d20cc311a3d88b1810ba4b65067838e862993e0cab34a1
|
|
7
|
+
data.tar.gz: f980ef1aa9fac904faa7a5bff362bb65edb24ee4ff9e9c7877e8352ad46b789c4c9d76bef8c4f75585c20f8120ae1fe939e5ea9d044d14427415d8b7cc55ffec
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## [1.0.0] - 2026-07-18
|
|
2
|
+
|
|
3
|
+
- Initial release: a pure-Ruby port of the JavaScript
|
|
4
|
+
[MiniSearch](https://github.com/lucaong/minisearch) full-text search engine.
|
|
5
|
+
- In-memory inverted index backed by a radix tree (`MiniFTS::SearchableMap`)
|
|
6
|
+
with exact, prefix, and fuzzy (Levenshtein) lookup.
|
|
7
|
+
- BM25+ relevance scoring with field boosting, term boosting, document boosting,
|
|
8
|
+
and per-field weights.
|
|
9
|
+
- Query combinators (`OR`, `AND`, `AND_NOT`), combination-tree queries, result
|
|
10
|
+
filtering, and wildcard queries.
|
|
11
|
+
- Auto-suggestions (`auto_suggest`).
|
|
12
|
+
- Incremental `add` / `remove` / `discard` / `replace`, with lazy index cleanup
|
|
13
|
+
and synchronous (auto-)vacuuming.
|
|
14
|
+
- Byte-identical JSON serialization with the JavaScript library (`to_json` /
|
|
15
|
+
`load_json`): a Ruby-serialized index loads and searches identically in
|
|
16
|
+
JavaScript, and vice versa, for any corpus without astral-plane (above
|
|
17
|
+
U+FFFF) characters. Verified bidirectionally against the real
|
|
18
|
+
`minisearch@7.2.0` across 32 scenarios (`rake compat`).
|
|
19
|
+
- Runs on every Ruby since 2.4; no runtime dependencies.
|
|
20
|
+
- Configurable `logger` for index-corruption warnings; setting `logger: nil`
|
|
21
|
+
silences them without raising.
|
|
22
|
+
- Verified bit-for-bit against the reference JavaScript implementation via
|
|
23
|
+
golden and randomized differential tests, plus a full parity suite mirroring
|
|
24
|
+
the reference's own (non-async) test cases, including the movie/song ranking
|
|
25
|
+
sets.
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"minifts" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["rodrigo.serradura@gmail.com"](mailto:"rodrigo.serradura@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rodrigo Serradura
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<h1 align="center">MiniFTS</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
A tiny, dependency-free full-text search engine for Ruby, held entirely in memory —<br>
|
|
5
|
+
a faithful, bit-for-bit port of the JavaScript <a href="https://github.com/lucaong/minisearch">MiniSearch</a> library.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://rubygems.org/gems/minifts"><img src="https://img.shields.io/gem/v/minifts" alt="Gem version"></a>
|
|
10
|
+
<a href="https://rubygems.org/gems/minifts"><img src="https://img.shields.io/gem/dt/minifts" alt="Downloads"></a>
|
|
11
|
+
<a href="https://github.com/serradura/minifts/actions/workflows/main.yml"><img src="https://github.com/serradura/minifts/actions/workflows/main.yml/badge.svg" alt="CI"></a>
|
|
12
|
+
<a href="https://github.com/serradura/minifts"><img src="https://img.shields.io/badge/ruby-%3E%3D%202.4-black" alt="Ruby >= 2.4"></a>
|
|
13
|
+
<a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License: MIT"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
It has the same BM25+ scoring, prefix and fuzzy matching, boosting, query
|
|
17
|
+
combinators, auto-suggestions, and JSON index format as the original — the
|
|
18
|
+
public API mirrors it, translated to idiomatic Ruby.
|
|
19
|
+
|
|
20
|
+
- **Pure Ruby, zero runtime dependencies.** No native extensions, no database,
|
|
21
|
+
no build step. It runs on the Ruby your OS already ships — the floor is
|
|
22
|
+
**Ruby 2.4**.
|
|
23
|
+
- **Bit-for-bit compatible with MiniSearch.** Scores, ranking, and the
|
|
24
|
+
serialized index are identical to the JavaScript library (verified against it
|
|
25
|
+
across thousands of generated cases — see [Fidelity](#fidelity)). A JSON index
|
|
26
|
+
written by one can be loaded by the other.
|
|
27
|
+
- **Good for the "just add search" case.** When you want relevance-ranked search
|
|
28
|
+
over a modest corpus (a few docs to hundreds of thousands) without reaching for
|
|
29
|
+
SQLite FTS5, Elasticsearch, or a service.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gem install minifts
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or in a `Gemfile`:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
gem "minifts"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
require "minifts"
|
|
47
|
+
|
|
48
|
+
documents = [
|
|
49
|
+
{ "id" => 1, "title" => "Moby Dick", "text" => "Call me Ishmael. Some years ago...", "category" => "fiction" },
|
|
50
|
+
{ "id" => 2, "title" => "Zen and the Art of Motorcycle Maintenance", "text" => "I can see by my watch...", "category" => "fiction" },
|
|
51
|
+
{ "id" => 3, "title" => "Neuromancer", "text" => "The sky above the port was...", "category" => "sci-fi" },
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
ms = MiniFTS.new(fields: %w[title text], store_fields: %w[title category])
|
|
55
|
+
ms.add_all(documents)
|
|
56
|
+
|
|
57
|
+
ms.search("zen motorcycle")
|
|
58
|
+
# => [
|
|
59
|
+
# { id: 2, score: 2.77, terms: ["zen", "motorcycle"], query_terms: ["zen", "motorcycle"],
|
|
60
|
+
# match: { "zen" => ["title"], "motorcycle" => ["title"] },
|
|
61
|
+
# "title" => "Zen and the Art of Motorcycle Maintenance", "category" => "fiction" },
|
|
62
|
+
# ]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each result is a Hash: `:id`, `:score`, `:terms` (the matched *document* terms),
|
|
66
|
+
`:query_terms` (the matched query terms), `:match` (term → fields it matched in),
|
|
67
|
+
plus any `store_fields` under their **string** keys.
|
|
68
|
+
|
|
69
|
+
## Search options
|
|
70
|
+
|
|
71
|
+
Pass options as the second argument to `search`:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
ms.search("moto", prefix: true) # prefix search
|
|
75
|
+
ms.search("ishmael", fuzzy: 0.2) # fuzzy (edit distance = 0.2 * term length)
|
|
76
|
+
ms.search("zen art", combine_with: "AND") # require all terms
|
|
77
|
+
ms.search("zen", boost: { "title" => 2 }) # weight matches in :title higher
|
|
78
|
+
ms.search("art", fields: ["title"]) # restrict to certain fields
|
|
79
|
+
ms.search("art", filter: ->(r) { r["category"] == "fiction" })
|
|
80
|
+
ms.search("art", boost_document: ->(id, term, stored) { stored["featured"] ? 2 : 1 })
|
|
81
|
+
ms.search("zen art", boost_term: ->(term, i, terms) { term == "zen" ? 2 : 1 }) # weight some query terms higher
|
|
82
|
+
ms.search("moto", prefix: true, weights: { prefix: 0.1 }) # down-weight non-exact (prefix/fuzzy) matches
|
|
83
|
+
ms.search("zen", bm25: { k: 1.2, b: 0.7, d: 0.5 }) # tune BM25+ scoring (defaults shown)
|
|
84
|
+
ms.search(MiniFTS::WILDCARD) # match every document
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Query strings can also be combination trees:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
ms.search(
|
|
91
|
+
combine_with: "AND",
|
|
92
|
+
queries: ["zen", { combine_with: "OR", queries: %w[motorcycle archery] }]
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Supported search options: `:fields`, `:filter`, `:boost`, `:boost_term`,
|
|
97
|
+
`:weights`, `:boost_document`, `:prefix`, `:fuzzy`, `:max_fuzzy`,
|
|
98
|
+
`:combine_with`, `:tokenize`, `:process_term`, `:bm25`.
|
|
99
|
+
|
|
100
|
+
`:weights` scales non-exact matches — defaults `{ fuzzy: 0.45, prefix: 0.375 }`,
|
|
101
|
+
so a fuzzy or prefix hit counts for less than an exact one. `:boost_term` takes a
|
|
102
|
+
callable returning a per-*query-term* multiplier (default `1`). `:bm25` tunes the
|
|
103
|
+
BM25+ parameters `k`, `b`, and `d` (defaults `{ k: 1.2, b: 0.7, d: 0.5 }`).
|
|
104
|
+
Defaults can be set once via the constructor's `:search_options`.
|
|
105
|
+
|
|
106
|
+
## Auto-suggestions
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
ms.auto_suggest("neuro")
|
|
110
|
+
# => [{ suggestion: "neuromancer", terms: ["neuromancer"], score: 0.46 }]
|
|
111
|
+
|
|
112
|
+
ms.auto_suggest("zen ar")
|
|
113
|
+
# => [{ suggestion: "zen archery art", terms: [...], score: 1.73 },
|
|
114
|
+
# { suggestion: "zen art", terms: [...], score: 1.21 }]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Adding, removing, and updating documents
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
ms.add(document) # add one
|
|
121
|
+
ms.add_all(documents) # add many
|
|
122
|
+
ms.remove(document) # remove (needs the full, unchanged document)
|
|
123
|
+
ms.discard(id) # remove by ID (lazy cleanup; see below)
|
|
124
|
+
ms.discard_all([id, ...])
|
|
125
|
+
ms.replace(updated) # discard + add, same ID
|
|
126
|
+
ms.remove_all # clear everything
|
|
127
|
+
ms.vacuum # reclaim space from discarded documents
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`discard` is the convenient counterpart to `remove`: it only needs the ID and
|
|
131
|
+
takes effect immediately, cleaning up the inverted index lazily (during later
|
|
132
|
+
searches, and via `vacuum`). Auto-vacuuming runs on your behalf once enough
|
|
133
|
+
documents have been discarded.
|
|
134
|
+
|
|
135
|
+
Introspection: `document_count`, `term_count`, `has?(id)`,
|
|
136
|
+
`get_stored_fields(id)`, `dirt_count`, `dirt_factor`.
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
MiniFTS.new(
|
|
142
|
+
fields: %w[title text], # REQUIRED: field names (strings) to index
|
|
143
|
+
id_field: "id", # unique-ID field (default "id")
|
|
144
|
+
store_fields: %w[title], # fields to keep and return in results
|
|
145
|
+
extract_field: ->(doc, field) { doc[field] }, # how to read a field
|
|
146
|
+
stringify_field: ->(value, field) { value.to_s },
|
|
147
|
+
tokenize: ->(text, field = nil) { text.split(/\s+/) },
|
|
148
|
+
process_term: ->(term, field = nil) { term.downcase }, # normalize/stem; return nil to drop
|
|
149
|
+
search_options: { prefix: true }, # default options for every search
|
|
150
|
+
auto_suggest_options: { fuzzy: 0.2 }, # default options for every auto_suggest
|
|
151
|
+
logger: ->(level, msg, code) { warn("#{level}: #{msg}") }, # warning sink; default logs to stderr
|
|
152
|
+
auto_vacuum: true
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Callables are anything responding to `call` (lambdas, procs, method objects).
|
|
157
|
+
`MiniFTS.get_default(:tokenize)` returns the built-in default for any
|
|
158
|
+
constructor option (`:tokenize`, `:process_term`, `:extract_field`, ...), handy
|
|
159
|
+
when you want to wrap the default rather than replace it.
|
|
160
|
+
|
|
161
|
+
### Documents with symbol keys
|
|
162
|
+
|
|
163
|
+
By default documents are Hashes with **string** keys matching the field names.
|
|
164
|
+
For symbol-keyed documents, supply an extractor:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
MiniFTS.new(fields: ["title"], extract_field: ->(doc, field) { doc[field.to_sym] })
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Stemming, stop words, synonyms
|
|
171
|
+
|
|
172
|
+
Do it in `process_term` (return `nil`/`false` to drop a term, or an array to
|
|
173
|
+
expand one):
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
STOP = %w[the a an of and].to_set
|
|
177
|
+
MiniFTS.new(
|
|
178
|
+
fields: ["text"],
|
|
179
|
+
process_term: ->(t, _f = nil) { d = t.downcase; STOP.include?(d) ? nil : d }
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Serialization and JavaScript interop
|
|
184
|
+
|
|
185
|
+
The index serializes to JSON in exactly MiniSearch's format:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
json = ms.to_json
|
|
189
|
+
File.write("index.json", json)
|
|
190
|
+
|
|
191
|
+
# Later — pass the same options used to build it:
|
|
192
|
+
ms = MiniFTS.load_json(File.read("index.json"), fields: %w[title text], store_fields: %w[title])
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
If you already hold the index as a Ruby Hash (string keys) rather than a JSON
|
|
196
|
+
string, use `ms.as_plain_object` to get one and `MiniFTS.load(hash, **opts)`
|
|
197
|
+
to load it back — `to_json`/`load_json` are exactly those two with JSON in
|
|
198
|
+
between.
|
|
199
|
+
|
|
200
|
+
Because the format is identical, an index built in Ruby can be loaded by
|
|
201
|
+
JavaScript MiniSearch in the browser (and vice-versa) — index server-side, search
|
|
202
|
+
client-side.
|
|
203
|
+
|
|
204
|
+
## The radix tree
|
|
205
|
+
|
|
206
|
+
The inverted index is backed by `MiniFTS::SearchableMap`, a radix tree with
|
|
207
|
+
`Map`-like semantics plus `at_prefix` and `fuzzy_get`. It is exported for
|
|
208
|
+
standalone use:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
map = MiniFTS::SearchableMap.new
|
|
212
|
+
map.set("motorcycle", 1).set("motor", 2)
|
|
213
|
+
map.at_prefix("moto").keys # => ["motor", "motorcycle"]
|
|
214
|
+
map.fuzzy_get("moter", 1) # => { "motor" => [2, 1] }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Performance
|
|
218
|
+
|
|
219
|
+
Everything is in-memory and the heavy lifting is a radix-tree lookup plus BM25
|
|
220
|
+
scoring, so it is fast for its class and scales with the number of *matching*
|
|
221
|
+
documents rather than the total. Indicative figures from
|
|
222
|
+
`benchmarks/search_bench.rb` (5,000 synthetic documents, pure Ruby):
|
|
223
|
+
|
|
224
|
+
| operation | throughput |
|
|
225
|
+
| -------------- | ---------------- |
|
|
226
|
+
| index build | ~3,000 docs/sec |
|
|
227
|
+
| exact search | ~400 queries/sec |
|
|
228
|
+
| prefix search | ~330 queries/sec |
|
|
229
|
+
| fuzzy search | ~180 queries/sec |
|
|
230
|
+
|
|
231
|
+
On the same corpus, exact search is ~6× faster than a naive "scan every
|
|
232
|
+
document" approach — a gap that widens as the corpus grows.
|
|
233
|
+
|
|
234
|
+
### Optimization
|
|
235
|
+
|
|
236
|
+
The implementation was profiled and tuned against a real-world corpus. Relative
|
|
237
|
+
to an early baseline, on the same workload with output verified byte-identical
|
|
238
|
+
(2,627 result rows, scores matched to 17 significant figures):
|
|
239
|
+
|
|
240
|
+
| metric | change vs. baseline |
|
|
241
|
+
| --------------- | -------------------------- |
|
|
242
|
+
| indexing memory | **4.3× lighter** (−76.6%) |
|
|
243
|
+
| search memory | **1.8× lighter** (−44.7%) |
|
|
244
|
+
| indexing speed | +37% (1.37×) |
|
|
245
|
+
| search speed | +15% (1.15×) |
|
|
246
|
+
|
|
247
|
+
Memory (allocated bytes) is deterministic; the throughput figures are wall-clock
|
|
248
|
+
medians and directional. The memory wins dominate because the hot path was
|
|
249
|
+
allocation-bound: indexing 500 documents churned ~11.5M objects and now churns
|
|
250
|
+
~2.4M — far lower GC pressure and peak memory, which is what matters most for an
|
|
251
|
+
in-memory index at scale.
|
|
252
|
+
|
|
253
|
+
## Fidelity
|
|
254
|
+
|
|
255
|
+
This port is validated against the original JavaScript MiniSearch, not just by
|
|
256
|
+
hand-written expectations:
|
|
257
|
+
|
|
258
|
+
- **Golden cases** (`test/golden.json`, `test/lifecycle.json`): every documented
|
|
259
|
+
feature and lifecycle operation, with the exact output the JS library produces.
|
|
260
|
+
- **Randomized differential testing** (`test/fuzz.json`): 250 documents and 700
|
|
261
|
+
randomly generated queries with random options, run through the real JS library
|
|
262
|
+
and replayed in Ruby — ~150,000 assertions, scores matched to full double
|
|
263
|
+
precision.
|
|
264
|
+
- **Byte-identical serialization**: a Ruby-serialized index is byte-for-byte
|
|
265
|
+
identical to the JavaScript library's — index in one, search in the other. The
|
|
266
|
+
lone exception is a corpus containing astral-plane characters (above U+FFFF,
|
|
267
|
+
e.g. most emoji), whose radix-tree terms sort by UTF-8 code point here vs
|
|
268
|
+
UTF-16 code unit in JS; such indexes still load and search identically, only
|
|
269
|
+
their serialized byte order differs. Interchange is verified in both directions
|
|
270
|
+
across 32 scenarios by `rake compat` (see `fidelity/`).
|
|
271
|
+
|
|
272
|
+
The whole suite runs on **Ruby 2.4** (verified in a `ruby:2.4` container).
|
|
273
|
+
|
|
274
|
+
### Differences from the JavaScript library
|
|
275
|
+
|
|
276
|
+
- Options and result metadata use **snake_case symbol keys** (`:id_field`,
|
|
277
|
+
`:store_fields`, `:query_terms`, ...). Stored fields keep their string keys.
|
|
278
|
+
- Field names are **strings**; documents are read with `doc[field]` by default.
|
|
279
|
+
- Vacuuming is **synchronous** (Ruby has no main thread to protect), so the
|
|
280
|
+
async variants (`addAllAsync`, `loadJSONAsync`, batched `vacuum`) are omitted;
|
|
281
|
+
`vacuum` cleans up immediately and `is_vacuuming` does not apply.
|
|
282
|
+
- The wildcard is `MiniFTS::WILDCARD` (or `MiniFTS.wildcard`).
|
|
283
|
+
|
|
284
|
+
## Development
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
bin/setup # install dependencies
|
|
288
|
+
rake test # run the test suite
|
|
289
|
+
rake rubocop # lint (Ruby 2.4 target)
|
|
290
|
+
rake # both
|
|
291
|
+
ruby -Ilib benchmarks/search_bench.rb [num_docs] [num_queries]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Credits
|
|
295
|
+
|
|
296
|
+
A Ruby port of [MiniSearch](https://github.com/lucaong/minisearch) by Luca
|
|
297
|
+
Ongaro. All the search design is theirs; this project translates it to idiomatic
|
|
298
|
+
Ruby with no dependencies.
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
Available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
|
|
5
|
+
# rake/testtask (not minitest/test_task) so `rake test` runs on every supported
|
|
6
|
+
# Ruby — minitest's own task class needs minitest 5.16+, which needs Ruby 2.6.
|
|
7
|
+
require "rake/testtask"
|
|
8
|
+
|
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
|
10
|
+
t.libs << "test" << "lib"
|
|
11
|
+
t.test_files = FileList["test/**/test_*.rb"].exclude("test/test_helper.rb")
|
|
12
|
+
t.warning = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Ruby <-> JavaScript index interchange suite. Kept out of `test`/`default`
|
|
16
|
+
# because it needs Node and the real `minisearch` npm package; the pure-Ruby
|
|
17
|
+
# suite (including the Ruby 2.4 floor) must not depend on either.
|
|
18
|
+
desc "Run the Ruby <-> JavaScript index interchange suite (needs Node in fidelity/)"
|
|
19
|
+
task :compat do
|
|
20
|
+
Dir.chdir("fidelity") do
|
|
21
|
+
sh "npm install --silent" unless Dir.exist?("node_modules")
|
|
22
|
+
sh "ruby bin/build_ruby.rb" # stage 1: Ruby produces the index fixtures
|
|
23
|
+
sh "node bin/check_js.mjs" # stage 2: JS loads them + builds native
|
|
24
|
+
sh "ruby -Itest test/test_interchange.rb" # stage 3: Ruby loads the JS indexes
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# RuboCop only installs on newer Rubies (see Gemfile); the default task degrades
|
|
29
|
+
# to test-only where it is absent.
|
|
30
|
+
begin
|
|
31
|
+
require "rubocop/rake_task"
|
|
32
|
+
RuboCop::RakeTask.new
|
|
33
|
+
task default: %i[test rubocop]
|
|
34
|
+
rescue LoadError
|
|
35
|
+
task default: %i[test]
|
|
36
|
+
end
|