sec_id 7.1.1 → 8.0.1

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: be4a59e18a04226f78492df3eb2ec1318af8e7c99044005bda0f7b9652ac7a33
4
- data.tar.gz: 6edd9226b4cc7b293a7fe1950f32aa0673ea0b44507b36ecc253da98f372d3c2
3
+ metadata.gz: d50c9163daf21728a883a7c9ae03215677c655ef2c8590b917b58306ab31efb1
4
+ data.tar.gz: 2a9ad832edc6be71a8bb4659909d1adfc6740e804aea2acad909b8373575a017
5
5
  SHA512:
6
- metadata.gz: b4b95d04179e0aee0ff4c45dbc2e169a024d3933a2c8234de2319422d19773d34247d83b8f457317af93d536ac431406fca9455a454cc7159ae2a9bad8982955
7
- data.tar.gz: f479c160b3229763197b1ac1fe3b7d67f8a6fc3281d3607adf59fece50e89cc8dd2160eb2d4f94d32954d136bc2e8c8998b19bfcdf1e24f4e32a2a035f9e972a
6
+ metadata.gz: 4a6b95868dd3d59b5bc3d48f6e7ce4b44b50d1c24389906505e5708cf4b6add66b6facc667dfe72348203a9c5e0fe35be9ef57978ed9600a4bfe9f53f5a9658a
7
+ data.tar.gz: 7592379ce4c9915548aa4e732f2065f41d455e6099e37b3a0a1dfeb48e36b3552c6f4b01bd089d4e505f7e5b73be940b832f965a8c8cc7060ed10e67171ed88f
data/CHANGELOG.md CHANGED
@@ -8,6 +8,38 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [8.0.1] - 2026-10-30
12
+
13
+ This patch release contains a breaking API change as an explicit exception to Semantic Versioning. The v7 bridge removal was scheduled for 8.0.0 but was omitted.
14
+
15
+ ### Removed
16
+
17
+ - **BREAKING:** Remove deprecated instance methods `check_digit` and `calculate_check_digit`, and class methods `check_digit` and `has_check_digit?`. Use `checksum`, `calculate_checksum`, and `has_checksum?`.
18
+ - **BREAKING:** Remove the `SecID::InvalidCheckDigitError` alias. Rescue `SecID::InvalidChecksumError` instead.
19
+ - **BREAKING:** Remove the `:check_digit` key from `to_h`, `as_json`, and pattern matching. Use `:checksum`.
20
+ - Remove the unused `SecID::Deprecation` warning helper and the bridge's RBS declarations.
21
+
22
+ ## [8.0.0] - 2026-10-30
23
+
24
+ Ruby 3.3 or later is now required. Dropping Ruby 3.2 support is the breaking change that requires this major release.
25
+
26
+ The deprecated v7 check-digit methods, error alias, and component key remain available in 8.0.0.
27
+
28
+ ### Added
29
+
30
+ - Lefthook Git hooks for staged secret scanning, Ruby autocorrection, whitespace, commit messages, tests, types, and documentation coverage.
31
+ - CI checks library CRAP scores with crap4ruby and fresh test coverage, with a maximum score of 10.
32
+
33
+ ### Changed
34
+
35
+ - **BREAKING:** Drop support for Ruby 3.2 and require Ruby 3.3 or later.
36
+ - Reduce detector allocations by sorting candidate types once and stopping parsing at the first valid match. Unsupported input lengths no longer grow the detector cache.
37
+ - Reduce LEI and IBAN checksum allocations by reusing decimal expansions and preserving IBAN numeric characters.
38
+ - Reduce scanner allocations by sorting candidate types once, stopping at the first valid type, and reusing the validated instance.
39
+ - Reduce temporary allocations in ISIN, CUSIP, CEI, and FIGI checksum calculations by processing ASCII bytes directly.
40
+ - Refresh development dependencies, all four lockfiles, and GitHub Actions for frozen CI installs.
41
+ - RBS: `SecID::CFI::AttributeSet#to_h` and `#as_json` are now typed `Hash[Symbol, Hash[Symbol, untyped]]` instead of `Hash[untyped, untyped]` (Steep 2.1 tuple inference)
42
+
11
43
  ## [7.1.1] - 2026-07-31
12
44
 
13
45
  Documentation and packaging only. No identifier, checksum, or public method changed.
@@ -285,7 +317,9 @@ Gem metadata only: corrected the gemspec description and README wording. No libr
285
317
 
286
318
  - ISIN numbers support: `SecID::ISIN`
287
319
 
288
- [Unreleased]: https://github.com/svyatov/sec_id/compare/v7.1.1...HEAD
320
+ [Unreleased]: https://github.com/svyatov/sec_id/compare/v8.0.1...HEAD
321
+ [8.0.1]: https://github.com/svyatov/sec_id/compare/v8.0.0...v8.0.1
322
+ [8.0.0]: https://github.com/svyatov/sec_id/compare/v7.1.1...v8.0.0
289
323
  [7.1.1]: https://github.com/svyatov/sec_id/compare/v7.1.0...v7.1.1
290
324
  [7.1.0]: https://github.com/svyatov/sec_id/compare/v7.0.0...v7.1.0
291
325
  [7.0.0]: https://github.com/svyatov/sec_id/compare/v6.1.0...v7.0.0
data/MIGRATION.md CHANGED
@@ -1,4 +1,23 @@
1
- # Upgrading to SecID 7.0
1
+ # Migration guide
2
+
3
+ ## Upgrading to SecID 8.0.1
4
+
5
+ Version 8.0.1 removes the deprecated v7 bridge that remained in 8.0.0. This is an explicit exception to Semantic Versioning: the patch release contains a breaking API change.
6
+
7
+ Replace these names before upgrading:
8
+
9
+ | Removed | Replacement |
10
+ |---|---|
11
+ | Instance `check_digit` | `checksum` |
12
+ | Instance `calculate_check_digit` | `calculate_checksum` |
13
+ | Class `check_digit(id)` | `checksum(id)` |
14
+ | Class `has_check_digit?` | `has_checksum?` |
15
+ | `SecID::InvalidCheckDigitError` | `SecID::InvalidChecksumError` |
16
+ | `:check_digit` in `to_h`, `as_json`, and pattern matching | `:checksum` |
17
+
18
+ The `SecID::Deprecation` warning helper is also removed. The canonical checksum API, `restore`, `restore!`, and checksum calculations are unchanged.
19
+
20
+ ## Upgrading to SecID 7.0
2
21
 
3
22
  This guide covers all breaking changes when upgrading from SecID 6.x to 7.0. They are all one
4
23
  mechanical rename: the **check-digit** concept became **checksum** across the public API —
@@ -25,7 +44,7 @@ deprecation warnings changed.
25
44
  `restore` / `restore!` and the `Checkable` concern name are unchanged.
26
45
 
27
46
  **Bridge:** the old method names, the `InvalidCheckDigitError` constant, and the `:check_digit`
28
- components key all keep working through v7 (the methods warn on use); they are removed in v8.
47
+ components key all keep working through v7 and 8.0.0 (the methods warn on use); they are removed in 8.0.1.
29
48
  **The one exception is the error code** — `:invalid_check_digit` flips hard to `:invalid_checksum`
30
49
  at v7 with no bridge, so an error-code matcher must be updated immediately.
31
50
 
@@ -56,7 +75,7 @@ SecID::ISIN.has_checksum?
56
75
  ```
57
76
 
58
77
  The old names still work through v7 but emit a deprecation warning on every call and are removed
59
- in v8. See step 6 to silence them if you cannot migrate every call site yet.
78
+ in 8.0.1. See step 6 to silence them on older releases if you cannot migrate every call site yet.
60
79
 
61
80
  ### 3. Rename the rescued error class
62
81
 
@@ -70,7 +89,7 @@ rescue SecID::InvalidChecksumError => e
70
89
 
71
90
  `SecID::InvalidCheckDigitError` remains as a constant alias of `SecID::InvalidChecksumError`
72
91
  through v7 (it is the *same class object*, so `rescue` under either name catches the same error),
73
- and is removed in v8.
92
+ and is removed in 8.0.1.
74
93
 
75
94
  The constant alias emits **no** deprecation warning at runtime (it is a plain constant, not a
76
95
  warned method), so `-W` output won't reveal your rescue sites — find them statically:
@@ -100,15 +119,15 @@ SecID.explain('US5949181040', types: [:isin])[:candidates].first[:errors]
100
119
  If you use the opt-in ActiveModel/Rails validator with `details: true`, the surfaced reason
101
120
  follows the same flip: an invalid checksum now reports `:invalid_checksum`.
102
121
 
103
- ### 5. Migrate pattern matches and `to_h` readers before v8
122
+ ### 5. Migrate pattern matches and `to_h` readers before 8.0.1
104
123
 
105
124
  `components` (and therefore `to_h` and `deconstruct_keys`) carries **both** `:checksum` and the
106
125
  deprecated `:check_digit` key through v7, so existing pattern matches keep working. The
107
- `:check_digit` key is removed in v8 — migrate to `:checksum` before then.
126
+ `:check_digit` key is removed in 8.0.1. Migrate to `:checksum` before then.
108
127
 
109
128
  ```ruby
110
129
  # Both work through v7, binding the same value:
111
- isin => { check_digit: } # deprecated, removed in v8
130
+ isin => { check_digit: } # deprecated, removed in 8.0.1
112
131
  isin => { checksum: } # canonical
113
132
 
114
133
  isin.to_h[:components]
data/README.md CHANGED
@@ -6,7 +6,7 @@ A Ruby toolkit for securities identifiers: validate, parse, normalize, detect, c
6
6
 
7
7
  - **Nothing to call, nothing to sign up for.** SecID validates all 16 identifier standards offline. No registry lookup, no API key, no network call anywhere in [`lib/`](lib/).
8
8
  - **9 of the 16 carry a checksum.** SecID validates it, restores a missing one, and repairs a wrong one.
9
- - **Zero runtime dependencies.** Runs on Ruby 3.2 or newer.
9
+ - **Zero runtime dependencies.** Runs on Ruby 3.3 or newer.
10
10
  - **Rails, if you want it.** An opt-in ActiveModel validator adds `validates :isin, sec_id: true`. It stays off the default require path, so the dependency count stays at zero.
11
11
  - **Typed.** Ships RBS signatures for the whole public API, checked by Steep in strict mode.
12
12
 
@@ -50,14 +50,16 @@ A Ruby toolkit for securities identifiers: validate, parse, normalize, detect, c
50
50
 
51
51
  ## Supported Ruby versions
52
52
 
53
- Ruby 3.2+ is required.
53
+ Ruby 3.3+ is required.
54
+
55
+ Version 8.0.1 removes the deprecated check-digit aliases retained in 8.0.0. This patch release contains a breaking API change; see the [migration guide](MIGRATION.md#upgrading-to-secid-801).
54
56
 
55
57
  ## Installation
56
58
 
57
59
  Add this line to your application's Gemfile:
58
60
 
59
61
  ```ruby
60
- gem 'sec_id', '~> 7.1'
62
+ gem 'sec_id', '~> 8.0'
61
63
  ```
62
64
 
63
65
  And then execute:
@@ -922,10 +924,26 @@ committed signatures fall out of sync with the tables.
922
924
 
923
925
  ## Development
924
926
 
925
- After checking out the repo, run `bin/setup` to install dependencies.
927
+ Install [Betterleaks](https://github.com/betterleaks/betterleaks) and make its `betterleaks` command available on your `PATH`.
928
+ After checking out the repo, run `bin/setup` to install dependencies and Git hooks.
926
929
  Then run `bundle exec rake` to run RuboCop, validate the RBS signatures, and run the specs.
927
930
  `bin/console` gives you an interactive prompt to experiment in.
928
931
 
932
+ Pre-commit scans staged changes for secrets and whitespace errors, and safely corrects staged Ruby files with RuboCop.
933
+ Corrected Ruby files are staged automatically.
934
+ Pre-push runs static type checks, type coverage, specs, runtime signature checks, and documentation coverage.
935
+ Commit messages must follow Conventional Commits.
936
+ Put personal hook overrides in the ignored `lefthook-local.yml` file.
937
+
938
+ Run all pre-push jobs manually with `bundle exec lefthook run pre-push`.
939
+
940
+ CI checks library methods with [crap4ruby](https://github.com/svyatov/crap4ruby), using fresh test coverage and a maximum CRAP score of 10.
941
+ Run the same check locally:
942
+
943
+ ```sh
944
+ COVERAGE=1 bundle exec rake spec crap
945
+ ```
946
+
929
947
  To use your working copy from another project, point its Gemfile at the checkout:
930
948
  `gem 'sec_id', path: '/path/to/sec_id'`.
931
949
 
@@ -941,6 +959,12 @@ One person actively maintains SecID. They read bug reports and pull requests, an
941
959
 
942
960
  Bug reports and pull requests are welcome on [GitHub](https://github.com/svyatov/sec_id). See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style, and PR guidelines.
943
961
 
962
+ For checksum throughput and allocation comparisons, run `ruby -Ilib benchmark/checksums.rb`. It covers Luhn checksums, LEI, and IBAN, including numeric and letter-heavy inputs. It prints repeated samples as JSON with garbage collection enabled. Run the same script with `-I/path/to/baseline/lib` to compare another checkout on the same Ruby version.
963
+
964
+ For scanner comparisons, run `ruby -Ilib benchmark/scanner.rb`. It measures paragraphs, dense identifiers, noise, type filters, ambiguous identifiers, and compound patterns. JSON output includes matches to check correctness alongside timings and allocations. Use the same baseline option to compare another checkout.
965
+
966
+ For detection and parsing comparisons, run `ruby -Ilib benchmark/detector.rb`. It measures valid identifiers, ambiguous inputs, invalid checksums, and unsupported lengths. JSON output includes results, timings, and allocations with garbage collection enabled. Use the same baseline option to compare another checkout.
967
+
944
968
  This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).
945
969
 
946
970
  ## Changelog
data/lib/sec_id/base.rb CHANGED
@@ -95,14 +95,6 @@ module SecID
95
95
 
96
96
  @has_checksum = ancestors.include?(SecID::Checkable)
97
97
  end
98
-
99
- # @deprecated Use {.has_checksum?}. Kept as a v7 bridge; removed in v8.
100
- #
101
- # @return [Boolean]
102
- def has_check_digit?
103
- SecID::Deprecation.warn(old: 'has_check_digit?', new: 'has_checksum?')
104
- has_checksum?
105
- end
106
98
  end
107
99
 
108
100
  # @api private
@@ -142,7 +134,7 @@ module SecID
142
134
  full_id: full_id,
143
135
  normalized: valid? ? normalized : nil,
144
136
  valid: valid?,
145
- components: components_with_deprecation_bridge
137
+ components: components
146
138
  }
147
139
  end
148
140
 
@@ -165,7 +157,7 @@ module SecID
165
157
  # in SecID::ISIN[country_code:, nsin:] then [country_code, nsin]
166
158
  # in nil then :invalid
167
159
  # end #=> ['US', '594918104']
168
- def deconstruct_keys(_keys) = components_with_deprecation_bridge
160
+ def deconstruct_keys(_keys) = components
169
161
 
170
162
  protected
171
163
 
@@ -176,17 +168,6 @@ module SecID
176
168
 
177
169
  private
178
170
 
179
- # Mirrors the canonical `:checksum` components key onto the deprecated `:check_digit`
180
- # key for the v7 bridge, when a checksum is present. The mirror reads `:checksum`, so the
181
- # deprecated `check_digit` reader never fires internally. Removed in v8 (revert `to_h` and
182
- # `deconstruct_keys` to call `components` directly).
183
- #
184
- # @return [Hash] the parsed components, with `:check_digit` added when `:checksum` is present
185
- def components_with_deprecation_bridge
186
- parsed = components
187
- parsed.key?(:checksum) ? parsed.merge(check_digit: parsed[:checksum]) : parsed
188
- end
189
-
190
171
  # @return [Hash]
191
172
  def components
192
173
  {}
@@ -10,7 +10,7 @@ module SecID
10
10
  #
11
11
  # @api private
12
12
  # @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
13
- # rubocop:disable Metrics/CollectionLiteralLength
13
+ # rubocop:disable-next Metrics/CollectionLiteralLength
14
14
  COUNTRY_CODES = Set.new(
15
15
  %w[
16
16
  AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ
@@ -41,6 +41,5 @@ module SecID
41
41
  XK
42
42
  ]
43
43
  ).freeze
44
- # rubocop:enable Metrics/CollectionLiteralLength
45
44
  end
46
45
  end
data/lib/sec_id/cei.rb CHANGED
@@ -55,7 +55,7 @@ module SecID
55
55
  # @raise [InvalidFormatError] if the CEI format is invalid
56
56
  def calculate_checksum
57
57
  validate_format_for_calculation!
58
- mod10(luhn_sum_double_add_double(reversed_digits_single(identifier)))
58
+ mod10(luhn_sum_single(identifier))
59
59
  end
60
60
 
61
61
  # Generates a random CEI body: 1 letter + 1 digit + 7 alphanumeric characters.
@@ -36,8 +36,11 @@ module SecID
36
36
  end
37
37
 
38
38
  # @return [Hash{Symbol => Hash}]
39
+ # @raise [ArgumentError] if a field has no meaning (category/group fields; never the case for attributes)
39
40
  def to_h
40
- @fields.to_h { |field| [field.meaning, field.to_h] }
41
+ @fields.to_h do |field|
42
+ [field.meaning || raise(ArgumentError, "attribute field #{field.code} has no meaning"), field.to_h]
43
+ end
41
44
  end
42
45
 
43
46
  # @return [Hash{Symbol => Hash}]
@@ -21,7 +21,7 @@ module SecID
21
21
  # +:not_applicable+, so it is never listed in a value map.
22
22
  #
23
23
  # @api private
24
- # rubocop:disable Metrics/ModuleLength
24
+ # rubocop:disable-next Metrics/ModuleLength
25
25
  module Tables
26
26
  # Marker for a pure not-applicable attribute position (accepts only +X+).
27
27
  NA = nil
@@ -1028,6 +1028,5 @@ module SecID
1028
1028
  GROUPS.dig(category_code, group_code)
1029
1029
  end
1030
1030
  end
1031
- # rubocop:enable Metrics/ModuleLength
1032
1031
  end
1033
1032
  end
@@ -22,26 +22,12 @@ module SecID
22
22
  #
23
23
  # def calculate_checksum
24
24
  # validate_format_for_calculation!
25
- # mod10(luhn_sum_standard(reversed_digits_multi(identifier)))
25
+ # mod10(luhn_sum_standard(identifier))
26
26
  # end
27
27
  # end
28
28
  #
29
29
  # @see https://en.wikipedia.org/wiki/Luhn_algorithm
30
30
  module Checkable
31
- # Character-to-digit mapping for Luhn algorithm variants.
32
- # Maps alphanumeric characters to digit arrays for multi-digit expansion.
33
- # Used by ISIN for checksum calculation.
34
- CHAR_TO_DIGITS = {
35
- '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
36
- '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
37
- 'A' => [1, 0], 'B' => [1, 1], 'C' => [1, 2], 'D' => [1, 3], 'E' => [1, 4],
38
- 'F' => [1, 5], 'G' => [1, 6], 'H' => [1, 7], 'I' => [1, 8], 'J' => [1, 9],
39
- 'K' => [2, 0], 'L' => [2, 1], 'M' => [2, 2], 'N' => [2, 3], 'O' => [2, 4],
40
- 'P' => [2, 5], 'Q' => [2, 6], 'R' => [2, 7], 'S' => [2, 8], 'T' => [2, 9],
41
- 'U' => [3, 0], 'V' => [3, 1], 'W' => [3, 2], 'X' => [3, 3], 'Y' => [3, 4], 'Z' => [3, 5],
42
- '*' => [3, 6], '@' => [3, 7], '#' => [3, 8]
43
- }.freeze
44
-
45
31
  # Character-to-digit mapping for single-digit conversion.
46
32
  # Maps alphanumeric characters to values 0-38 (A=10, B=11, ..., Z=35, *=36, @=37, #=38).
47
33
  # Used by CUSIP, FIGI, SEDOL, LEI, and IBAN for checksum calculations.
@@ -56,6 +42,15 @@ module SecID
56
42
  '*' => 36, '@' => 37, '#' => 38
57
43
  }.freeze
58
44
 
45
+ # ASCII byte lookup avoids allocating a String for each checksum character.
46
+ BYTE_TO_DIGIT = CHAR_TO_DIGIT.transform_keys(&:ord).freeze
47
+
48
+ # Decimal expansions reused by LEI and IBAN without allocating replacement strings.
49
+ DECIMAL_VALUES = CHAR_TO_DIGIT.transform_values { |value| value.to_s.freeze }.freeze
50
+
51
+ # Doubled decimal digits with their digits summed, for expanded ISIN Luhn.
52
+ LUHN_DOUBLE = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9].freeze
53
+
59
54
  # Extends the including identifier class with the concern's class methods and checksum reader.
60
55
  #
61
56
  # @param base [Class] the identifier class including this concern
@@ -92,16 +87,6 @@ module SecID
92
87
  def checksum(id)
93
88
  new(id).calculate_checksum
94
89
  end
95
-
96
- # @deprecated Use {.checksum}. Kept as a v7 bridge; removed in v8.
97
- #
98
- # @param id [String] the identifier to calculate the checksum for
99
- # @return [Integer, String] the calculated checksum
100
- # @raise [InvalidFormatError] if the identifier format is invalid
101
- def check_digit(id)
102
- SecID::Deprecation.warn(old: 'check_digit', new: 'checksum')
103
- checksum(id)
104
- end
105
90
  end
106
91
 
107
92
  # Validates format and checksum.
@@ -143,80 +128,48 @@ module SecID
143
128
  "#{identifier}#{checksum&.to_s&.rjust(checksum_width, '0')}"
144
129
  end
145
130
 
146
- # @deprecated Use {#checksum}. Kept as a v7 bridge; removed in v8.
147
- #
148
- # @return [Integer, String]
149
- def check_digit
150
- SecID::Deprecation.warn(old: 'check_digit', new: 'checksum')
151
- checksum
152
- end
153
-
154
- # @deprecated Use {#calculate_checksum}. Kept as a v7 bridge; removed in v8.
155
- #
156
- # @return [Integer, String]
157
- # @raise [InvalidFormatError] if the identifier format is invalid
158
- def calculate_check_digit
159
- SecID::Deprecation.warn(old: 'calculate_check_digit', new: 'calculate_checksum')
160
- calculate_checksum
161
- end
162
-
163
131
  private
164
132
 
165
- # CUSIP/CEI style: "Double Add Double" algorithm.
166
- # Processes pairs of digits, doubling the first (even-positioned from right),
167
- # then summing both digit's div10mod10 values.
168
- #
169
- # @param digits [Array<Integer>] reversed array of digit values
170
- # @return [Integer] the Luhn sum
171
- def luhn_sum_double_add_double(digits)
172
- digits.each_slice(2).reduce(0) do |sum, (even, odd)|
173
- double_even = (even || 0) * 2
174
- sum + div10mod10(double_even) + div10mod10(odd || 0)
175
- end
176
- end
177
-
178
- # FIGI style: index-based doubling algorithm.
179
- # Doubles odd-indexed digits (from right), then sums div10mod10 values.
133
+ # CUSIP/CEI double the rightmost body character; FIGI starts one position left.
134
+ # Input must already have passed the identifier's ASCII format validation.
180
135
  #
181
- # @param digits [Array<Integer>] reversed array of digit values
136
+ # @param id [String] validated identifier body
137
+ # @param double_first [Boolean] whether to double the rightmost value
182
138
  # @return [Integer] the Luhn sum
183
- def luhn_sum_indexed(digits)
184
- digits.each_with_index.reduce(0) do |sum, (digit, index)|
185
- digit *= 2 if index.odd?
186
- sum + div10mod10(digit)
139
+ def luhn_sum_single(id, double_first: true)
140
+ # @type var id: String
141
+ sum = 0
142
+ double = double_first
143
+ (id.bytesize - 1).downto(0) do |index|
144
+ value = BYTE_TO_DIGIT.fetch(id.getbyte(index))
145
+ value *= 2 if double
146
+ sum += div10mod10(value)
147
+ double = !double
187
148
  end
149
+ sum
188
150
  end
189
151
 
190
- # ISIN style: standard Luhn with subtract-9 for values > 9.
191
- # Processes pairs of digits, doubling the first (even-positioned from right),
192
- # subtracting 9 if result > 9.
152
+ # ISIN expands letters into two decimal digits before applying Luhn.
153
+ # A letter consumes two positions, so only a numeric character flips parity.
193
154
  #
194
- # @param digits [Array<Integer>] reversed array of digit values
155
+ # @param id [String] validated identifier body
195
156
  # @return [Integer] the Luhn sum
196
- def luhn_sum_standard(digits)
197
- digits.each_slice(2).reduce(0) do |sum, (even, odd)|
198
- double_even = (even || 0) * 2
199
- double_even -= 9 if double_even > 9
200
- sum + double_even + (odd || 0)
157
+ def luhn_sum_standard(id)
158
+ # @type var id: String
159
+ sum = 0
160
+ double = true
161
+ (id.bytesize - 1).downto(0) do |index|
162
+ value = BYTE_TO_DIGIT.fetch(id.getbyte(index))
163
+ units = value % 10
164
+ sum += double ? LUHN_DOUBLE.fetch(units) : units
165
+ if value >= 10
166
+ tens = value / 10
167
+ sum += double ? tens : LUHN_DOUBLE.fetch(tens)
168
+ else
169
+ double = !double
170
+ end
201
171
  end
202
- end
203
-
204
- # Converts identifier characters to reversed digit array using single-digit mapping.
205
- # Used by CUSIP, CEI, FIGI, and SEDOL.
206
- #
207
- # @param id [String] the identifier string
208
- # @return [Array<Integer>] reversed array of digit values
209
- def reversed_digits_single(id)
210
- id.each_char.map { |c| CHAR_TO_DIGIT.fetch(c) }.reverse!
211
- end
212
-
213
- # Converts identifier characters to reversed digit array using multi-digit mapping.
214
- # Used by ISIN where letters expand to two digits.
215
- #
216
- # @param id [String] the identifier string
217
- # @return [Array<Integer>] reversed array of digit values
218
- def reversed_digits_multi(id)
219
- id.each_char.flat_map { |c| CHAR_TO_DIGITS.fetch(c) }.reverse!
172
+ sum
220
173
  end
221
174
 
222
175
  # Returns error codes including checksum validation.
data/lib/sec_id/cusip.rb CHANGED
@@ -61,7 +61,7 @@ module SecID
61
61
  # @raise [InvalidFormatError] if the CUSIP format is invalid
62
62
  def calculate_checksum
63
63
  validate_format_for_calculation!
64
- mod10(luhn_sum_double_add_double(reversed_digits_single(identifier)))
64
+ mod10(luhn_sum_single(identifier))
65
65
  end
66
66
 
67
67
  # Generates a random CUSIP body: 8 alphanumeric characters.
@@ -19,7 +19,7 @@ module SecID
19
19
  class Detector
20
20
  # @param identifier_list [Array<Class>] registered identifier classes
21
21
  def initialize(identifier_list)
22
- @classes = identifier_list.dup.freeze
22
+ @classes = identifier_list.sort_by(&:detection_priority).freeze
23
23
  precompute
24
24
  end
25
25
 
@@ -33,7 +33,7 @@ module SecID
33
33
 
34
34
  upcased = input.upcase
35
35
  candidates = filter_candidates(upcased)
36
- validate_and_sort(input, candidates)
36
+ valid_types(input, candidates)
37
37
  end
38
38
 
39
39
  # Returns whether any registered type matches, short-circuiting on the first
@@ -57,8 +57,11 @@ module SecID
57
57
  return if input.empty?
58
58
 
59
59
  candidates = filter_candidates(input.upcase)
60
- matches = candidates.filter_map { |klass| (i = klass.new(input)).valid? ? i : nil }
61
- matches.min_by { |instance| instance.class.detection_priority }
60
+ candidates.each do |klass|
61
+ instance = klass.new(input)
62
+ return instance if instance.valid?
63
+ end
64
+ nil
62
65
  end
63
66
 
64
67
  private
@@ -74,14 +77,13 @@ module SecID
74
77
  stage3_charset(upcased, candidates)
75
78
  end
76
79
 
77
- # Validates candidates and returns sorted symbol keys.
80
+ # Validates candidates in precomputed specificity order.
78
81
  #
79
82
  # @param input [String]
80
83
  # @param candidates [Array<Class>]
81
84
  # @return [Array<Symbol>]
82
- def validate_and_sort(input, candidates)
85
+ def valid_types(input, candidates)
83
86
  matches = candidates.select { |klass| klass.valid?(input) }
84
- matches.sort_by!(&:detection_priority)
85
87
  matches.map!(&:type_key)
86
88
  end
87
89
 
@@ -105,11 +107,12 @@ module SecID
105
107
  #
106
108
  # @return [void]
107
109
  def build_length_table
108
- @candidates_by_length = Hash.new { |h, k| h[k] = [] }
110
+ @candidates_by_length = {}
109
111
  @classes.each do |klass|
110
- klass.length_values.each { |len| @candidates_by_length[len] << klass }
112
+ klass.length_values.each { |len| (@candidates_by_length[len] ||= []) << klass }
111
113
  end
112
114
  @candidates_by_length.each_value(&:freeze)
115
+ @candidates_by_length.default = [].freeze
113
116
  end
114
117
 
115
118
  # Stage 1: route strings with special characters to the only types that accept them.
@@ -130,7 +133,7 @@ module SecID
130
133
  # @param length [Integer]
131
134
  # @return [Array<Class>]
132
135
  def stage2_length(length)
133
- @candidates_by_length[length] || []
136
+ @candidates_by_length[length]
134
137
  end
135
138
 
136
139
  # Stage 3: filter candidates by character set.
data/lib/sec_id/figi.rb CHANGED
@@ -69,7 +69,7 @@ module SecID
69
69
  # @raise [InvalidFormatError] if the FIGI format is invalid
70
70
  def calculate_checksum
71
71
  validate_format_for_calculation!
72
- mod10(luhn_sum_indexed(reversed_digits_single(identifier)))
72
+ mod10(luhn_sum_single(identifier, double_first: false))
73
73
  end
74
74
 
75
75
  # Generates a random FIGI body: non-reserved 2-char prefix + 'G' + 8-char random part.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecID
4
- # rubocop:disable Metrics/ClassLength
4
+ # rubocop:disable-next Metrics/ClassLength
5
5
  class IBAN < Base
6
6
  # Country-specific BBAN rules for EU/EEA countries
7
7
  # Each entry defines:
@@ -265,5 +265,4 @@ module SecID
265
265
  'XK' => 16 # Kosovo
266
266
  }.freeze
267
267
  end
268
- # rubocop:enable Metrics/ClassLength
269
268
  end
data/lib/sec_id/iban.rb CHANGED
@@ -226,7 +226,7 @@ module SecID
226
226
 
227
227
  # @return [String] the numeric string representation
228
228
  def numeric_string_for_check
229
- "#{bban}#{country_code}00".each_char.map { |char| CHAR_TO_DIGIT.fetch(char) }.join
229
+ "#{bban}#{country_code}00".gsub(/[^0-9]/) { |char| DECIMAL_VALUES.fetch(char) }
230
230
  end
231
231
  end
232
232
  end
data/lib/sec_id/isin.rb CHANGED
@@ -86,7 +86,7 @@ module SecID
86
86
  # @raise [InvalidFormatError] if the ISIN format is invalid
87
87
  def calculate_checksum
88
88
  validate_format_for_calculation!
89
- mod10(luhn_sum_standard(reversed_digits_multi(identifier)))
89
+ mod10(luhn_sum_standard(identifier))
90
90
  end
91
91
 
92
92
  # Generates a random ISIN body: 2-letter country code + 9-character NSIN.
data/lib/sec_id/lei.rb CHANGED
@@ -90,7 +90,7 @@ module SecID
90
90
 
91
91
  # @return [String] the numeric string representation
92
92
  def numeric_identifier
93
- identifier.each_char.map { |char| CHAR_TO_DIGIT.fetch(char) }.join
93
+ identifier.each_char.with_object(+'') { |char, numeric| numeric << DECIMAL_VALUES.fetch(char) }
94
94
  end
95
95
  end
96
96
  end
@@ -73,7 +73,7 @@ module SecID
73
73
  @classes.each do |klass|
74
74
  klass.length_values.each { |len| @candidates_by_length[len] << klass }
75
75
  end
76
- @candidates_by_length.each_value(&:freeze)
76
+ @candidates_by_length.each_value { |classes| classes.sort_by!(&:detection_priority).freeze }
77
77
  end
78
78
 
79
79
  # @param input [String]
@@ -118,18 +118,23 @@ module SecID
118
118
  return unless best
119
119
 
120
120
  end_pos = start_pos + raw.length
121
- Match.new(type: best.type_key, raw: raw, range: start_pos...end_pos, identifier: best.new(cleaned))
121
+ Match.new(type: best.class.type_key, raw: raw, range: start_pos...end_pos, identifier: best)
122
122
  end
123
123
 
124
- # @return [Class, nil]
124
+ # @return [Base, nil]
125
125
  def best_match(cleaned, classes)
126
126
  return if classes.empty?
127
127
 
128
- candidates = (@candidates_by_length[cleaned.length] || []) & classes
129
- return if candidates.empty?
128
+ candidates = @candidates_by_length[cleaned.length]
129
+ return unless candidates
130
130
 
131
- validated = candidates.select { |k| cleaned.match?(k::VALID_CHARS_REGEX) && k.valid?(cleaned) }
132
- validated.min_by(&:detection_priority)
131
+ candidates.each do |klass|
132
+ next unless classes.include?(klass) && cleaned.match?(klass::VALID_CHARS_REGEX)
133
+
134
+ instance = klass.new(cleaned)
135
+ return instance if instance.valid?
136
+ end
137
+ nil
133
138
  end
134
139
  end
135
140
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module SecID
4
4
  # Current gem version.
5
- VERSION = '7.1.1'
5
+ VERSION = '8.0.1'
6
6
  end
data/lib/sec_id.rb CHANGED
@@ -17,10 +17,6 @@ module SecID
17
17
  # Raised when the checksum does not match the calculated value.
18
18
  class InvalidChecksumError < Error; end
19
19
 
20
- # @deprecated Use {InvalidChecksumError}. Kept as a v7 bridge (same class object,
21
- # so `rescue` under either name keeps working); removed in v8.
22
- InvalidCheckDigitError = InvalidChecksumError
23
-
24
20
  # Raised for type-specific structural errors (invalid prefix, category, group, BBAN, or date).
25
21
  class InvalidStructureError < Error; end
26
22
 
@@ -233,7 +229,6 @@ module SecID
233
229
  end
234
230
  end
235
231
 
236
- require 'sec_id/deprecation'
237
232
  require 'sec_id/deep_freeze'
238
233
  require 'sec_id/errors'
239
234
  require 'sec_id/concerns/normalizable'
data/sec_id.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.homepage = 'https://github.com/svyatov/sec_id'
23
23
  spec.license = 'MIT'
24
24
 
25
- spec.required_ruby_version = '>= 3.2.0'
25
+ spec.required_ruby_version = '>= 3.3.0'
26
26
 
27
27
  spec.require_paths = ['lib']
28
28
  spec.files = Dir['lib/**/*.rb'] + Dir['sig/**/*'] +
data/sig/sec_id/base.rbs CHANGED
@@ -47,9 +47,6 @@ module SecID
47
47
  # module-level `SecID.suggest` can call it on any registered class object.
48
48
  def self.suggest: (SecID::input str) -> Array[Suggestion]
49
49
 
50
- # @deprecated v7 bridge alias for `has_checksum?`; removed in v8.
51
- def self.has_check_digit?: () -> bool
52
-
53
50
  def self.inherited: (singleton(Base) subclass) -> void
54
51
 
55
52
  def initialize: (SecID::input _sec_id_number) -> void
@@ -75,7 +72,6 @@ module SecID
75
72
 
76
73
  private
77
74
 
78
- def components_with_deprecation_bridge: () -> Hash[Symbol, untyped]
79
75
  def components: () -> Hash[Symbol, untyped]
80
76
  def parse: (SecID::input sec_id_number) -> (::MatchData | ::Hash[Symbol, untyped])
81
77
 
data/sig/sec_id/cei.rbs CHANGED
@@ -17,10 +17,6 @@ module SecID
17
17
  def initialize: (SecID::input cei) -> void
18
18
  def calculate_checksum: () -> Integer
19
19
  def self.checksum: (SecID::input id) -> Integer
20
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
21
- def calculate_check_digit: () -> Integer
22
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
23
- def self.check_digit: (SecID::input id) -> Integer
24
20
  private def self.generate_body: (Random random) -> String
25
21
 
26
22
  private
@@ -12,8 +12,8 @@ module SecID
12
12
  | () -> Enumerator[Field, self]
13
13
  def []: (Symbol meaning) -> Field?
14
14
  def empty?: () -> bool
15
- def to_h: () -> Hash[untyped, untyped]
16
- def as_json: (*untyped) -> Hash[Symbol, untyped]
15
+ def to_h: () -> Hash[Symbol, Hash[Symbol, untyped]]
16
+ def as_json: (*untyped) -> Hash[Symbol, Hash[Symbol, untyped]]
17
17
 
18
18
  # Per-meaning readers — the union of every attribute meaning. Each instance
19
19
  # only answers the meanings actually present; an absent one raises
@@ -1,8 +1,10 @@
1
1
  module SecID
2
2
  # Provides checksum validation and calculation for identifiers with a checksum.
3
3
  module Checkable : SecID::Base
4
- CHAR_TO_DIGITS: Hash[String, Integer | Array[Integer]]
5
4
  CHAR_TO_DIGIT: Hash[String, Integer]
5
+ BYTE_TO_DIGIT: Hash[Integer, Integer]
6
+ DECIMAL_VALUES: Hash[String, String]
7
+ LUHN_DOUBLE: Array[Integer]
6
8
 
7
9
  def self.included: (Module base) -> void
8
10
 
@@ -15,8 +17,6 @@ module SecID
15
17
  def restore!: (SecID::input id_without_checksum) -> Base
16
18
  def checksum: (SecID::input id) -> (Integer | String)
17
19
 
18
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
19
- def check_digit: (SecID::input id) -> (Integer | String)
20
20
  end
21
21
 
22
22
  def valid?: () -> bool
@@ -25,19 +25,12 @@ module SecID
25
25
  def calculate_checksum: () -> (Integer | String)
26
26
  def to_s: () -> String
27
27
 
28
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
29
- def check_digit: () -> (Integer | String)?
30
-
31
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
32
- def calculate_check_digit: () -> (Integer | String)
33
-
34
28
  private
35
29
 
36
- def luhn_sum_double_add_double: (Array[Integer] digits) -> Integer
37
- def luhn_sum_indexed: (Array[Integer] digits) -> Integer
38
- def luhn_sum_standard: (Array[Integer] digits) -> Integer
39
- def reversed_digits_single: (untyped id) -> Array[Integer]
40
- def reversed_digits_multi: (untyped id) -> Array[Integer]
30
+ # Readers are nilable before validation. The callers validate first; local
31
+ # annotations keep the byte traversal typed without adding runtime guards.
32
+ def luhn_sum_single: (untyped id, ?double_first: bool) -> Integer
33
+ def luhn_sum_standard: (untyped id) -> Integer
41
34
  def error_codes: () -> Array[Symbol]
42
35
  def checksum_width: () -> Integer
43
36
  def validation_message: (Symbol code) -> String?
data/sig/sec_id/cusip.rbs CHANGED
@@ -18,10 +18,6 @@ module SecID
18
18
  def to_pretty_s: () -> String?
19
19
  def calculate_checksum: () -> Integer
20
20
  def self.checksum: (SecID::input id) -> Integer
21
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
22
- def calculate_check_digit: () -> Integer
23
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
24
- def self.check_digit: (SecID::input id) -> Integer
25
21
  private def self.generate_body: (Random random) -> String
26
22
  def to_isin: (String country_code) -> ISIN
27
23
  def cins?: () -> bool
@@ -11,7 +11,7 @@ module SecID
11
11
  private
12
12
 
13
13
  def filter_candidates: (String upcased) -> Array[singleton(Base)]
14
- def validate_and_sort: (String input, Array[untyped] candidates) -> Array[Symbol]
14
+ def valid_types: (String input, Array[untyped] candidates) -> Array[Symbol]
15
15
  def precompute: () -> void
16
16
  def build_discriminator_sets: () -> void
17
17
  def build_length_table: () -> void
data/sig/sec_id/dti.rbs CHANGED
@@ -17,10 +17,6 @@ module SecID
17
17
  def initialize: (SecID::input dti) -> void
18
18
  def calculate_checksum: () -> String
19
19
  def self.checksum: (SecID::input id) -> String
20
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
21
- def calculate_check_digit: () -> String
22
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
23
- def self.check_digit: (SecID::input id) -> String
24
20
  private def self.generate_body: (Random random) -> String
25
21
 
26
22
  private
data/sig/sec_id/figi.rbs CHANGED
@@ -19,10 +19,6 @@ module SecID
19
19
  def to_pretty_s: () -> String?
20
20
  def calculate_checksum: () -> Integer
21
21
  def self.checksum: (SecID::input id) -> Integer
22
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
23
- def calculate_check_digit: () -> Integer
24
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
25
- def self.check_digit: (SecID::input id) -> Integer
26
22
  private def self.generate_body: (Random random) -> String
27
23
 
28
24
  private
data/sig/sec_id/iban.rbs CHANGED
@@ -28,10 +28,6 @@ module SecID
28
28
  def restore: () -> String
29
29
  def calculate_checksum: () -> Integer
30
30
  def self.checksum: (SecID::input id) -> Integer
31
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
32
- def calculate_check_digit: () -> Integer
33
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
34
- def self.check_digit: (SecID::input id) -> Integer
35
31
  private def self.generate_body: (Random random) -> String
36
32
  def valid_bban_format?: () -> bool
37
33
  def country_rule: () -> rule?
data/sig/sec_id/isin.rbs CHANGED
@@ -21,10 +21,6 @@ module SecID
21
21
  def to_pretty_s: () -> String?
22
22
  def calculate_checksum: () -> Integer
23
23
  def self.checksum: (SecID::input id) -> Integer
24
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
25
- def calculate_check_digit: () -> Integer
26
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
27
- def self.check_digit: (SecID::input id) -> Integer
28
24
  private def self.generate_body: (Random random) -> String
29
25
  def to_cusip: () -> CUSIP
30
26
  def cgs?: () -> bool
data/sig/sec_id/lei.rbs CHANGED
@@ -22,10 +22,6 @@ module SecID
22
22
  def to_pretty_s: () -> String?
23
23
  def calculate_checksum: () -> Integer
24
24
  def self.checksum: (SecID::input id) -> Integer
25
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
26
- def calculate_check_digit: () -> Integer
27
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
28
- def self.check_digit: (SecID::input id) -> Integer
29
25
  private def self.generate_body: (Random random) -> String
30
26
 
31
27
  private
@@ -33,7 +33,7 @@ module SecID
33
33
  def scan_text: (String input, Array[singleton(Base)] target_classes) { (Match) -> void } -> void
34
34
  def identify_candidate: (untyped match_data, Array[singleton(Base)] target_classes) -> Match?
35
35
  def try_classes: (String raw, String cleaned, Integer start_pos, Array[singleton(Base)] classes) -> Match?
36
- def best_match: (String cleaned, Array[singleton(Base)] classes) -> singleton(Base)?
36
+ def best_match: (String cleaned, Array[singleton(Base)] classes) -> Base?
37
37
 
38
38
  @classes: Array[singleton(Base)]
39
39
  @fisn_classes: Array[singleton(Base)]
data/sig/sec_id/sedol.rbs CHANGED
@@ -21,10 +21,6 @@ module SecID
21
21
  def to_isin: (?String country_code) -> ISIN
22
22
  def calculate_checksum: () -> Integer
23
23
  def self.checksum: (SecID::input id) -> Integer
24
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
25
- def calculate_check_digit: () -> Integer
26
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
27
- def self.check_digit: (SecID::input id) -> Integer
28
24
  private def self.generate_body: (Random random) -> String
29
25
 
30
26
  private
data/sig/sec_id/upi.rbs CHANGED
@@ -16,10 +16,6 @@ module SecID
16
16
  def initialize: (SecID::input upi) -> void
17
17
  def calculate_checksum: () -> String
18
18
  def self.checksum: (SecID::input id) -> String
19
- # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
20
- def calculate_check_digit: () -> String
21
- # @deprecated v7 bridge alias for `checksum`; removed in v8.
22
- def self.check_digit: (SecID::input id) -> String
23
19
  private def self.generate_body: (Random random) -> String
24
20
 
25
21
  private
data/sig/sec_id.rbs CHANGED
@@ -11,9 +11,6 @@ module SecID
11
11
  class InvalidChecksumError < Error
12
12
  end
13
13
 
14
- # @deprecated v7 bridge constant alias for `InvalidChecksumError` (same class object); removed in v8.
15
- InvalidCheckDigitError: singleton(InvalidChecksumError)
16
-
17
14
  # Raised for type-specific structural errors.
18
15
  class InvalidStructureError < Error
19
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sec_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.1
4
+ version: 8.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -46,7 +46,6 @@ files:
46
46
  - lib/sec_id/concerns/validatable.rb
47
47
  - lib/sec_id/cusip.rb
48
48
  - lib/sec_id/deep_freeze.rb
49
- - lib/sec_id/deprecation.rb
50
49
  - lib/sec_id/detector.rb
51
50
  - lib/sec_id/dti.rb
52
51
  - lib/sec_id/errors.rb
@@ -85,7 +84,6 @@ files:
85
84
  - sig/sec_id/concerns/validatable.rbs
86
85
  - sig/sec_id/cusip.rbs
87
86
  - sig/sec_id/deep_freeze.rbs
88
- - sig/sec_id/deprecation.rbs
89
87
  - sig/sec_id/detector.rbs
90
88
  - sig/sec_id/dti.rbs
91
89
  - sig/sec_id/errors.rbs
@@ -119,14 +117,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
117
  requirements:
120
118
  - - ">="
121
119
  - !ruby/object:Gem::Version
122
- version: 3.2.0
120
+ version: 3.3.0
123
121
  required_rubygems_version: !ruby/object:Gem::Requirement
124
122
  requirements:
125
123
  - - ">="
126
124
  - !ruby/object:Gem::Version
127
125
  version: '0'
128
126
  requirements: []
129
- rubygems_version: 4.0.16
127
+ rubygems_version: 4.0.20
130
128
  specification_version: 4
131
129
  summary: 'A Ruby toolkit for securities identifiers: validate, parse, normalize, detect,
132
130
  convert, generate, classify, and repair.'
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SecID
4
- # Emits standardized, silenceable deprecation warnings for the v7 → v8 rename
5
- # bridge. Stateless: warns on every call with no per-call-site dedup, so the
6
- # migration signal is never suppressed by default.
7
- #
8
- # The warning is written via {Kernel#warn} at Ruby's default verbosity — it is
9
- # visible unless the process runs with `-W0` / `$VERBOSE = nil`. No `category:`
10
- # is passed, because `Warning[:deprecated]` defaults to `false` and would hide
11
- # the bridge warning the rename relies on.
12
- module Deprecation
13
- # Emits a standardized deprecation warning for a renamed API name.
14
- #
15
- # @param old [String] the deprecated name
16
- # @param new [String] the canonical replacement name
17
- # @param removed_in [String] the version that removes the deprecated name
18
- # @return [void]
19
- def self.warn(old:, new:, removed_in: 'v8')
20
- Kernel.warn(
21
- "SecID: `#{old}` is deprecated and will be removed in #{removed_in}; use `#{new}` instead.",
22
- uplevel: 2
23
- )
24
- end
25
- end
26
- end
@@ -1,6 +0,0 @@
1
- module SecID
2
- # Emits standardized, silenceable deprecation warnings for the v7 → v8 rename bridge.
3
- module Deprecation
4
- def self.warn: (old: String, new: String, ?removed_in: String) -> void
5
- end
6
- end