sec_id 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f9406f82fc8d468dff0ada3da32d7c9a3cf69d3f18e57eee32c146bf4d7057e
4
- data.tar.gz: 8b85da6d43ebd5c46daff708c0070693e16dd8f66e11a0682e4c54c0d46aeead
3
+ metadata.gz: 4f48ba902d7677a13e7d53d251473ba5a8b5c0379c06b09ceacda30df79a31d1
4
+ data.tar.gz: 1e54b0d518ca576ad853ff0d170026bbf9e0267d419c8a20209e5334a6d15276
5
5
  SHA512:
6
- metadata.gz: 867400988954ad03423b476920077c8d498ed2744cb1b863c4ab2959478609b6ce3cd8b424f4189d5d6220a9794fb3df9e3abe3c2b948273c41754e34a69f9de
7
- data.tar.gz: 0a357e424416fc0969a25fc492c05215a54dd5c8518978fb55830dbac47e656fa5b50e482e34e09b7d0cb21057bf906dda963350a826e934f77e107a727d28ca
6
+ metadata.gz: 10c3e1c32582736216141a539161a6d8aead43bdde27c4ad25ac95fd02ce4009959585248c24a740b0b8752554d36d31dfe2e3d28b1ec1ca92b9b8be2c69b1e7
7
+ data.tar.gz: 41a279f6df4045093a9c870c3ddd03845fe508d0d729bc145f59d96351b3580d51285fbae1aa0bef2c176b4c526d4452a5423796b348028ecbe63a9346430f12
data/.travis.yml CHANGED
@@ -5,7 +5,7 @@ language: ruby
5
5
 
6
6
  env:
7
7
  global:
8
- - CC_TEST_REPORTER_ID=9995f7b2aa6866b0427aef31d946ff1feded8f6e109eaf6ccb58e91c73d0bfeb
8
+ - CC_TEST_REPORTER_ID=48c33b2bc3ccdc6dafe9ed76482eca03cde47b922a7859074fc2be791247e5fd
9
9
 
10
10
  rvm:
11
11
  - 2.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0] - 2019-02-03
4
+
5
+ ### Added
6
+
7
+ - CUSIP numbers support: `SecId::CUSIP`
8
+ - CHANGELOG.md file added
9
+
10
+ ### Updated
11
+
12
+ - Char to digit conversion now uses precalculated tables instead of dynamic calculation for speed
13
+
14
+ ## [1.0.0] - 2017-10-25
15
+
16
+ ### Added
17
+
18
+ - ISIN numbers support: `SecId::ISIN`
data/README.md CHANGED
@@ -1,15 +1,18 @@
1
1
  # SecId
2
2
  [![Gem Version](https://badge.fury.io/rb/sec_id.svg)](https://badge.fury.io/rb/sec_id)
3
3
  [![Build Status](https://travis-ci.org/svyatov/sec_id.svg?branch=master)](https://travis-ci.org/svyatov/sec_id)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/maintainability)](https://codeclimate.com/github/svyatov/sec_id/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/test_coverage)](https://codeclimate.com/github/svyatov/sec_id/test_coverage)
4
6
 
5
7
  Validate security identification numbers with ease!
6
8
 
7
9
  Check-digit calculation is also available.
8
10
 
9
11
  Currently supported standards:
10
- [ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number)
12
+ [ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
13
+ [CUSIP](https://en.wikipedia.org/wiki/CUSIP)
11
14
 
12
- WIP: [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
15
+ WIP:
13
16
  [SEDOL](https://en.wikipedia.org/wiki/SEDOL),
14
17
  [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
15
18
 
@@ -18,7 +21,7 @@ WIP: [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
18
21
  Add this line to your application's Gemfile:
19
22
 
20
23
  ```ruby
21
- gem 'sec_id', '~> 1.0'
24
+ gem 'sec_id', '~> 1.1'
22
25
  ```
23
26
 
24
27
  And then execute:
@@ -114,6 +117,27 @@ isin.restore! # => 'US5949181045'
114
117
  isin.calculate_check_digit # => 5
115
118
  ```
116
119
 
120
+ ### SecId::CUSIP full example
121
+
122
+ ```ruby
123
+ # class level
124
+ SecId::CUSIP.valid?('594918104') # => true
125
+ SecId::CUSIP.valid_format?('59491810') # => true
126
+ SecId::CUSIP.restore!('59491810') # => '594918104'
127
+ SecId::CUSIP.check_digit('59491810') # => 5
128
+
129
+ # instance level
130
+ cusip = SecId::CUSIP.new('594918104')
131
+ cusip.cusip # => '594918104'
132
+ cusip.cusip6 # => '594918'
133
+ cusip.issue # => '10'
134
+ cusip.check_digit # => 4
135
+ cusip.valid? # => true
136
+ cusip.valid_format? # => true
137
+ cusip.restore! # => '594918104'
138
+ cusip.calculate_check_digit # => 4
139
+ ```
140
+
117
141
  ## Development
118
142
 
119
143
  After checking out the repo, run `bin/setup` to install dependencies.
data/lib/sec_id.rb CHANGED
@@ -4,6 +4,7 @@ require 'set'
4
4
  require 'sec_id/version'
5
5
  require 'sec_id/base'
6
6
  require 'sec_id/isin'
7
+ require 'sec_id/cusip'
7
8
 
8
9
  module SecId
9
10
  Error = Class.new(StandardError)
data/lib/sec_id/base.rb CHANGED
@@ -2,7 +2,27 @@
2
2
 
3
3
  module SecId
4
4
  class Base
5
- LETTERS = Set.new('A'..'Z').freeze
5
+ CHAR_TO_DIGITS = {
6
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
7
+ '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
8
+ 'A' => [1, 0], 'B' => [1, 1], 'C' => [1, 2], 'D' => [1, 3], 'E' => [1, 4],
9
+ 'F' => [1, 5], 'G' => [1, 6], 'H' => [1, 7], 'I' => [1, 8], 'J' => [1, 9],
10
+ 'K' => [2, 0], 'L' => [2, 1], 'M' => [2, 2], 'N' => [2, 3], 'O' => [2, 4],
11
+ 'P' => [2, 5], 'Q' => [2, 6], 'R' => [2, 7], 'S' => [2, 8], 'T' => [2, 9],
12
+ 'U' => [3, 0], 'V' => [3, 1], 'W' => [3, 2], 'X' => [3, 3], 'Y' => [3, 4], 'Z' => [3, 5],
13
+ '*' => [3, 6], '@' => [3, 7], '#' => [3, 8]
14
+ }.freeze
15
+
16
+ CHAR_TO_DIGIT = {
17
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
18
+ '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
19
+ 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14,
20
+ 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19,
21
+ 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24,
22
+ 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29,
23
+ 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35,
24
+ '*' => 36, '@' => 37, '#' => 38
25
+ }.freeze
6
26
 
7
27
  attr_reader :identifier, :check_digit
8
28
 
@@ -50,14 +70,15 @@ module SecId
50
70
  private
51
71
 
52
72
  def digitized_identifier
53
- @digitized_identifier ||= identifier.each_char.flat_map { |char| char_to_digits char }
73
+ raise NotImplementedError
54
74
  end
55
75
 
56
76
  def char_to_digits(char)
57
- return char.to_i unless LETTERS.include? char
77
+ CHAR_TO_DIGITS.fetch(char)
78
+ end
58
79
 
59
- number = char.to_i(36)
60
- [number / 10, number % 10]
80
+ def char_to_digit(char)
81
+ CHAR_TO_DIGIT.fetch(char)
61
82
  end
62
83
 
63
84
  def mod_10(sum)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecId
4
+ # https://en.wikipedia.org/wiki/CUSIP
5
+ class CUSIP < Base
6
+ ID_REGEX = /\A
7
+ (?<identifier>
8
+ (?<cusip6>[A-Z0-9]{5}[A-Z0-9*@#])
9
+ (?<issue>[A-Z0-9*@#]{2}))
10
+ (?<check_digit>\d)?
11
+ \z/x.freeze
12
+
13
+ VALID_COUNTRY_CODES_FOR_CONVERSION_TO_ISIN = %w[US CA].freeze
14
+
15
+ attr_reader :cusip, :cusip6, :issue
16
+
17
+ def initialize(cusip)
18
+ @cusip = cusip.to_s.strip.upcase
19
+ cusip_parts = @cusip.match(ID_REGEX) || {}
20
+
21
+ @identifier = cusip_parts[:identifier]
22
+ @cusip6 = cusip_parts[:cusip6]
23
+ @issue = cusip_parts[:issue]
24
+ @check_digit = cusip_parts[:check_digit].to_i if cusip_parts[:check_digit]
25
+ end
26
+
27
+ def valid?
28
+ return false unless valid_format?
29
+
30
+ check_digit == calculate_check_digit
31
+ end
32
+
33
+ def valid_format?
34
+ identifier ? true : false
35
+ end
36
+
37
+ def restore!
38
+ @check_digit = calculate_check_digit
39
+ @cusip = to_s
40
+ end
41
+
42
+ def calculate_check_digit
43
+ return mod_10(modified_luhn_sum) if valid_format?
44
+
45
+ raise InvalidFormatError, "CUSIP '#{cusip}' is invalid and check-digit cannot be calculated!"
46
+ end
47
+
48
+ private
49
+
50
+ # https://en.wikipedia.org/wiki/Luhn_algorithm
51
+ def modified_luhn_sum
52
+ sum = 0
53
+
54
+ digitized_identifier.reverse.each_slice(2) do |even, odd|
55
+ double_even = (even || 0) * 2
56
+ double_even -= 9 if double_even > 9
57
+ sum += div_10_mod_10(double_even) + div_10_mod_10(odd || 0)
58
+ end
59
+
60
+ sum
61
+ end
62
+
63
+ def digitized_identifier
64
+ @digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
65
+ end
66
+
67
+ def div_10_mod_10(number)
68
+ (number / 10) + (number % 10)
69
+ end
70
+ end
71
+ end
data/lib/sec_id/isin.rb CHANGED
@@ -38,9 +38,9 @@ module SecId
38
38
  end
39
39
 
40
40
  def calculate_check_digit
41
- raise InvalidFormatError, "ISIN '#{isin}' is invalid and check-digit cannot be calculated!" unless valid_format?
41
+ return mod_10(luhn_sum) if valid_format?
42
42
 
43
- mod_10 luhn_sum
43
+ raise InvalidFormatError, "ISIN '#{isin}' is invalid and check-digit cannot be calculated!"
44
44
  end
45
45
 
46
46
  private
@@ -57,5 +57,9 @@ module SecId
57
57
 
58
58
  sum
59
59
  end
60
+
61
+ def digitized_identifier
62
+ @digitized_identifier ||= identifier.each_char.flat_map(&method(:char_to_digits))
63
+ end
60
64
  end
61
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecId
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  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: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -106,6 +106,7 @@ files:
106
106
  - ".rspec"
107
107
  - ".rubocop.yml"
108
108
  - ".travis.yml"
109
+ - CHANGELOG.md
109
110
  - Gemfile
110
111
  - LICENSE.txt
111
112
  - README.md
@@ -114,6 +115,7 @@ files:
114
115
  - bin/setup
115
116
  - lib/sec_id.rb
116
117
  - lib/sec_id/base.rb
118
+ - lib/sec_id/cusip.rb
117
119
  - lib/sec_id/isin.rb
118
120
  - lib/sec_id/version.rb
119
121
  - sec_id.gemspec