aamva 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.simplecov +3 -0
- data/.travis.yml +14 -2
- data/README.md +4 -10
- data/aamva.gemspec +4 -2
- data/bin/console +2 -9
- data/bin/setup +1 -2
- data/exe/aamva +1 -1
- data/lib/aamva.rb +29 -16
- data/lib/aamva/calculator.rb +35 -0
- data/lib/aamva/cli.rb +10 -4
- data/lib/aamva/data.rb +11 -0
- data/lib/aamva/data/info/2016.yml +209 -0
- data/lib/aamva/decoder.rb +76 -0
- data/lib/aamva/encoder.rb +59 -0
- data/lib/aamva/factory.rb +122 -0
- data/lib/aamva/generator.rb +53 -21
- data/lib/aamva/header.rb +17 -0
- data/lib/aamva/standard.rb +53 -0
- data/lib/aamva/subfile.rb +15 -0
- data/lib/aamva/subfile_designator.rb +17 -0
- data/lib/aamva/utils.rb +7 -0
- data/lib/aamva/validator.rb +8 -60
- data/lib/aamva/version.rb +2 -2
- metadata +49 -11
- data/lib/aamva/card.rb +0 -6
- data/lib/aamva/data_element.rb +0 -9
- data/lib/aamva/data_element/base.rb +0 -8
- data/lib/aamva/data_element/day.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 523077b5eb76178e4058d12f2d6d2310b48b42a4
|
4
|
+
data.tar.gz: 9e44dc38c7ec45de75e65985473f66223615e17c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81d9a57d3d2265519b5cd39ed09eb15c9b521e889a745d1414d60e83f1c8d62362402777e74644d4b1835c0c8eb5a75658cc1fb535b998de6b5ccd7147da7dcc
|
7
|
+
data.tar.gz: ff50896a726449681cabc6db9c0b6edf7225464da511299acdf746f1ca5f796502d912ce904f6c7243600a70925d3304a050b2ce2bbd71aeca062e28a6aefada
|
data/.rspec
CHANGED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
aamva
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.1
|
data/.simplecov
ADDED
data/.travis.yml
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
-
|
1
|
+
env:
|
2
|
+
global:
|
3
|
+
- CC_TEST_REPORTER_ID=0924c08910b176edb9ebc474d67645038730cb6d88da74343c6b6e526af3e88f
|
2
4
|
language: ruby
|
5
|
+
sudo: false
|
3
6
|
rvm:
|
4
7
|
- 2.4.1
|
5
|
-
|
8
|
+
before_script:
|
9
|
+
- gem install bundler -v 1.16.1
|
10
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
11
|
+
- chmod +x ./cc-test-reporter
|
12
|
+
- ./cc-test-reporter before-build
|
13
|
+
script:
|
14
|
+
- bundle exec rspec
|
15
|
+
after_script:
|
16
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
17
|
+
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# AAMVA
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
[![Build Status](https://travis-ci.org/kyledecot/aamva.svg?branch=master)](https://travis-ci.org/kyledecot/aamva)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/kyledecot/aamva/badges/gpa.svg)](https://codeclimate.com/github/kyledecot/aamva)
|
6
5
|
|
7
6
|
## Installation
|
8
7
|
|
@@ -20,10 +19,6 @@ Or install it yourself as:
|
|
20
19
|
|
21
20
|
$ gem install aamva
|
22
21
|
|
23
|
-
## Usage
|
24
|
-
|
25
|
-
TODO: Write usage instructions here
|
26
|
-
|
27
22
|
## Development
|
28
23
|
|
29
24
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -32,10 +27,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
27
|
|
33
28
|
## Contributing
|
34
29
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
30
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kyledecot/aamva.
|
36
31
|
|
37
32
|
|
38
33
|
## License
|
39
34
|
|
40
35
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
-
|
data/aamva.gemspec
CHANGED
@@ -7,7 +7,7 @@ require 'aamva/version'
|
|
7
7
|
|
8
8
|
Gem::Specification.new do |spec|
|
9
9
|
spec.name = 'aamva'
|
10
|
-
spec.version =
|
10
|
+
spec.version = AAMVA::VERSION
|
11
11
|
spec.authors = ['Kyle Decot']
|
12
12
|
spec.email = ['kyle@joinroot.com']
|
13
13
|
|
@@ -23,8 +23,10 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
-
spec.add_dependency 'gli', '~> 2.16'
|
27
26
|
spec.add_dependency 'faker'
|
27
|
+
spec.add_dependency 'gli', '~> 2.16'
|
28
|
+
spec.add_dependency 'pdf417', '~> 1.0.0'
|
29
|
+
spec.add_dependency 'chunky_png', '~> 1.3.10'
|
28
30
|
|
29
31
|
spec.add_development_dependency 'bundler', '~> 1.14'
|
30
32
|
spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
|
data/bin/console
CHANGED
@@ -3,13 +3,6 @@
|
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'aamva'
|
6
|
+
require "pry"
|
6
7
|
|
7
|
-
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
-
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
# require "pry"
|
12
|
-
# Pry.start
|
13
|
-
|
14
|
-
require 'irb'
|
15
|
-
IRB.start(__FILE__)
|
8
|
+
Pry.start
|
data/bin/setup
CHANGED
data/exe/aamva
CHANGED
data/lib/aamva.rb
CHANGED
@@ -1,26 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aamva/version'
|
4
|
-
require 'aamva/card'
|
5
|
-
require 'aamva/data_element'
|
6
4
|
require 'aamva/cli'
|
7
5
|
require 'aamva/generator'
|
8
6
|
require 'aamva/validator'
|
7
|
+
require 'aamva/decoder'
|
8
|
+
require 'aamva/standard'
|
9
|
+
require 'aamva/encoder'
|
10
|
+
require 'aamva/calculator'
|
11
|
+
require 'aamva/header'
|
12
|
+
require 'aamva/subfile_designator'
|
13
|
+
require 'aamva/subfile'
|
14
|
+
require 'aamva/data'
|
15
|
+
require 'aamva/factory'
|
16
|
+
require 'aamva/utils'
|
9
17
|
|
10
|
-
module
|
11
|
-
|
12
|
-
|
18
|
+
module AAMVA
|
19
|
+
UPPER_ALPHA_CHARACTERS = ('A'..'Z').to_a
|
20
|
+
LOWER_ALPHA_CHARACTERS = ('a'..'z').to_a
|
21
|
+
ALPHA_CHARACTERS = UPPER_ALPHA_CHARACTERS + LOWER_ALPHA_CHARACTERS
|
13
22
|
|
14
|
-
|
15
|
-
'USA' => 'USA',
|
16
|
-
'CAN' => 'CAN'
|
17
|
-
}
|
23
|
+
LENGTH_UNITS = %w[in cm].freeze
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
DATE_FORMATS = {
|
26
|
+
can: '%Y%m%d',
|
27
|
+
usa: '%m%d%Y'
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
REQUIRED_DATA_ELEMENTS = %i[
|
31
|
+
dac
|
32
|
+
dbc
|
33
|
+
day
|
34
|
+
ddf
|
35
|
+
ddg
|
36
|
+
].freeze
|
24
37
|
|
25
38
|
DAY_MAPPING = {
|
26
39
|
'BLK' => 'Black',
|
@@ -30,6 +43,6 @@ module Aamva
|
|
30
43
|
'GRN' => 'Green',
|
31
44
|
'HAZ' => 'Hazel',
|
32
45
|
'MAR' => 'Maroon PNK Pink',
|
33
|
-
'DIC' => 'Dichromatic UNK Unknown'
|
34
|
-
}
|
46
|
+
'DIC' => 'Dichromatic UNK Unknown'
|
47
|
+
}.freeze
|
35
48
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AAMVA
|
2
|
+
class Calculator
|
3
|
+
def self.subfile(type:, data_elements:, data_element_separator:, segment_terminator:)
|
4
|
+
joined_pairs = data_elements
|
5
|
+
.map { |k, v| "#{k}#{v}" }
|
6
|
+
.join(data_element_separator)
|
7
|
+
|
8
|
+
"#{type}#{joined_pairs}#{segment_terminator}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.subfiles(subfiles:, data_element_separator:, segment_terminator:)
|
12
|
+
subfiles.map do |type, subfile|
|
13
|
+
Calculator.subfile(
|
14
|
+
type: type,
|
15
|
+
data_elements: subfile.data_elements,
|
16
|
+
data_element_separator: data_element_separator,
|
17
|
+
segment_terminator: segment_terminator
|
18
|
+
)
|
19
|
+
end.join("")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.subfile_offset
|
23
|
+
"TODO"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.subfile_length(type:, data_elements:, data_element_separator:, segment_terminator:)
|
27
|
+
subfile(
|
28
|
+
type: type,
|
29
|
+
data_elements: data_elements,
|
30
|
+
data_element_separator: data_element_separator,
|
31
|
+
segment_terminator: segment_terminator
|
32
|
+
).length
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/aamva/cli.rb
CHANGED
@@ -3,16 +3,22 @@
|
|
3
3
|
require 'gli'
|
4
4
|
require 'aamva/version'
|
5
5
|
|
6
|
-
module
|
6
|
+
module AAMVA
|
7
7
|
class CLI
|
8
8
|
extend GLI::App
|
9
9
|
|
10
|
-
version
|
10
|
+
version AAMVA::VERSION
|
11
11
|
|
12
|
-
command [
|
12
|
+
command ['encode'] do |c|
|
13
13
|
c.action do |_global, _options, _args|
|
14
|
+
standard = AAMVA::Standard.new("2016")
|
15
|
+
data = AAMVA::Generator.new(standard).data
|
16
|
+
encoder = AAMVA::Encoder.new(
|
17
|
+
standard: standard,
|
18
|
+
data: data,
|
19
|
+
)
|
14
20
|
|
15
|
-
puts
|
21
|
+
puts encoder.png
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
data/lib/aamva/data.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
compliance_indicator: "@"
|
2
|
+
record_separator: "\u001E"
|
3
|
+
data_element_separator: "\u000A"
|
4
|
+
segment_terminator: "\u000D"
|
5
|
+
aamva_version_number: "08"
|
6
|
+
file_type: "ANSI "
|
7
|
+
subfile_designator_regexp: !ruby/regexp '/(?<subfile_designator>(?<subfile_type>DL|ID|Z[A-Z])(?<offset>\d{4})(?<length>\d{4})+)/'
|
8
|
+
header_regexp: !ruby/regexp '/@\n\u001E\rANSI (?<issuer_identification_number>\d{6})(?<aamva_version_number>\d{2})(?<jurisdiction_version_number>\d{2})(?<number_of_entries>\d{2})/'
|
9
|
+
truncation_indicators:
|
10
|
+
- T
|
11
|
+
- U
|
12
|
+
- N
|
13
|
+
|
14
|
+
header:
|
15
|
+
issuer_identification_number:
|
16
|
+
factory:
|
17
|
+
type: string
|
18
|
+
options:
|
19
|
+
value: '123456'
|
20
|
+
jurisdiction_version_number:
|
21
|
+
factory:
|
22
|
+
type: range
|
23
|
+
options:
|
24
|
+
start: '00'
|
25
|
+
end: '99'
|
26
|
+
data_elements:
|
27
|
+
dbb:
|
28
|
+
required: true
|
29
|
+
data_element: Date of Birth
|
30
|
+
definition: Date on which the cardholder was born. (MMDDCCYY for U.S., CCYYMMDD for Canada)
|
31
|
+
regexp: \A[\d+]{8,8}\z
|
32
|
+
factory:
|
33
|
+
type: date
|
34
|
+
dba:
|
35
|
+
required: true
|
36
|
+
data_element: Document Expiration Date
|
37
|
+
definition: Date on which the driving and identification privileges granted by the document are no longer valid. (MMDDCCYY for U.S., CCYYMMDD for Canada)
|
38
|
+
regexp: \A[\d+]{8,8}\z
|
39
|
+
factory:
|
40
|
+
type: date
|
41
|
+
dbd:
|
42
|
+
required: true
|
43
|
+
data_element: Document Issue Date
|
44
|
+
definition: Date on which the document was issued. (MMDDCCYY for U.S., CCYYMMDD for Canada)
|
45
|
+
regexp: \A[\d+]{8,8}\z
|
46
|
+
factory:
|
47
|
+
type: date
|
48
|
+
daj:
|
49
|
+
required: true
|
50
|
+
data_element: Address – Jurisdiction Code
|
51
|
+
definition: State portion of the cardholder address.
|
52
|
+
regexp: \A[\w]{2}\z
|
53
|
+
factory:
|
54
|
+
type: state
|
55
|
+
dcf:
|
56
|
+
required: true
|
57
|
+
data_element: Document Discriminator
|
58
|
+
definition: Number must uniquely identify a particular document issued to that customer from others that may have been issued in the past. This number may serve multiple purposes of document discrimination, audit information number, and/or inventory control.
|
59
|
+
regexp: \A[\d\w]{1,25}\z
|
60
|
+
factory:
|
61
|
+
type: string
|
62
|
+
options:
|
63
|
+
truncate:
|
64
|
+
length: 20
|
65
|
+
dai:
|
66
|
+
required: true
|
67
|
+
data_element: Address – City
|
68
|
+
definition: City portion of the cardholder address.
|
69
|
+
regexp: \A[\w ]{1,20}\z
|
70
|
+
factory:
|
71
|
+
type: city
|
72
|
+
options:
|
73
|
+
length: 20
|
74
|
+
dak:
|
75
|
+
required: true
|
76
|
+
data_element: Address – Postal Code
|
77
|
+
definition: Postal code portion of the cardholder address in the U.S. and Canada. If the trailing portion of the postal code in the U.S. is not known, zeros will be used to fill the trailing set of numbers up to nine (9) digits.
|
78
|
+
regexp: \A[\d\w]{11}\z
|
79
|
+
factory:
|
80
|
+
type: postal_code
|
81
|
+
dcb:
|
82
|
+
required: true
|
83
|
+
data_element: Jurisdiction- specific restriction codes
|
84
|
+
definition: Jurisdiction-specific codes that represent restrictions to driving privileges (such as airbrakes, automatic transmission, daylight only, etc.).
|
85
|
+
regexp: \A[\d\w]{1,12}\z
|
86
|
+
factory:
|
87
|
+
type: restriction_codes
|
88
|
+
dau:
|
89
|
+
required: true
|
90
|
+
data_element: Physical Description – Height
|
91
|
+
definition: |
|
92
|
+
Height of cardholder.
|
93
|
+
Inches (in): number of inches followed by " in" ex. 6'1'' = "073 in"
|
94
|
+
Centimeters (cm): number of centimeters followed by " cm"
|
95
|
+
ex. 181 centimeters="181 cm"
|
96
|
+
regexp: \A[\d]{3} (in|cm)\z
|
97
|
+
factory:
|
98
|
+
type: height
|
99
|
+
dcd:
|
100
|
+
required: true
|
101
|
+
data_element: Jurisdiction- specific endorsement codes
|
102
|
+
definition: Jurisdiction-specific codes that represent additional privileges granted to the cardholder beyond the vehicle class (such as transportation of passengers, hazardous materials, operation of motorcycles, etc.).
|
103
|
+
regexp: \A[\d\w]{1,5}\z
|
104
|
+
factory:
|
105
|
+
type: endorsement_codes
|
106
|
+
dad:
|
107
|
+
required: true
|
108
|
+
data_element: Customer Middle Name(s)
|
109
|
+
definition: Middle name(s) of the cardholder. In the case of multiple middle names they shall be separated by a comma “,”.
|
110
|
+
regexp: \A[\w\,]{1,40}\z
|
111
|
+
factory:
|
112
|
+
type: first_name
|
113
|
+
options:
|
114
|
+
length: 40
|
115
|
+
dcs:
|
116
|
+
required: true
|
117
|
+
data_element: Customer Family Name
|
118
|
+
definition: Family name of the cardholder. (Family name is sometimes also called “last name” or “surname.”) Collect full name for record, print as many characters as possible on portrait side of DL/ID.
|
119
|
+
regexp: \A[\w\,\']{1,40}\z
|
120
|
+
factory:
|
121
|
+
type: last_name
|
122
|
+
options:
|
123
|
+
length: 40
|
124
|
+
daq:
|
125
|
+
required: true
|
126
|
+
data_element: Customer ID Number
|
127
|
+
definition: The number assigned or calculated by the issuing authority.
|
128
|
+
regexp: \A[\d\w]{1,25}\z
|
129
|
+
factory:
|
130
|
+
type: customer_id_number
|
131
|
+
ddg:
|
132
|
+
required: true
|
133
|
+
data_element: Middle name truncation
|
134
|
+
definition: A code that indicates whether a field has been truncated (T), has not been truncated (N), or – unknown whether truncated (U).
|
135
|
+
regexp: (T|N|U)
|
136
|
+
factory:
|
137
|
+
type: enum
|
138
|
+
options:
|
139
|
+
values: ["T", "N", "U"]
|
140
|
+
dac:
|
141
|
+
required: true
|
142
|
+
data_element: Customer First Name
|
143
|
+
definition: First name of the cardholder.
|
144
|
+
regexp: \A[\d\w]{1,40}\z
|
145
|
+
factory:
|
146
|
+
type: first_name
|
147
|
+
options:
|
148
|
+
length: 40
|
149
|
+
ddf:
|
150
|
+
required: true
|
151
|
+
data_element: First name truncation
|
152
|
+
definition: A code that indicates whether a field has been truncated (T), has not been truncated (N), or – unknown whether truncated (U).
|
153
|
+
regexp: (T|N|U)
|
154
|
+
factory:
|
155
|
+
type: enum
|
156
|
+
options:
|
157
|
+
values: ["T", "N", "U"]
|
158
|
+
dbc:
|
159
|
+
required: true
|
160
|
+
data_element: Physical Description – Sex
|
161
|
+
definition: Gender of the cardholder. 1 = male, 2 = female, 9 = not specified.
|
162
|
+
regexp: (1|2|9)
|
163
|
+
factory:
|
164
|
+
type: enum
|
165
|
+
options:
|
166
|
+
values: ["1", "2", "9"]
|
167
|
+
day:
|
168
|
+
required: true
|
169
|
+
data_element: Physical Description – Eye Color
|
170
|
+
definition: Color of cardholder's eyes. (ANSI D-20 codes)
|
171
|
+
regexp: (BLK|BLU|BRO|GRY|GRN|HAZ|MAR|DIC)
|
172
|
+
factory:
|
173
|
+
type: enum
|
174
|
+
options:
|
175
|
+
values: ["BLK", "BLU", "BRO", "GRY", "GRN", "HAZ", "MAR", "DIC"]
|
176
|
+
dcg:
|
177
|
+
required: true
|
178
|
+
data_element: Country Identification
|
179
|
+
definition: Country in which DL/ID is issued. U.S. = USA, Canada = CAN.
|
180
|
+
regexp: \A(USA|CAN)\z
|
181
|
+
factory:
|
182
|
+
type: enum
|
183
|
+
options:
|
184
|
+
values: ["USA", "CAN"]
|
185
|
+
dde:
|
186
|
+
required: true
|
187
|
+
data_element: Family name truncation
|
188
|
+
definition: A code that indicates whether a field has been truncated (T), has not been truncated (N), or – unknown whether truncated (U).
|
189
|
+
regexp: (T|N|U)
|
190
|
+
factory:
|
191
|
+
type: enum
|
192
|
+
options:
|
193
|
+
values: ["T", "N", "U"]
|
194
|
+
dca:
|
195
|
+
required: true
|
196
|
+
data_element: Jurisdiction- specific vehicle class
|
197
|
+
definition: Jurisdiction-specific vehicle class / group code, designating the type of vehicle the cardholder has privilege to drive.
|
198
|
+
regexp: \A[\d\w]{1,6}\z
|
199
|
+
factory:
|
200
|
+
type: vehicle_class
|
201
|
+
dag:
|
202
|
+
required: true
|
203
|
+
data_element: Address – Street 1
|
204
|
+
definition: Street portion of the cardholder address.
|
205
|
+
regexp: \A[A-Za-z\d["!", "\"", "#", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "\/", ":", ";", "<", "=", ">", "?", "[", "\\", "]", "^", "_", "@", " "]]{1,35}\z
|
206
|
+
factory:
|
207
|
+
type: street_address
|
208
|
+
options:
|
209
|
+
length: 35
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AAMVA
|
4
|
+
class Decoder
|
5
|
+
attr_reader :standard, :barcode
|
6
|
+
|
7
|
+
def initialize(standard, barcode)
|
8
|
+
@standard = standard
|
9
|
+
@barcode = barcode
|
10
|
+
end
|
11
|
+
|
12
|
+
def data
|
13
|
+
@data ||= Data.new(
|
14
|
+
header: header,
|
15
|
+
subfile_designators: subfile_designators,
|
16
|
+
subfiles: subfiles
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def data_elements(subfile_designator)
|
23
|
+
@barcode
|
24
|
+
.byteslice(subfile_designator.offset.to_i, subfile_designator.length.to_i)
|
25
|
+
.slice((subfile_designator.type).length..-1)
|
26
|
+
.chomp(standard["segment_terminator"])
|
27
|
+
.split(standard["data_element_separator"])
|
28
|
+
.map { |r| [r[0, 3], r[3..-1]] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def header_match
|
32
|
+
@barcode.match(standard["header_regexp"])
|
33
|
+
end
|
34
|
+
|
35
|
+
def header
|
36
|
+
@header ||= Header.new(
|
37
|
+
number_of_entries: number_of_entries,
|
38
|
+
jurisdiction_version_number: jurisdiction_version_number,
|
39
|
+
issuer_identification_number: issuer_identification_number
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def subfiles
|
44
|
+
subfile_designators.map do |subfile_designator|
|
45
|
+
Subfile.new(
|
46
|
+
type: subfile_designator.type,
|
47
|
+
data_elements: data_elements(subfile_designator)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def subfile_designators
|
53
|
+
@barcode
|
54
|
+
.scan(@standard["subfile_designator_regexp"])
|
55
|
+
.map do |_, type, offset, length|
|
56
|
+
SubfileDesignator.new(
|
57
|
+
type: type,
|
58
|
+
offset: offset,
|
59
|
+
length: length
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def issuer_identification_number
|
65
|
+
header_match&.[]("issuer_identification_number")
|
66
|
+
end
|
67
|
+
|
68
|
+
def jurisdiction_version_number
|
69
|
+
header_match&.[]("jurisdiction_version_number")
|
70
|
+
end
|
71
|
+
|
72
|
+
def number_of_entries
|
73
|
+
header_match&.[]("number_of_entries")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module AAMVA
|
2
|
+
class Encoder
|
3
|
+
attr_reader :standard, :data
|
4
|
+
|
5
|
+
def initialize(standard:, data:)
|
6
|
+
@standard = standard
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def string
|
11
|
+
"#{header}#{subfile_designators}#{subfiles}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def pdf417
|
15
|
+
@pdf417 ||= begin
|
16
|
+
require "pdf417"
|
17
|
+
|
18
|
+
PDF417.new(string)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def png
|
23
|
+
@png ||= pdf417.to_chunky_png
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def header
|
29
|
+
@header ||= begin
|
30
|
+
[
|
31
|
+
standard.spec.fetch("compliance_indicator"),
|
32
|
+
standard.spec.fetch("data_element_separator"),
|
33
|
+
standard.spec.fetch("record_separator"),
|
34
|
+
standard.spec.fetch("segment_terminator"),
|
35
|
+
standard.spec.fetch("file_type"),
|
36
|
+
data.header.issuer_identification_number,
|
37
|
+
standard.spec.fetch("aamva_version_number"),
|
38
|
+
data.header.jurisdiction_version_number,
|
39
|
+
data.header.number_of_entries
|
40
|
+
].join("")
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def subfile_designators
|
46
|
+
@subfile_designators ||= data.subfile_designators.map do |designator|
|
47
|
+
"#{designator.type}#{designator.offset}#{designator.length}"
|
48
|
+
end.join("")
|
49
|
+
end
|
50
|
+
|
51
|
+
def subfiles
|
52
|
+
@subfiles ||= Calculator.subfiles(
|
53
|
+
subfiles: data.subfiles,
|
54
|
+
data_element_separator: standard.spec.fetch("data_element_separator"),
|
55
|
+
segment_terminator: standard.spec.fetch("segment_terminator")
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'faker'
|
2
|
+
|
3
|
+
module AAMVA
|
4
|
+
class Factory
|
5
|
+
def self.build(type, options = {})
|
6
|
+
if respond_to?(type)
|
7
|
+
send(type, options)
|
8
|
+
else
|
9
|
+
raise "Unsupported Factory Type: #{type}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.enum(options = {})
|
16
|
+
options.fetch("values").sample
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.range(options = {})
|
20
|
+
(options.fetch('start')..options.fetch('end')).to_a.sample
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.string(options = {})
|
24
|
+
value = options.fetch('value') do
|
25
|
+
random_string(options['truncate']['length'])
|
26
|
+
end
|
27
|
+
value = Utils.truncate(value, options['truncate']) if options.key?('truncate')
|
28
|
+
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.restriction_codes(options = {})
|
33
|
+
defaults = {
|
34
|
+
'value' => UPPER_ALPHA_CHARACTERS.sample(12).join('')
|
35
|
+
}
|
36
|
+
|
37
|
+
string(defaults.merge(options))
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.street_address(options = {})
|
41
|
+
defaults = {
|
42
|
+
'value' => Faker::Address.street_address
|
43
|
+
}
|
44
|
+
|
45
|
+
string(defaults.merge(options))
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.last_name(options = {})
|
49
|
+
defaults = {
|
50
|
+
'value' => Faker::Name.last_name
|
51
|
+
}
|
52
|
+
|
53
|
+
string(defaults.merge(options))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.postal_code(options = {})
|
57
|
+
defaults = {
|
58
|
+
'value' => random_string(11)
|
59
|
+
}
|
60
|
+
|
61
|
+
string(defaults.merge(options))
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.customer_id_number(options = {})
|
65
|
+
defaults = {
|
66
|
+
'value' => random_string(25)
|
67
|
+
}
|
68
|
+
|
69
|
+
string(defaults.merge(options))
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.height(options = {})
|
73
|
+
height = ('000'..'999').to_a.sample
|
74
|
+
|
75
|
+
"#{height} #{LENGTH_UNITS.first}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.first_name(options = {})
|
79
|
+
defaults = {
|
80
|
+
'value' => Faker::Name.first_name
|
81
|
+
}
|
82
|
+
|
83
|
+
string(defaults.merge(options))
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.date(options = {})
|
87
|
+
Faker::Date.backward.strftime(DATE_FORMATS[:usa])
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.endorsement_codes(options = {})
|
91
|
+
UPPER_ALPHA_CHARACTERS.sample(5).join('')
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.city(options = {})
|
95
|
+
defaults = {
|
96
|
+
'value' => Faker::Address.city
|
97
|
+
}
|
98
|
+
|
99
|
+
string(defaults.merge(options))
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.state(options = {})
|
103
|
+
defaults = {
|
104
|
+
'value' => Faker::Address.state_abbr
|
105
|
+
}
|
106
|
+
|
107
|
+
string(defaults.merge(options))
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.vehicle_class(options = {})
|
111
|
+
chars = ('A'..'Z').to_a + ('0'..'9').to_a
|
112
|
+
|
113
|
+
chars.sample(6).join('')
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.random_string(length)
|
117
|
+
chars = ('A'..'Z').to_a + ('0'..'9').to_a
|
118
|
+
|
119
|
+
chars.sample(length).join('')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/aamva/generator.rb
CHANGED
@@ -1,39 +1,71 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module AAMVA
|
4
4
|
class Generator
|
5
|
-
|
6
|
-
|
5
|
+
attr_reader :standard
|
6
|
+
|
7
|
+
def initialize(standard)
|
8
|
+
@standard = standard
|
7
9
|
end
|
8
10
|
|
9
|
-
|
11
|
+
def data
|
12
|
+
@data ||= Data.new(
|
13
|
+
header: header,
|
14
|
+
subfile_designators: subfile_designators,
|
15
|
+
subfiles: subfiles
|
16
|
+
)
|
17
|
+
end
|
10
18
|
|
11
|
-
|
12
|
-
chars = ('A'..'Z').to_a + (('0'..'9').to_a)
|
19
|
+
private
|
13
20
|
|
14
|
-
|
21
|
+
def header
|
22
|
+
@header ||= Header.new(
|
23
|
+
issuer_identification_number: issuer_identification_number,
|
24
|
+
jurisdiction_version_number: jurisdiction_version_number,
|
25
|
+
number_of_entries: subfiles.size
|
26
|
+
)
|
15
27
|
end
|
16
28
|
|
17
|
-
|
29
|
+
def subfile_designators
|
30
|
+
@subfile_designators ||= subfiles.map do |_, subfile|
|
31
|
+
length = Calculator.subfile_length(
|
32
|
+
type: subfile.type,
|
33
|
+
data_elements: subfile.data_elements,
|
34
|
+
data_element_separator: @standard["data_element_separator"],
|
35
|
+
segment_terminator: @standard["segment_terminator"]
|
36
|
+
)
|
18
37
|
|
19
|
-
|
20
|
-
DAY_MAPPING.keys.sample
|
21
|
-
end
|
38
|
+
offset = Calculator.subfile_offset
|
22
39
|
|
23
|
-
|
24
|
-
|
40
|
+
SubfileDesignator.new(
|
41
|
+
type: subfile.type,
|
42
|
+
length: length,
|
43
|
+
offset: offset
|
44
|
+
)
|
45
|
+
end
|
25
46
|
end
|
26
47
|
|
27
|
-
|
48
|
+
def subfiles
|
49
|
+
@subfiles ||= begin
|
50
|
+
data_elements = Hash[@standard.required_data_elements.map do |type|
|
51
|
+
[type.upcase, send(type)]
|
52
|
+
end]
|
28
53
|
|
29
|
-
|
30
|
-
|
54
|
+
{
|
55
|
+
"DL" => Subfile.new(
|
56
|
+
type: "DL",
|
57
|
+
data_elements: data_elements
|
58
|
+
)
|
59
|
+
}
|
60
|
+
end
|
31
61
|
end
|
32
62
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
63
|
+
def method_missing(name, *args)
|
64
|
+
if factory = @standard.factory(name)
|
65
|
+
Factory.build(factory[:type], factory[:options])
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
37
69
|
end
|
38
70
|
end
|
39
71
|
end
|
data/lib/aamva/header.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module AAMVA
|
2
|
+
class Header
|
3
|
+
attr_reader :number_of_entries, :jurisdiction_version_number, :issuer_identification_number
|
4
|
+
|
5
|
+
def initialize(number_of_entries:, jurisdiction_version_number:, issuer_identification_number:)
|
6
|
+
@number_of_entries = number_of_entries
|
7
|
+
@jurisdiction_version_number = jurisdiction_version_number
|
8
|
+
@issuer_identification_number = issuer_identification_number
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
number_of_entries == other.number_of_entries &&
|
13
|
+
jurisdiction_version_number == other.jurisdiction_version_number &&
|
14
|
+
issuer_identification_number == other.issuer_identification_number
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module AAMVA
|
6
|
+
class Standard
|
7
|
+
attr_reader :spec
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def initialize(year)
|
12
|
+
@spec = YAML.load_file(File.expand_path("../../../lib/aamva/data/info/#{year}.yml", __FILE__))
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :@spec, :[]
|
16
|
+
|
17
|
+
def factory(field)
|
18
|
+
if data_element?(field)
|
19
|
+
{
|
20
|
+
:type => data_element(field)&.dig("factory", "type"),
|
21
|
+
:options => data_element(field)&.dig("factory", "options").to_h
|
22
|
+
}
|
23
|
+
elsif header_field?(field)
|
24
|
+
{
|
25
|
+
:type => header(field)&.dig("factory", "type"),
|
26
|
+
:options => header(field)&.dig("factory", "options").to_h
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def header(field)
|
32
|
+
@spec['header'][field.to_s]
|
33
|
+
end
|
34
|
+
|
35
|
+
def required_data_elements
|
36
|
+
@spec['data_elements'].reject { |de| de['required'] }.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def data_element(data_element)
|
40
|
+
@spec['data_elements'][data_element.to_s]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def data_element?(field)
|
46
|
+
!!data_element(field)
|
47
|
+
end
|
48
|
+
|
49
|
+
def header_field?(field)
|
50
|
+
!!header(field)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module AAMVA
|
2
|
+
class Subfile
|
3
|
+
attr_reader :type, :data_elements
|
4
|
+
|
5
|
+
def initialize(type:, data_elements:)
|
6
|
+
@type = type
|
7
|
+
@data_elements = data_elements
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
type == other.type &&
|
12
|
+
data_elements == other.data_elements
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AAMVA
|
2
|
+
class SubfileDesignator
|
3
|
+
attr_reader :type, :offset, :length
|
4
|
+
|
5
|
+
def initialize(type:, length:, offset:)
|
6
|
+
@type = type
|
7
|
+
@length = length
|
8
|
+
@offset = offset
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
type == other.type &&
|
13
|
+
offset == other.offset &&
|
14
|
+
length == other.length
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/aamva/utils.rb
ADDED
data/lib/aamva/validator.rb
CHANGED
@@ -1,67 +1,15 @@
|
|
1
|
-
|
2
|
-
class Validator
|
3
|
-
|
4
|
-
# Physical Description – Eye Color
|
5
|
-
|
6
|
-
def self.day(day)
|
7
|
-
return false unless length(day, min: DAY_LENGTH, max: DAY_LENGTH)
|
8
|
-
return false unless DAY_MAPPING.keys.include?(day)
|
9
|
-
|
10
|
-
true
|
11
|
-
end
|
12
|
-
|
13
|
-
# Document Expiration Date
|
14
|
-
|
15
|
-
def self.dba(dbd)
|
16
|
-
dbd.match?(/\A[\d+]{8,8}\z/)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Customer ID Number
|
20
|
-
|
21
|
-
def self.daq(daq)
|
22
|
-
daq.match?(/\A[\d\w]{1,25}\z/)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Document Issue Date
|
26
|
-
|
27
|
-
def self.dbd(dbd)
|
28
|
-
dbd.match?(/\A[\d+]{8,8}\z/)
|
29
|
-
end
|
1
|
+
# frozen_string_literal: true
|
30
2
|
|
31
|
-
|
32
|
-
|
33
|
-
def
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.dbc(dbc)
|
38
|
-
return false unless length(dbc, min: 1, max: 1)
|
39
|
-
return false unless DBC_MAPPING.keys.include?(dbc)
|
40
|
-
|
41
|
-
true
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.dcg(dcg)
|
45
|
-
return false unless length(dcg, min: 3, max: 3)
|
46
|
-
return false unless DCG_MAPPING.keys.include?(dcg)
|
47
|
-
|
48
|
-
true
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.dai(dai)
|
52
|
-
return false unless length(dai, min: 1, max: 20)
|
53
|
-
|
54
|
-
true
|
3
|
+
module AAMVA
|
4
|
+
class Validator
|
5
|
+
def initialize(standard)
|
6
|
+
@standard = standard
|
55
7
|
end
|
56
8
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
true
|
61
|
-
end
|
9
|
+
def valid?(data_element, value)
|
10
|
+
info = @standard.data_element(data_element)
|
62
11
|
|
63
|
-
|
64
|
-
value.length >= min && value.length <= max
|
12
|
+
value.match?(Regexp.new(info['regexp']))
|
65
13
|
end
|
66
14
|
end
|
67
15
|
end
|
data/lib/aamva/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aamva
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Decot
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faker
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: gli
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -25,19 +39,33 @@ dependencies:
|
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '2.16'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: pdf417
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- - "
|
45
|
+
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
47
|
+
version: 1.0.0
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - "
|
52
|
+
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: 1.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: chunky_png
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.10
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.10
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,6 +174,9 @@ extra_rdoc_files: []
|
|
146
174
|
files:
|
147
175
|
- ".gitignore"
|
148
176
|
- ".rspec"
|
177
|
+
- ".ruby-gemset"
|
178
|
+
- ".ruby-version"
|
179
|
+
- ".simplecov"
|
149
180
|
- ".travis.yml"
|
150
181
|
- Gemfile
|
151
182
|
- Guardfile
|
@@ -157,12 +188,19 @@ files:
|
|
157
188
|
- bin/setup
|
158
189
|
- exe/aamva
|
159
190
|
- lib/aamva.rb
|
160
|
-
- lib/aamva/
|
191
|
+
- lib/aamva/calculator.rb
|
161
192
|
- lib/aamva/cli.rb
|
162
|
-
- lib/aamva/
|
163
|
-
- lib/aamva/
|
164
|
-
- lib/aamva/
|
193
|
+
- lib/aamva/data.rb
|
194
|
+
- lib/aamva/data/info/2016.yml
|
195
|
+
- lib/aamva/decoder.rb
|
196
|
+
- lib/aamva/encoder.rb
|
197
|
+
- lib/aamva/factory.rb
|
165
198
|
- lib/aamva/generator.rb
|
199
|
+
- lib/aamva/header.rb
|
200
|
+
- lib/aamva/standard.rb
|
201
|
+
- lib/aamva/subfile.rb
|
202
|
+
- lib/aamva/subfile_designator.rb
|
203
|
+
- lib/aamva/utils.rb
|
166
204
|
- lib/aamva/validator.rb
|
167
205
|
- lib/aamva/version.rb
|
168
206
|
homepage: https://github.com/kyledecot/aamva
|
data/lib/aamva/card.rb
DELETED
data/lib/aamva/data_element.rb
DELETED