sec_id 5.2.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +24 -0
  4. data/MIGRATION.md +101 -0
  5. data/README.md +200 -17
  6. data/lib/sec_id/active_model.rb +144 -0
  7. data/lib/sec_id/base.rb +33 -1
  8. data/lib/sec_id/bic/country_codes.rb +46 -0
  9. data/lib/sec_id/bic.rb +123 -0
  10. data/lib/sec_id/cei.rb +19 -7
  11. data/lib/sec_id/cfi/attribute_set.rb +49 -0
  12. data/lib/sec_id/cfi/classification.rb +107 -0
  13. data/lib/sec_id/cfi/field.rb +56 -0
  14. data/lib/sec_id/cfi/tables.rb +1033 -0
  15. data/lib/sec_id/cfi.rb +138 -208
  16. data/lib/sec_id/cik.rb +13 -0
  17. data/lib/sec_id/concerns/checkable.rb +2 -2
  18. data/lib/sec_id/concerns/generatable.rb +71 -0
  19. data/lib/sec_id/concerns/normalizable.rb +1 -0
  20. data/lib/sec_id/concerns/validatable.rb +14 -3
  21. data/lib/sec_id/cusip.rb +18 -7
  22. data/lib/sec_id/deep_freeze.rb +18 -0
  23. data/lib/sec_id/detector.rb +28 -7
  24. data/lib/sec_id/dti.rb +104 -0
  25. data/lib/sec_id/figi.rb +18 -0
  26. data/lib/sec_id/fisn.rb +31 -0
  27. data/lib/sec_id/iban/country_rules.rb +7 -6
  28. data/lib/sec_id/iban.rb +19 -1
  29. data/lib/sec_id/isin.rb +20 -15
  30. data/lib/sec_id/lei.rb +13 -0
  31. data/lib/sec_id/occ.rb +21 -0
  32. data/lib/sec_id/railtie.rb +14 -0
  33. data/lib/sec_id/scanner.rb +2 -6
  34. data/lib/sec_id/sedol.rb +16 -0
  35. data/lib/sec_id/valoren.rb +13 -0
  36. data/lib/sec_id/version.rb +2 -1
  37. data/lib/sec_id/wkn.rb +16 -0
  38. data/lib/sec_id.rb +29 -4
  39. data/sec_id.gemspec +11 -5
  40. data/sig/manifest.yaml +8 -0
  41. data/sig/sec_id/base.rbs +72 -0
  42. data/sig/sec_id/bic/country_codes.rbs +5 -0
  43. data/sig/sec_id/bic.rbs +30 -0
  44. data/sig/sec_id/cei.rbs +26 -0
  45. data/sig/sec_id/cfi/attribute_set.rbs +68 -0
  46. data/sig/sec_id/cfi/classification.rbs +23 -0
  47. data/sig/sec_id/cfi/field.rbs +337 -0
  48. data/sig/sec_id/cfi/tables.rbs +68 -0
  49. data/sig/sec_id/cfi.rbs +56 -0
  50. data/sig/sec_id/cik.rbs +19 -0
  51. data/sig/sec_id/concerns/checkable.rbs +41 -0
  52. data/sig/sec_id/concerns/generatable.rbs +26 -0
  53. data/sig/sec_id/concerns/normalizable.rbs +34 -0
  54. data/sig/sec_id/concerns/validatable.rbs +42 -0
  55. data/sig/sec_id/cusip.rbs +29 -0
  56. data/sig/sec_id/deep_freeze.rbs +6 -0
  57. data/sig/sec_id/detector.rbs +35 -0
  58. data/sig/sec_id/dti.rbs +27 -0
  59. data/sig/sec_id/errors.rbs +28 -0
  60. data/sig/sec_id/figi.rbs +31 -0
  61. data/sig/sec_id/fisn.rbs +24 -0
  62. data/sig/sec_id/iban/country_rules.rbs +10 -0
  63. data/sig/sec_id/iban.rbs +54 -0
  64. data/sig/sec_id/isin.rbs +40 -0
  65. data/sig/sec_id/lei.rbs +33 -0
  66. data/sig/sec_id/occ.rbs +45 -0
  67. data/sig/sec_id/scanner.rbs +46 -0
  68. data/sig/sec_id/sedol.rbs +34 -0
  69. data/sig/sec_id/valoren.rbs +29 -0
  70. data/sig/sec_id/version.rbs +3 -0
  71. data/sig/sec_id/wkn.rbs +15 -0
  72. data/sig/sec_id.rbs +58 -0
  73. metadata +55 -7
  74. data/lib/sec_id/concerns/identifier_metadata.rb +0 -56
@@ -0,0 +1,46 @@
1
+ module SecID
2
+ # Immutable value object representing a matched identifier found in text.
3
+ class Match < Data
4
+ attr_reader type: Symbol
5
+ attr_reader raw: String
6
+ attr_reader range: Range[Integer]
7
+ attr_reader identifier: Base
8
+
9
+ def self.new: (type: Symbol, raw: String, range: Range[Integer], identifier: Base) -> instance
10
+ | (Symbol type, String raw, Range[Integer] range, Base identifier) -> instance
11
+ def self.[]: (type: Symbol, raw: String, range: Range[Integer], identifier: Base) -> instance
12
+ | (Symbol type, String raw, Range[Integer] range, Base identifier) -> instance
13
+ end
14
+
15
+ # Finds securities identifiers in freeform text.
16
+ #
17
+ # @api private
18
+ class Scanner
19
+ # Regex plumbing works with nilable MatchData throughout; typed `untyped` so the
20
+ # nil-unguarded capture access in the internal helpers stays free of noise.
21
+ CANDIDATE_RE: untyped
22
+
23
+ def initialize: (Array[singleton(Base)] identifier_list) -> void
24
+ def call: (String? text, ?classes: Array[singleton(Base)]?) { (Match) -> void } -> void
25
+ | (String? text, ?classes: Array[singleton(Base)]?) -> Enumerator[Match, void]
26
+ | (String? text, ?classes: Array[singleton(Base)]?) ?{ (Match) -> void } -> Enumerator[Match, void]?
27
+
28
+ private
29
+
30
+ def precompute: () -> void
31
+ def build_key_table: () -> void
32
+ def build_priority_table: () -> void
33
+ def scan_text: (String input, Array[singleton(Base)] target_classes) { (Match) -> void } -> void
34
+ def identify_candidate: (untyped match_data, Array[singleton(Base)] target_classes) -> Match?
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)?
37
+
38
+ @classes: Array[singleton(Base)]
39
+ @fisn_classes: Array[singleton(Base)]
40
+ @occ_classes: Array[singleton(Base)]
41
+ @simple_classes: Array[singleton(Base)]
42
+ @candidates_by_length: Hash[Integer, Array[singleton(Base)]]
43
+ @priority_for: Hash[singleton(Base), Array[Integer | Float | nil]]
44
+ @key_for: Hash[singleton(Base), Symbol]
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ module SecID
2
+ # Stock Exchange Daily Official List (SEDOL).
3
+ class SEDOL < Base
4
+ include Checkable
5
+ extend Checkable::ClassMethods
6
+
7
+ FULL_NAME: String
8
+ ID_LENGTH: Integer
9
+ EXAMPLE: String
10
+ VALID_CHARS_REGEX: ::Regexp
11
+ ID_REGEX: ::Regexp
12
+ CHARACTER_WEIGHTS: Array[Integer]
13
+ GENERATE_CHARSET: Array[String]
14
+ ISIN_COUNTRY_CODES: Set[String]
15
+
16
+ # Used unguarded as non-nil after validation (`#id_digits`); typed loosely so static
17
+ # Steep and runtime RBS::Test (which sees nil for invalid) agree.
18
+ def identifier: () -> untyped
19
+
20
+ def initialize: (SecID::input sedol) -> void
21
+ def to_isin: (?String country_code) -> ISIN
22
+ def calculate_check_digit: () -> Integer
23
+ def self.check_digit: (SecID::input id) -> Integer
24
+ private def self.generate_body: (Random random) -> String
25
+
26
+ private
27
+
28
+ def components: () -> Hash[Symbol, untyped]
29
+ def weighted_sum: () -> Integer
30
+ def id_digits: () -> Array[Integer]
31
+
32
+ @id_digits: Array[Integer]
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ module SecID
2
+ class Valoren < Base
3
+ FULL_NAME: String
4
+ # Typed loosely: `#normalized` does `ID_LENGTH.max` and Range#max is nilable in RBS.
5
+ ID_LENGTH: untyped
6
+ EXAMPLE: String
7
+ VALID_CHARS_REGEX: ::Regexp
8
+ ID_REGEX: ::Regexp
9
+ ISIN_COUNTRY_CODES: Set[String]
10
+
11
+ attr_reader padding: String?
12
+
13
+ # Used unguarded after validation (`#normalized`/`#to_pretty_s`); typed loosely so
14
+ # static Steep and runtime RBS::Test (which sees nil for invalid) agree.
15
+ def identifier: () -> untyped
16
+
17
+ def initialize: (SecID::input valoren) -> void
18
+ def to_pretty_s: () -> String?
19
+ def to_isin: (?String country_code) -> ISIN
20
+ def normalized: () -> String
21
+ def normalize!: () -> self
22
+ def to_s: () -> String
23
+ private def self.generate_body: (Random random) -> String
24
+
25
+ private
26
+
27
+ def components: () -> Hash[Symbol, untyped]
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module SecID
2
+ VERSION: String
3
+ end
@@ -0,0 +1,15 @@
1
+ module SecID
2
+ # Wertpapierkennnummer (WKN).
3
+ class WKN < Base
4
+ FULL_NAME: String
5
+ ID_LENGTH: Integer
6
+ EXAMPLE: String
7
+ VALID_CHARS_REGEX: ::Regexp
8
+ ID_REGEX: ::Regexp
9
+ GENERATE_CHARSET: Array[String]
10
+
11
+ def initialize: (SecID::input wkn) -> void
12
+ def to_isin: (?String country_code) -> ISIN
13
+ private def self.generate_body: (Random random) -> String
14
+ end
15
+ end
data/sig/sec_id.rbs ADDED
@@ -0,0 +1,58 @@
1
+ module SecID
2
+ # Base error class for all SecID errors.
3
+ class Error < StandardError
4
+ end
5
+
6
+ # Raised for invalid format, length, or characters.
7
+ class InvalidFormatError < Error
8
+ end
9
+
10
+ # Raised when the check digit does not match the calculated value.
11
+ class InvalidCheckDigitError < Error
12
+ end
13
+
14
+ # Raised for type-specific structural errors.
15
+ class InvalidStructureError < Error
16
+ end
17
+
18
+ # Raised when multiple identifier types match and on_ambiguous: :raise is used.
19
+ class AmbiguousMatchError < Error
20
+ end
21
+
22
+ def self.[]: (Symbol key) -> singleton(Base)
23
+ def self.identifiers: () -> Array[singleton(Base)]
24
+ def self.detect: (SecID::input str) -> Array[Symbol]
25
+ def self.valid?: (SecID::input str, ?types: Array[Symbol]?) -> bool
26
+ def self.extract: (String? text, ?types: Array[Symbol]?) -> Array[Match]
27
+ def self.scan: (String? text, ?types: Array[Symbol]?) { (Match) -> void } -> void
28
+ | (String? text, ?types: Array[Symbol]?) -> Enumerator[Match, void]
29
+ def self.explain: (SecID::input str, ?types: Array[Symbol]?) -> Hash[Symbol, untyped]
30
+ def self.parse: (SecID::input str, ?types: Array[Symbol]?, on_ambiguous: :all) -> Array[Base]
31
+ | (SecID::input str, ?types: Array[Symbol]?, ?on_ambiguous: (:first | :raise)) -> Base?
32
+ | (SecID::input str, ?types: Array[Symbol]?, ?on_ambiguous: Symbol) -> untyped
33
+ # Single broad return, not per-`on_ambiguous` overloads: the shared `parse!` body calls
34
+ # `result.empty?` on the `:all` path, so precise overloads would make Steep check `.empty?`
35
+ # against the `:first`/`:raise` arm's `Base?` and fail. Widening here keeps `result` untyped.
36
+ def self.parse!: (SecID::input str, ?types: Array[Symbol]?, ?on_ambiguous: Symbol) -> (Base | Array[Base])
37
+ def self.generate: (Symbol key, ?random: Random) -> Base
38
+
39
+ # Singleton-method privacy must be declared inline (`private def self.…`); a bare
40
+ # `private` section only affects instance methods, leaving these public.
41
+ private def self.register_identifier: (singleton(Base) klass) -> void
42
+ private def self.parse_any: (SecID::input str) -> Base?
43
+ private def self.parse_from: (SecID::input str, Array[Symbol] types) -> Base?
44
+ private def self.parse_strict: (SecID::input str, Array[Symbol]? types) -> Base?
45
+ private def self.parse_all: (SecID::input str, Array[Symbol]? types) -> Array[Base]
46
+ private def self.resolve_candidates: (SecID::input str, Array[Symbol]? types) -> Array[Symbol]
47
+ private def self.ambiguous_message: (SecID::input str, Array[Symbol] candidates) -> String
48
+ private def self.parse_error_message: (SecID::input str, Array[Symbol]? types) -> String
49
+ private def self.detector: () -> Detector
50
+ private def self.scanner: () -> Scanner
51
+ private def self.identifier_map: () -> Hash[Symbol, singleton(Base)]
52
+ private def self.identifier_list: () -> Array[singleton(Base)]
53
+
54
+ self.@identifier_map: Hash[Symbol, singleton(Base)]
55
+ self.@identifier_list: Array[singleton(Base)]
56
+ self.@detector: Detector?
57
+ self.@scanner: Scanner?
58
+ 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: 5.2.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -9,30 +9,43 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Validate, normalize, parse, and convert securities identifiers. Auto-detect
13
- identifier type from any string. Calculate and restore check digits. Supports ISIN,
14
- CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN, Valoren, CFI, and FISN.
12
+ description: Validate, normalize, parse, convert, generate, and classify securities
13
+ identifiers. Auto-detect identifier type from any string. Calculate and restore
14
+ check digits. Generate format-valid identifiers as test fixtures. Decode CFI codes
15
+ to full ISO 10962:2021 classifications. Includes an opt-in ActiveModel/Rails validator
16
+ that adds no runtime dependency. Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN,
17
+ CIK, OCC, WKN, Valoren, CFI, FISN, BIC, and DTI.
15
18
  email:
16
19
  - leonid@svyatov.ru
17
20
  executables: []
18
21
  extensions: []
19
22
  extra_rdoc_files: []
20
23
  files:
24
+ - ".yardopts"
21
25
  - CHANGELOG.md
22
26
  - LICENSE.txt
23
27
  - MIGRATION.md
24
28
  - README.md
25
29
  - lib/sec_id.rb
30
+ - lib/sec_id/active_model.rb
26
31
  - lib/sec_id/base.rb
32
+ - lib/sec_id/bic.rb
33
+ - lib/sec_id/bic/country_codes.rb
27
34
  - lib/sec_id/cei.rb
28
35
  - lib/sec_id/cfi.rb
36
+ - lib/sec_id/cfi/attribute_set.rb
37
+ - lib/sec_id/cfi/classification.rb
38
+ - lib/sec_id/cfi/field.rb
39
+ - lib/sec_id/cfi/tables.rb
29
40
  - lib/sec_id/cik.rb
30
41
  - lib/sec_id/concerns/checkable.rb
31
- - lib/sec_id/concerns/identifier_metadata.rb
42
+ - lib/sec_id/concerns/generatable.rb
32
43
  - lib/sec_id/concerns/normalizable.rb
33
44
  - lib/sec_id/concerns/validatable.rb
34
45
  - lib/sec_id/cusip.rb
46
+ - lib/sec_id/deep_freeze.rb
35
47
  - lib/sec_id/detector.rb
48
+ - lib/sec_id/dti.rb
36
49
  - lib/sec_id/errors.rb
37
50
  - lib/sec_id/figi.rb
38
51
  - lib/sec_id/fisn.rb
@@ -41,17 +54,52 @@ files:
41
54
  - lib/sec_id/isin.rb
42
55
  - lib/sec_id/lei.rb
43
56
  - lib/sec_id/occ.rb
57
+ - lib/sec_id/railtie.rb
44
58
  - lib/sec_id/scanner.rb
45
59
  - lib/sec_id/sedol.rb
46
60
  - lib/sec_id/valoren.rb
47
61
  - lib/sec_id/version.rb
48
62
  - lib/sec_id/wkn.rb
49
63
  - sec_id.gemspec
64
+ - sig/manifest.yaml
65
+ - sig/sec_id.rbs
66
+ - sig/sec_id/base.rbs
67
+ - sig/sec_id/bic.rbs
68
+ - sig/sec_id/bic/country_codes.rbs
69
+ - sig/sec_id/cei.rbs
70
+ - sig/sec_id/cfi.rbs
71
+ - sig/sec_id/cfi/attribute_set.rbs
72
+ - sig/sec_id/cfi/classification.rbs
73
+ - sig/sec_id/cfi/field.rbs
74
+ - sig/sec_id/cfi/tables.rbs
75
+ - sig/sec_id/cik.rbs
76
+ - sig/sec_id/concerns/checkable.rbs
77
+ - sig/sec_id/concerns/generatable.rbs
78
+ - sig/sec_id/concerns/normalizable.rbs
79
+ - sig/sec_id/concerns/validatable.rbs
80
+ - sig/sec_id/cusip.rbs
81
+ - sig/sec_id/deep_freeze.rbs
82
+ - sig/sec_id/detector.rbs
83
+ - sig/sec_id/dti.rbs
84
+ - sig/sec_id/errors.rbs
85
+ - sig/sec_id/figi.rbs
86
+ - sig/sec_id/fisn.rbs
87
+ - sig/sec_id/iban.rbs
88
+ - sig/sec_id/iban/country_rules.rbs
89
+ - sig/sec_id/isin.rbs
90
+ - sig/sec_id/lei.rbs
91
+ - sig/sec_id/occ.rbs
92
+ - sig/sec_id/scanner.rbs
93
+ - sig/sec_id/sedol.rbs
94
+ - sig/sec_id/valoren.rbs
95
+ - sig/sec_id/version.rbs
96
+ - sig/sec_id/wkn.rbs
50
97
  homepage: https://github.com/svyatov/sec_id
51
98
  licenses:
52
99
  - MIT
53
100
  metadata:
54
101
  rubygems_mfa_required: 'true'
102
+ documentation_uri: https://rubydoc.info/gems/sec_id
55
103
  source_code_uri: https://github.com/svyatov/sec_id
56
104
  changelog_uri: https://github.com/svyatov/sec_id/blob/main/CHANGELOG.md
57
105
  bug_tracker_uri: https://github.com/svyatov/sec_id/issues
@@ -69,8 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
117
  - !ruby/object:Gem::Version
70
118
  version: '0'
71
119
  requirements: []
72
- rubygems_version: 4.0.6
120
+ rubygems_version: 4.0.12
73
121
  specification_version: 4
74
122
  summary: A Ruby toolkit for securities identifiers — validate, parse, normalize, detect,
75
- and convert.
123
+ convert, generate, and classify.
76
124
  test_files: []
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SecID
4
- # Provides class-level metadata methods for identifier types.
5
- #
6
- # Including classes must define constants: `FULL_NAME`, `ID_LENGTH`, `EXAMPLE`.
7
- #
8
- # @example
9
- # SecID::ISIN.short_name #=> "ISIN"
10
- # SecID::ISIN.full_name #=> "International Securities Identification Number"
11
- # SecID::ISIN.has_check_digit? #=> true
12
- module IdentifierMetadata
13
- # @api private
14
- def self.included(base)
15
- base.extend(ClassMethods)
16
- end
17
-
18
- # Class methods added when IdentifierMetadata is included.
19
- module ClassMethods
20
- # Returns the unqualified class name (e.g. "ISIN", "CUSIP").
21
- #
22
- # @return [String]
23
- def short_name
24
- @short_name ||= name.split('::').last
25
- end
26
-
27
- # Returns the full human-readable standard name.
28
- #
29
- # @return [String]
30
- def full_name
31
- self::FULL_NAME
32
- end
33
-
34
- # Returns the fixed length or valid length range for identifiers of this type.
35
- #
36
- # @return [Integer, Range]
37
- def id_length
38
- self::ID_LENGTH
39
- end
40
-
41
- # Returns a representative valid identifier string.
42
- #
43
- # @return [String]
44
- def example
45
- self::EXAMPLE
46
- end
47
-
48
- # @return [Boolean] true if this identifier type uses a check digit
49
- def has_check_digit?
50
- return @has_check_digit if defined?(@has_check_digit)
51
-
52
- @has_check_digit = ancestors.include?(SecID::Checkable)
53
- end
54
- end
55
- end
56
- end