obfs 0.1.2 → 0.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/README.md +602 -0
- data/lib/obfs/levenshtein.rb +41 -0
- data/lib/obfs/store.rb +967 -114
- data/lib/obfs/string_similarity.rb +41 -0
- data/lib/obfs.rb +2 -4
- metadata +9 -7
- data/lib/text/levenshtein.rb +0 -166
- data/lib/text/white_similarity.rb +0 -66
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a4b8b313bcfac3893da631ea24c90b5cf39f7337e4dd804e922edfa893d4d77
|
|
4
|
+
data.tar.gz: b7afaac2c2fa61e82e888223dd6473a14dbde609dea0eb67b06d2069bafb164a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b782fbfd5d5c740c3256a74b0fb66083d102cc71c59439c673c9a86bc516a438aa63f673d7a609d55d3376fb548421d846345142429b211eeb3e3b304b0edbc6
|
|
7
|
+
data.tar.gz: 3fdd856e0c0c4d65b593f47893c3a85d8d2a3514a03f28656eebbece06aa5ff22a746c58f9b022b14d761187b0cd5ef7336113912caf82c06c80de4a4881fdce
|
data/README.md
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
# OBFS
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/obfs)
|
|
4
|
+
|
|
5
|
+
File-based, object-oriented data store for Ruby.
|
|
6
|
+
|
|
7
|
+
OBFS maps recursive Ruby object access directly to the filesystem, allowing data to be addressed using dot notation, bracket notation, or a combination of both.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
datastore.users.cat.preferences.theme = "dark"
|
|
11
|
+
|
|
12
|
+
datastore["users"]["cat"]["preferences"]["theme"]
|
|
13
|
+
# => "dark"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The resulting filesystem structure is:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
users/
|
|
20
|
+
└── cat/
|
|
21
|
+
└── preferences/
|
|
22
|
+
└── theme
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
OBFS also includes filesystem discovery helpers, including direct existence checks, recursive fuzzy directory search, timestamps, and directory indexing. It uses Ruby standard libraries and independent OBFS-owned text algorithms, with no external runtime gem dependencies.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
Add OBFS to your `Gemfile`:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
gem "obfs"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Require it:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "obfs"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Create a store:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
datastore = OBFS::Store.new
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Write data:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
datastore.somefile = "some value"
|
|
52
|
+
|
|
53
|
+
datastore["somefile"] = "some value"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Read data:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
datastore.somefile
|
|
60
|
+
|
|
61
|
+
datastore["somefile"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Delete data:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
datastore.somefile = nil
|
|
68
|
+
|
|
69
|
+
datastore["somefile"] = nil
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Supported values include:
|
|
73
|
+
|
|
74
|
+
- String
|
|
75
|
+
- Array
|
|
76
|
+
- Hash
|
|
77
|
+
- Integer
|
|
78
|
+
- Float
|
|
79
|
+
- Boolean
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
By default, OBFS stores data inside:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
~/.obfs
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A custom path can be specified:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
datastore = OBFS::Store.new({
|
|
94
|
+
path: "/some/other/folder"
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Paths are recursively created as needed.
|
|
99
|
+
|
|
100
|
+
For example:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
datastore.some.long.path.to.create = "some string"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
creates:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
some/
|
|
110
|
+
└── long/
|
|
111
|
+
└── path/
|
|
112
|
+
└── to/
|
|
113
|
+
└── create
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The file `create` contains the JSON-serialized value:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
"some string"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Bracket notation can also be used:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
datastore["some"]["long"]["path"]["to"]["create"] = "some string"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Dot and bracket notation may be mixed:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
datastore.some["long"].path["to"].create = "some string"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Hashes and arrays are serialized as JSON values into leaf files:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
datastore.settings = {
|
|
138
|
+
"theme" => "dark",
|
|
139
|
+
"notifications" => true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
datastore.numbers = [1, 2, 3]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Reading those values returns the corresponding Ruby objects:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
datastore.settings
|
|
149
|
+
# => {"theme"=>"dark", "notifications"=>true}
|
|
150
|
+
|
|
151
|
+
datastore.numbers
|
|
152
|
+
# => [1, 2, 3]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
## Path Safety
|
|
157
|
+
|
|
158
|
+
Each OBFS key represents exactly one filesystem node.
|
|
159
|
+
|
|
160
|
+
Nested paths should therefore be expressed recursively:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
datastore["foo"]["bar"]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
rather than by embedding filesystem separators:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
datastore["foo/bar"]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
OBFS rejects keys containing filesystem path separators, null bytes, `.` or `..`.
|
|
173
|
+
|
|
174
|
+
For example, the following are rejected:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
datastore[".."]
|
|
178
|
+
datastore["../foo"]
|
|
179
|
+
datastore["../../etc/passwd"]
|
|
180
|
+
datastore["foo/bar"]
|
|
181
|
+
datastore["..\\Windows"]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This prevents dynamic OBFS keys from traversing outside the intended datastore hierarchy.
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
## Special Methods
|
|
188
|
+
|
|
189
|
+
### `_path`
|
|
190
|
+
|
|
191
|
+
Returns the current filesystem path.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
datastore.some.data._path
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
/home/user/.obfs/some/data
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
### `_index`
|
|
205
|
+
|
|
206
|
+
Returns the entries in the current directory.
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
datastore.some.data._index
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
[
|
|
216
|
+
"foo",
|
|
217
|
+
"bar",
|
|
218
|
+
"baz"
|
|
219
|
+
]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
### `_keys`
|
|
224
|
+
|
|
225
|
+
Alias for `_index`.
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
datastore.some.data._keys
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### `_find`
|
|
233
|
+
|
|
234
|
+
Performs a fuzzy search against entries in the current directory only.
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
datastore.some.data._find("some term")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Full signature:
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
datastore.some.data._find(term, records, tolerance)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Arguments:
|
|
247
|
+
|
|
248
|
+
- `term` - search term
|
|
249
|
+
- `records` - maximum number of records to return; defaults to `1000`
|
|
250
|
+
- `tolerance` - maximum Levenshtein distance; defaults to `50`
|
|
251
|
+
|
|
252
|
+
Results are returned as an array of entry names sorted by relevance. Entries must be within the Levenshtein tolerance and have a positive normalized bigram/Dice similarity score. The distance check is case-sensitive; similarity normalizes case, path separators, and whitespace.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
datastore.services._find("nginx")
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
might return:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
[
|
|
264
|
+
"nginx",
|
|
265
|
+
"nginx_config",
|
|
266
|
+
"old_nginx"
|
|
267
|
+
]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`_find` searches only the current directory level.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
### `_exist`
|
|
274
|
+
|
|
275
|
+
Checks whether an immediate child with the specified name exists.
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
datastore.some.data._exist("some term")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
true
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
or:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
false
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
This method is retained for compatibility with earlier versions of OBFS.
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
### `_exists`
|
|
297
|
+
|
|
298
|
+
Checks whether the current OBFS node physically exists.
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
datastore.some.data._exists
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A question-mark alias is also available:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
datastore.some.data._exists?
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
datastore.services.nginx._exists?
|
|
314
|
+
# => true
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
### `_has`
|
|
319
|
+
|
|
320
|
+
Checks whether an immediate child exists.
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
datastore.services._has("nginx")
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
A question-mark alias is also available:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
datastore.services._has?("nginx")
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
true
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
or:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
false
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Unlike `_exist`, `_has` performs a direct filesystem existence check rather than enumerating the current directory.
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
### `_timestamp`
|
|
348
|
+
|
|
349
|
+
Returns the modification time of the current filesystem node.
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
datastore.services.nginx._timestamp
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Returns a Ruby `Time` object when the node exists:
|
|
356
|
+
|
|
357
|
+
```ruby
|
|
358
|
+
2026-10-04 14:30:00 -0700
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
or:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
nil
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
if the node does not exist or cannot be read.
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
### `_search`
|
|
371
|
+
|
|
372
|
+
Performs a fuzzy search against directory names and relative directory paths.
|
|
373
|
+
|
|
374
|
+
Unlike `_find`, `_search` can recursively search the entire directory hierarchy beneath the current OBFS node.
|
|
375
|
+
|
|
376
|
+
Basic usage:
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
datastore._search("nginx")
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Example result:
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
[
|
|
386
|
+
{
|
|
387
|
+
path: "systems/services/nginx",
|
|
388
|
+
score: 1.0
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
path: "systems/services/nginx/certificates",
|
|
392
|
+
score: 0.95
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Search may also be scoped to any recursive OBFS node:
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
datastore.systems.services._search("nginx")
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Options can be supplied using a hash:
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
datastore._search(
|
|
407
|
+
"nginx certificate",
|
|
408
|
+
{
|
|
409
|
+
recursive: true,
|
|
410
|
+
min_score: 0.6,
|
|
411
|
+
limit: 10
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Available options:
|
|
417
|
+
|
|
418
|
+
- `recursive` - recursively search child directories; defaults to `true`
|
|
419
|
+
- `min_score` - minimum fuzzy relevance score from `0.0` to `1.0`; defaults to `0.5`
|
|
420
|
+
- `limit` - maximum number of results; unlimited by default
|
|
421
|
+
|
|
422
|
+
`minScore` is also accepted for compatibility with the JavaScript version:
|
|
423
|
+
|
|
424
|
+
```ruby
|
|
425
|
+
datastore._search(
|
|
426
|
+
"nginx",
|
|
427
|
+
{
|
|
428
|
+
minScore: 0.6
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Search results are sorted from highest to lowest relevance.
|
|
434
|
+
|
|
435
|
+
`_search`:
|
|
436
|
+
|
|
437
|
+
- searches directory names
|
|
438
|
+
- searches relative directory paths
|
|
439
|
+
- does not read stored file contents
|
|
440
|
+
- does not follow symbolic links
|
|
441
|
+
- ignores dot-prefixed entries
|
|
442
|
+
- is scoped to the current OBFS node
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
## `_find` vs `_search`
|
|
446
|
+
|
|
447
|
+
`_find` is intended for lightweight one-level discovery:
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
datastore.services._find("nginx")
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
and returns:
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
[
|
|
457
|
+
"nginx",
|
|
458
|
+
"nginx_config"
|
|
459
|
+
]
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
`_search` is intended for deeper discovery:
|
|
463
|
+
|
|
464
|
+
```ruby
|
|
465
|
+
datastore._search("nginx certificate")
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
and returns scored relative paths:
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
[
|
|
472
|
+
{
|
|
473
|
+
path: "systems/services/nginx/certificates",
|
|
474
|
+
score: 0.95
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
## Recursive Object Storage
|
|
481
|
+
|
|
482
|
+
One of the primary goals of OBFS is to allow the filesystem to behave like an arbitrarily recursive Ruby object.
|
|
483
|
+
|
|
484
|
+
For example:
|
|
485
|
+
|
|
486
|
+
```ruby
|
|
487
|
+
datastore.projects.example.servers.production.services.nginx.status = "running"
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
can later be accessed using the same structure:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
datastore.projects.example.servers.production.services.nginx.status
|
|
494
|
+
# => "running"
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The hierarchy itself is stored using physical directories and files, making the datastore directly inspectable using normal filesystem tools.
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
## Notes
|
|
501
|
+
|
|
502
|
+
- OBFS uses the filesystem directly as its persistence layer.
|
|
503
|
+
- Intermediate paths are created automatically when writing data.
|
|
504
|
+
- Setting a value to `nil` removes the corresponding file or directory recursively.
|
|
505
|
+
- Existing values are replaced when reassigned.
|
|
506
|
+
- JSON-compatible values are serialized using Ruby's JSON library.
|
|
507
|
+
- Files containing valid JSON are deserialized when read.
|
|
508
|
+
- Non-JSON files are returned as raw text.
|
|
509
|
+
- Fuzzy search uses independent `OBFS::Levenshtein` and `OBFS::StringSimilarity` implementations.
|
|
510
|
+
- No external runtime gem dependencies are required; the Text gem is not needed.
|
|
511
|
+
- No database server is required.
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
## Platform Support
|
|
515
|
+
|
|
516
|
+
OBFS requires Ruby >= 2.0.0. The updated text algorithms use Ruby 2.0-compatible APIs. Syntax and basic behavior were validated on Ruby 4.0.7 on Linux; this update has not been tested on Ruby 2.0 itself.
|
|
517
|
+
|
|
518
|
+
Windows and macOS have not been formally tested.
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
## Links
|
|
522
|
+
|
|
523
|
+
- [RubyGems](https://rubygems.org/gems/obfs)
|
|
524
|
+
- [GitHub](https://github.com/jenselg/obfs-ruby)
|
|
525
|
+
- [JavaScript / Node.js Version](https://github.com/jenselg/obfs)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
## Text Algorithms
|
|
529
|
+
|
|
530
|
+
The algorithms are implemented locally in `lib/obfs/levenshtein.rb` and
|
|
531
|
+
`lib/obfs/string_similarity.rb`. Requiring `obfs` loads both automatically.
|
|
532
|
+
|
|
533
|
+
### `OBFS::Levenshtein.distance`
|
|
534
|
+
|
|
535
|
+
Calculates unit-cost insertion, deletion, and substitution distance over Unicode
|
|
536
|
+
codepoints. Inputs must support String encoding; Unicode normalization is not
|
|
537
|
+
performed.
|
|
538
|
+
|
|
539
|
+
```ruby
|
|
540
|
+
OBFS::Levenshtein.distance("kitten", "sitting")
|
|
541
|
+
# => 3
|
|
542
|
+
|
|
543
|
+
OBFS::Levenshtein.distance("kitten", "sitting", 2)
|
|
544
|
+
# => 2
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The optional `max_distance` must be a nonnegative Integer or `nil`. When supplied,
|
|
548
|
+
the result is capped at that maximum, preserving the existing API contract. A
|
|
549
|
+
result equal to the maximum means the actual distance is at least that value.
|
|
550
|
+
|
|
551
|
+
### `OBFS::StringSimilarity.similarity`
|
|
552
|
+
|
|
553
|
+
Returns a normalized character-bigram Dice score between `0.0` and `1.0`. It
|
|
554
|
+
normalizes case, path separators (`/`, `\`, `:`, `_`, and `-`), and whitespace.
|
|
555
|
+
Bigrams span the normalized string, including spaces, and repeated bigrams count
|
|
556
|
+
separately. Unicode codepoints are used without Unicode normalization.
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
OBFS::StringSimilarity.similarity("France", "French")
|
|
560
|
+
# => 0.4
|
|
561
|
+
|
|
562
|
+
OBFS::StringSimilarity.similarity("nginx_config", "NGINX CONFIG")
|
|
563
|
+
# => 1.0
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Equal nonempty normalized strings score `1.0`, including one-character strings.
|
|
567
|
+
Empty inputs score `0.0`; unequal one-character strings score `0.0`.
|
|
568
|
+
|
|
569
|
+
Store relevance takes the highest score from exact, prefix, substring, token
|
|
570
|
+
coverage, normalized Levenshtein, and Dice matching. `_find` and `_search` retain
|
|
571
|
+
their signatures and result formats, but rankings can change with the new Dice
|
|
572
|
+
scorer.
|
|
573
|
+
|
|
574
|
+
When upgrading, replace `lib/obfs/white_similarity.rb` with
|
|
575
|
+
`lib/obfs/string_similarity.rb` and update any direct `OBFS::WhiteSimilarity`
|
|
576
|
+
references to `OBFS::StringSimilarity`. The copied Text implementations and
|
|
577
|
+
`text/*` requires are no longer used.
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
## License
|
|
581
|
+
|
|
582
|
+
### MIT License
|
|
583
|
+
|
|
584
|
+
Copyright (c) 2021 Jensel Gatchalian <jensel.gatchalian@gmail.com>
|
|
585
|
+
|
|
586
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
587
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
588
|
+
in the Software without restriction, including without limitation the rights
|
|
589
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
590
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
591
|
+
furnished to do so, subject to the following conditions:
|
|
592
|
+
|
|
593
|
+
The above copyright notice and this permission notice shall be included in all
|
|
594
|
+
copies or substantial portions of the Software.
|
|
595
|
+
|
|
596
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
597
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
598
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
599
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
600
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
601
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
602
|
+
SOFTWARE.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module OBFS
|
|
2
|
+
module Levenshtein
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
# Unit-cost edit distance over Unicode codepoints, without normalization.
|
|
6
|
+
# With a maximum, return the exact distance capped at that maximum.
|
|
7
|
+
def distance(str1, str2, max_distance = nil)
|
|
8
|
+
unless max_distance.nil? ||
|
|
9
|
+
(max_distance.is_a?(Integer) && max_distance >= 0)
|
|
10
|
+
raise ArgumentError, 'max_distance must be a nonnegative Integer or nil'
|
|
11
|
+
end
|
|
12
|
+
return 0 if max_distance == 0
|
|
13
|
+
|
|
14
|
+
left = str1.encode('UTF-8').each_codepoint.to_a
|
|
15
|
+
right = str2.encode('UTF-8').each_codepoint.to_a
|
|
16
|
+
return 0 if left == right
|
|
17
|
+
|
|
18
|
+
# The shorter input determines the width of the two working rows.
|
|
19
|
+
left, right = right, left if left.length < right.length
|
|
20
|
+
if max_distance && left.length - right.length >= max_distance
|
|
21
|
+
return max_distance
|
|
22
|
+
end
|
|
23
|
+
return left.length if right.empty?
|
|
24
|
+
|
|
25
|
+
previous = (0..right.length).to_a
|
|
26
|
+
left.each_with_index do |character, row|
|
|
27
|
+
current = [row + 1]
|
|
28
|
+
right.each_with_index do |other, column|
|
|
29
|
+
current << [current[column] + 1,
|
|
30
|
+
previous[column + 1] + 1,
|
|
31
|
+
previous[column] + (character == other ? 0 : 1)].min
|
|
32
|
+
end
|
|
33
|
+
# Every path to the final cell crosses this row; costs cannot decrease.
|
|
34
|
+
return max_distance if max_distance && current.min >= max_distance
|
|
35
|
+
previous = current
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
max_distance ? [previous.last, max_distance].min : previous.last
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|