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 +4 -4
- data/CHANGELOG.md +35 -1
- data/MIGRATION.md +26 -7
- data/README.md +28 -4
- data/lib/sec_id/base.rb +2 -21
- data/lib/sec_id/bic/country_codes.rb +1 -2
- data/lib/sec_id/cei.rb +1 -1
- data/lib/sec_id/cfi/attribute_set.rb +4 -1
- data/lib/sec_id/cfi/tables.rb +1 -2
- data/lib/sec_id/concerns/checkable.rb +42 -89
- data/lib/sec_id/cusip.rb +1 -1
- data/lib/sec_id/detector.rb +13 -10
- data/lib/sec_id/figi.rb +1 -1
- data/lib/sec_id/iban/country_rules.rb +1 -2
- data/lib/sec_id/iban.rb +1 -1
- data/lib/sec_id/isin.rb +1 -1
- data/lib/sec_id/lei.rb +1 -1
- data/lib/sec_id/scanner.rb +12 -7
- data/lib/sec_id/version.rb +1 -1
- data/lib/sec_id.rb +0 -5
- data/sec_id.gemspec +1 -1
- data/sig/sec_id/base.rbs +0 -4
- data/sig/sec_id/cei.rbs +0 -4
- data/sig/sec_id/cfi/attribute_set.rbs +2 -2
- data/sig/sec_id/concerns/checkable.rbs +7 -14
- data/sig/sec_id/cusip.rbs +0 -4
- data/sig/sec_id/detector.rbs +1 -1
- data/sig/sec_id/dti.rbs +0 -4
- data/sig/sec_id/figi.rbs +0 -4
- data/sig/sec_id/iban.rbs +0 -4
- data/sig/sec_id/isin.rbs +0 -4
- data/sig/sec_id/lei.rbs +0 -4
- data/sig/sec_id/scanner.rbs +1 -1
- data/sig/sec_id/sedol.rbs +0 -4
- data/sig/sec_id/upi.rbs +0 -4
- data/sig/sec_id.rbs +0 -3
- metadata +3 -5
- data/lib/sec_id/deprecation.rb +0 -26
- data/sig/sec_id/deprecation.rbs +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d50c9163daf21728a883a7c9ae03215677c655ef2c8590b917b58306ab31efb1
|
|
4
|
+
data.tar.gz: 2a9ad832edc6be71a8bb4659909d1adfc6740e804aea2acad909b8373575a017
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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', '~>
|
|
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
|
-
|
|
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:
|
|
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) =
|
|
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(
|
|
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
|
|
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}]
|
data/lib/sec_id/cfi/tables.rb
CHANGED
|
@@ -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(
|
|
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
|
|
166
|
-
#
|
|
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
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
191
|
-
#
|
|
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
|
|
155
|
+
# @param id [String] validated identifier body
|
|
195
156
|
# @return [Integer] the Luhn sum
|
|
196
|
-
def luhn_sum_standard(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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(
|
|
64
|
+
mod10(luhn_sum_single(identifier))
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Generates a random CUSIP body: 8 alphanumeric characters.
|
data/lib/sec_id/detector.rb
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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".
|
|
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(
|
|
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.
|
|
93
|
+
identifier.each_char.with_object(+'') { |char, numeric| numeric << DECIMAL_VALUES.fetch(char) }
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
data/lib/sec_id/scanner.rb
CHANGED
|
@@ -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
|
|
121
|
+
Match.new(type: best.class.type_key, raw: raw, range: start_pos...end_pos, identifier: best)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
# @return [
|
|
124
|
+
# @return [Base, nil]
|
|
125
125
|
def best_match(cleaned, classes)
|
|
126
126
|
return if classes.empty?
|
|
127
127
|
|
|
128
|
-
candidates =
|
|
129
|
-
return
|
|
128
|
+
candidates = @candidates_by_length[cleaned.length]
|
|
129
|
+
return unless candidates
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
data/lib/sec_id/version.rb
CHANGED
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.
|
|
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[
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
def
|
|
39
|
-
def
|
|
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
|
data/sig/sec_id/detector.rbs
CHANGED
|
@@ -11,7 +11,7 @@ module SecID
|
|
|
11
11
|
private
|
|
12
12
|
|
|
13
13
|
def filter_candidates: (String upcased) -> Array[singleton(Base)]
|
|
14
|
-
def
|
|
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
|
data/sig/sec_id/scanner.rbs
CHANGED
|
@@ -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) ->
|
|
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:
|
|
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.
|
|
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.
|
|
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.'
|
data/lib/sec_id/deprecation.rb
DELETED
|
@@ -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
|