disarm 0.15.0-x86_64-linux → 0.16.0-x86_64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abc080c897b04a4bc4da77201766d150a429e26c7768ddca4c752b4e5b5c2c0c
4
- data.tar.gz: 4b145b7f0184fbe3617c921f5a3ed222f712ba89a499c8352f8c404ca7279c3b
3
+ metadata.gz: 6721d5de6e559d2ca365c66853ced362a1df2e8b26c46b0e1b2c070441984081
4
+ data.tar.gz: 02e5b3546142c90f96d773ca4e859c06eb7187d562da242e64709d335d0faf1e
5
5
  SHA512:
6
- metadata.gz: c3799a0aad40b3c3d5b26bf0017ea43ea4f05ec88c291d22c84674dfca84ec9941590d27155a0ec19bd9443c57a6c39ce821682cdca5142150b64b3db9e39fd9
7
- data.tar.gz: 1a3f8dd7fc0c8c4b9a41c427ec854d91d46b4733626716d7e5e96e710a6b517202a061acec88c6f452180870445085c5b8be2570ebbf789919589dccf623de0d
6
+ metadata.gz: 4cd955deabb3aa23d54ff62e96e2c0fa7f7dfd480873d3f3de11b6eac5b11da472fd59da6ea1cd7933c27efd6e06a39e7d300eb6eda0ccc1915b87113126b42e
7
+ data.tar.gz: 480e07dbf124ea56c1d37bcdcc227317233544d7809e8b87fd647c05a3ad5cf59e6a0bca84dc4d2796a9d7bdde48b77e586c5199d181c3ce9b07c17bfe165f9a
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Disarm
4
4
  # Kept in lockstep with the Rust crate / Python package version.
5
- VERSION = "0.15.0"
5
+ VERSION = "0.16.0"
6
6
  end
data/lib/disarm.rb CHANGED
@@ -116,11 +116,26 @@ module Disarm
116
116
  translate_errors { _demojize(text, strip_modifiers) }
117
117
  end
118
118
 
119
+ # Replace every emoji with +replacement+, verbatim.
120
+ #
121
+ # The counterpart to +demojize+, and a different question of a different table.
122
+ # +demojize+ asks what CLDR calls a character, so its domain is the name table, which
123
+ # is wider than the emoji: <tt>demojize("x\u2122y")</tt> is "x trade mark y". This
124
+ # asks whether the UCD calls it an emoji — Emoji_Presentation=Yes, an Emoji=Yes base
125
+ # carrying U+FE0F, and the ZWJ, modifier, keycap and flag sequences on those. Nothing
126
+ # else moves.
127
+ #
128
+ # +replacement+ is inserted exactly as given: "" closes an intra-word split and " "
129
+ # keeps two words apart, and no rule serves both.
130
+ def replace_emoji(text, replacement = "")
131
+ translate_errors { _replace_emoji(text, replacement) }
132
+ end
133
+
119
134
  # Canonicalize, but raise rather than silently normalize a structural difference
120
135
  # away — the half of the pair that lets a caller reject input instead of comparing
121
136
  # a value the sender never wrote.
122
- def canonicalize_strict(text)
123
- translate_errors { _canonicalize_strict(text) }
137
+ def canonicalize_strict(text, digit_policy: :numeric)
138
+ translate_errors { _canonicalize_strict(text, digit_policy.to_s) }
124
139
  end
125
140
 
126
141
  # Strip the non-interchange and invisible classes while KEEPING the script.
@@ -136,8 +151,8 @@ module Disarm
136
151
 
137
152
  # Remove obfuscation (zero-width, bidi, combining-mark abuse) while keeping
138
153
  # legible content.
139
- def strip_obfuscation(text)
140
- translate_errors { _strip_obfuscation(text) }
154
+ def strip_obfuscation(text, digit_policy: :numeric)
155
+ translate_errors { _strip_obfuscation(text, digit_policy.to_s) }
141
156
  end
142
157
 
143
158
  # Canonicalize text for security-sensitive comparison: strip obfuscation,
@@ -151,8 +166,8 @@ module Disarm
151
166
  # delimiter can leave here carrying one. #inspect_anomalies reports it as :confusable
152
167
  # WHEN the word also carries an ASCII letter, which is the gate that keeps ordinary
153
168
  # non-Latin text from firing; a delimiter-only string is not reported.
154
- def canonicalize(text)
155
- translate_errors { _canonicalize(text) }
169
+ def canonicalize(text, digit_policy: :numeric)
170
+ translate_errors { _canonicalize(text, digit_policy.to_s) }
156
171
  end
157
172
 
158
173
  # @deprecated Renamed to {#canonicalize} in 0.11 (the +_clean+ name
@@ -165,22 +180,60 @@ module Disarm
165
180
  # Case/accent/script-insensitive search lookup key. `lang:` applies a
166
181
  # language profile for transliteration (e.g. "ru", "uk"); nil means none.
167
182
  # Raises Disarm::InvalidArgument on an unknown lang.
168
- def search_key(text, lang: nil)
169
- translate_errors { _search_key(text, lang&.to_s) }
183
+ def search_key(text, lang: nil, digit_policy: :numeric)
184
+ translate_errors { _search_key(text, lang&.to_s, digit_policy.to_s) }
170
185
  end
171
186
 
172
187
  # Collation sort key (like #search_key, but keeps base accented characters
173
188
  # for correct ordering). `lang:` applies a language profile; nil means none.
174
189
  # Raises Disarm::InvalidArgument on an unknown lang.
175
- def sort_key(text, lang: nil)
176
- translate_errors { _sort_key(text, lang&.to_s) }
190
+ def sort_key(text, lang: nil, digit_policy: :numeric)
191
+ translate_errors { _sort_key(text, lang&.to_s, digit_policy.to_s) }
177
192
  end
178
193
 
179
194
  # Library catalog deduplication key (search_key plus confusable folding).
180
195
  # `lang:` applies a language profile; `strict_iso9:` selects the ISO 9:1995
181
196
  # Cyrillic scheme. Raises Disarm::InvalidArgument on an unknown lang.
182
- def catalog_key(text, lang: nil, strict_iso9: false)
183
- translate_errors { _catalog_key(text, lang&.to_s, strict_iso9) }
197
+ def catalog_key(text, lang: nil, strict_iso9: false, digit_policy: :numeric)
198
+ translate_errors { _catalog_key(text, lang&.to_s, strict_iso9, digit_policy.to_s) }
199
+ end
200
+
201
+ # The TR39 identifier skeleton plus the two prototype classes disarm keeps apart
202
+ # (#650). A spoof key: its only job is to make confusable identifiers collide, and its
203
+ # output is never for display. `digit_policy:` is `:numeric` (the letter half only),
204
+ # `:tr39` (adds `1 ≡ l` and `0 ≡ O`) or `:preserve` (a non-Latin numeral keeps its
205
+ # script).
206
+ def skeleton_key(text, digit_policy: :numeric)
207
+ translate_errors { _skeleton_key(text, digit_policy.to_s) }
208
+ end
209
+
210
+ # Levenshtein edit distance between `a` and `b`, in characters (#894). The one class
211
+ # of registry spoofing the confusable tables deliberately do not model — `paypa1`,
212
+ # `adm1n`. Canonicalize both sides first when composed and decomposed spellings
213
+ # should compare equal.
214
+ def edit_distance(left, right)
215
+ translate_errors { _edit_distance(left, right) }
216
+ end
217
+
218
+ # The candidate closest to `value`, as `{ value:, distance: }`, or nil beyond
219
+ # `max_distance:` (#894). Reports; it does not decide. An exact match is reported with
220
+ # distance 0, and ties go to the first candidate at the lowest distance.
221
+ def nearest_match(value, candidates, max_distance: 1)
222
+ hit = translate_errors { _nearest_match(value, candidates.map(&:to_s), max_distance) }
223
+ hit && { value: hit[0], distance: hit[1] }
224
+ end
225
+
226
+ # Whether `text` is already its own canonical form under `preset:` (#730).
227
+ #
228
+ # The verification-path counterpart to the presets: text in, normalized text out is
229
+ # the generation path, and this is the question a caller asks about bytes that arrive
230
+ # already bound. `has_anomalies?` is not this predicate — 142,760 assigned code points are
231
+ # reported clean by the detector and are not their own canonical form.
232
+ #
233
+ # `preset:` is any name in the preset registry or any profile. Raises
234
+ # Disarm::InvalidArgument on an unknown one.
235
+ def canonical?(text, preset: "canonicalize")
236
+ translate_errors { _is_canonical?(text, preset.to_s) }
184
237
  end
185
238
 
186
239
  # Strip diacritics ("café" → "cafe").
@@ -486,6 +539,22 @@ module Disarm
486
539
  translate_errors { _script_info(name.to_s) }
487
540
  end
488
541
 
542
+ # TR39 confusable sources whose prototype is in +script+, and how many of those
543
+ # disarm's bundled tables fold. Returns a Hash with +:script+, +:sources+ and
544
+ # +:folded+ keys.
545
+ #
546
+ # The denominator +unmapped_confusables+ does not have: that measures one bundled
547
+ # table against the whole 6,565-source population, so a script disarm ships no table
548
+ # for reports a number determined by that absence rather than by its coverage.
549
+ #
550
+ # +:folded+ counts sources any bundled table reaches, not sources folded *toward*
551
+ # this script — Greek is 71 of 159, because the Latin table folds Greek letters that
552
+ # look Latin. A script disarm knows that TR39 never uses as a prototype returns 0 of
553
+ # 0. Raises Disarm::InvalidArgument on an unknown script.
554
+ def confusable_coverage(script)
555
+ translate_errors { _confusable_coverage(script.to_s) }
556
+ end
557
+
489
558
  # The Unicode `confusables.txt` release the bundled confusable tables were folded
490
559
  # from, e.g. "17.0.0". Not a Unicode version for the library as a whole — the
491
560
  # case-folding and width tables track different releases (see docs/provenance.md).
@@ -574,7 +643,9 @@ module Disarm
574
643
  # pipe.process("Café") # => "cafe"
575
644
  # pipe.process("Köln") # reuse the same handle
576
645
  #
577
- # Disarm::Pipeline#process is the Rust-defined instance method on the handle.
646
+ # Disarm::Pipeline#process is the Rust-defined instance method on the handle, and
647
+ # Disarm::Pipeline#with_digit_policy(policy) returns a copy whose confusable passes
648
+ # fold under `policy` (#646); a profile with no confusables step refuses one.
578
649
  def get_pipeline(profile)
579
650
  translate_errors { _get_pipeline(profile.to_s) }
580
651
  end
@@ -615,4 +686,17 @@ module Disarm
615
686
  raise Error, e.message, e.backtrace
616
687
  end
617
688
  end
689
+
690
+ # The reusable handle `Disarm.get_pipeline` returns. `#process` is Rust-defined; this
691
+ # reopens the class for the one method that can fail, so its error arrives as
692
+ # `Disarm::InvalidArgument` the way every module-level call's does.
693
+ class Pipeline
694
+ # A copy of this pipeline whose confusable passes fold under `policy` (#646):
695
+ # `:numeric`, `:tr39` or `:preserve`. Raises Disarm::InvalidArgument when the profile
696
+ # has no confusables step and the policy is not the default — a setting that would
697
+ # never run is refused rather than kept.
698
+ def with_digit_policy(policy)
699
+ Disarm.send(:translate_errors) { _with_digit_policy(policy.to_s) }
700
+ end
701
+ end
618
702
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Richard Quinn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-01 00:00:00.000000000 Z
11
+ date: 2026-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake