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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3af3f537099b16b1d96ab150d8823913fcd87e18
4
- data.tar.gz: 7ce0e6fea0b14b4b9ae30dbbda0a3dd42c9b7eb3
3
+ metadata.gz: 523077b5eb76178e4058d12f2d6d2310b48b42a4
4
+ data.tar.gz: 9e44dc38c7ec45de75e65985473f66223615e17c
5
5
  SHA512:
6
- metadata.gz: 03af9e81adc73f281da2bb35d3e496e06bb4a88afe5aa7171debefcfa4cb78e3239dda1953197931eac31162b905ef228a887ecfb89454a648e5a561be4bbd71
7
- data.tar.gz: 6cce0f716c0c9d2be90db8648f14c58a02af4af4998c3ebb7e000916dc94d36de2a1063b2ff89752847436a7339b98eb564f2b2ba646235ed82aef5840a56f8e
6
+ metadata.gz: 81d9a57d3d2265519b5cd39ed09eb15c9b521e889a745d1414d60e83f1c8d62362402777e74644d4b1835c0c8eb5a75658cc1fb535b998de6b5ccd7147da7dcc
7
+ data.tar.gz: ff50896a726449681cabc6db9c0b6edf7225464da511299acdf746f1ca5f796502d912ce904f6c7243600a70925d3304a050b2ce2bbd71aeca062e28a6aefada
data/.rspec CHANGED
@@ -1,2 +1 @@
1
- --format documentation
2
1
  --color
@@ -0,0 +1 @@
1
+ aamva
@@ -0,0 +1 @@
1
+ 2.4.1
@@ -0,0 +1,3 @@
1
+ SimpleCov.start do
2
+ add_filter "/spec/"
3
+ end
@@ -1,5 +1,17 @@
1
- sudo: false
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
- before_install: gem install bundler -v 1.14.6
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
- # Aamva
1
+ # AAMVA
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/aamva`. To experiment with that code, run `bin/console` for an interactive prompt.
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/[USERNAME]/aamva.
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
-
@@ -7,7 +7,7 @@ require 'aamva/version'
7
7
 
8
8
  Gem::Specification.new do |spec|
9
9
  spec.name = 'aamva'
10
- spec.version = Aamva::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'
@@ -3,13 +3,6 @@
3
3
 
4
4
  require 'bundler/setup'
5
5
  require 'aamva'
6
+ require "pry"
6
7
 
7
- # You can add fixtures and/or initialization code here to make experimenting
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
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env bash
2
+
2
3
  set -euo pipefail
3
4
  IFS=$'\n\t'
4
5
  set -vx
5
6
 
6
7
  bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/exe/aamva CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  require 'aamva'
5
5
 
6
- Aamva::CLI.run(ARGV)
6
+ AAMVA::CLI.run(ARGV)
@@ -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 Aamva
11
- DAY_LENGTH = 3
12
- MAX_DAC_LENGTH = 40
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
- DCG_MAPPING = {
15
- 'USA' => 'USA',
16
- 'CAN' => 'CAN'
17
- }
23
+ LENGTH_UNITS = %w[in cm].freeze
18
24
 
19
- DBC_MAPPING = {
20
- "1" => "Male",
21
- "2" => "Female",
22
- "9" => "Not Specified"
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
@@ -3,16 +3,22 @@
3
3
  require 'gli'
4
4
  require 'aamva/version'
5
5
 
6
- module Aamva
6
+ module AAMVA
7
7
  class CLI
8
8
  extend GLI::App
9
9
 
10
- version Aamva::VERSION
10
+ version AAMVA::VERSION
11
11
 
12
- command ["generate:day"] do |c|
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 Aamva::Generator.day
21
+ puts encoder.png
16
22
  end
17
23
  end
18
24
  end
@@ -0,0 +1,11 @@
1
+ module AAMVA
2
+ class Data
3
+ attr_reader :header, :subfile_designators, :subfiles
4
+
5
+ def initialize(header:, subfile_designators:, subfiles:)
6
+ @header = header
7
+ @subfile_designators = subfile_designators
8
+ @subfiles = subfiles
9
+ end
10
+ end
11
+ end
@@ -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
@@ -1,39 +1,71 @@
1
- require 'faker'
1
+ # frozen_string_literal: true
2
2
 
3
- module Aamva
3
+ module AAMVA
4
4
  class Generator
5
- def self.dcg
6
- DCG_MAPPING.keys.sample
5
+ attr_reader :standard
6
+
7
+ def initialize(standard)
8
+ @standard = standard
7
9
  end
8
10
 
9
- # Customer ID Number
11
+ def data
12
+ @data ||= Data.new(
13
+ header: header,
14
+ subfile_designators: subfile_designators,
15
+ subfiles: subfiles
16
+ )
17
+ end
10
18
 
11
- def self.daq
12
- chars = ('A'..'Z').to_a + (('0'..'9').to_a)
19
+ private
13
20
 
14
- chars.sample(25).join("")
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
- # Physical Description – Eye Color
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
- def self.day
20
- DAY_MAPPING.keys.sample
21
- end
38
+ offset = Calculator.subfile_offset
22
39
 
23
- def self.dac
24
- Faker::Name.first_name[0..MAX_DAC_LENGTH]
40
+ SubfileDesignator.new(
41
+ type: subfile.type,
42
+ length: length,
43
+ offset: offset
44
+ )
45
+ end
25
46
  end
26
47
 
27
- # Date of Birth
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
- def self.dbb
30
- Faker::Date.birthday.strftime("%Y%m%d")
54
+ {
55
+ "DL" => Subfile.new(
56
+ type: "DL",
57
+ data_elements: data_elements
58
+ )
59
+ }
60
+ end
31
61
  end
32
62
 
33
- # Document Issue Date
34
-
35
- def self.dbd
36
- Faker::Date.backward.strftime("%Y%m%d")
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
@@ -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
@@ -0,0 +1,7 @@
1
+ module AAMVA
2
+ class Utils
3
+ def self.truncate(value, options = {})
4
+ value[0...options.fetch('length', -1)]
5
+ end
6
+ end
7
+ end
@@ -1,67 +1,15 @@
1
- module Aamva
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
- # Date of Birth
32
-
33
- def self.dbb(dbb)
34
- dbb.match?(/\A[\d+]{8,8}\z/)
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 self.dac(dac)
58
- return false unless length(dac, min: 1, max: MAX_DAC_LENGTH)
59
-
60
- true
61
- end
9
+ def valid?(data_element, value)
10
+ info = @standard.data_element(data_element)
62
11
 
63
- def self.length(value, min:, max:)
64
- value.length >= min && value.length <= max
12
+ value.match?(Regexp.new(info['regexp']))
65
13
  end
66
14
  end
67
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Aamva
4
- VERSION = '0.1.0'
3
+ module AAMVA
4
+ VERSION = '0.2.0'
5
5
  end
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.1.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-01-24 00:00:00.000000000 Z
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: faker
42
+ name: pdf417
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - ">="
45
+ - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0'
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: '0'
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/card.rb
191
+ - lib/aamva/calculator.rb
161
192
  - lib/aamva/cli.rb
162
- - lib/aamva/data_element.rb
163
- - lib/aamva/data_element/base.rb
164
- - lib/aamva/data_element/day.rb
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
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aamva
4
- class Card
5
- end
6
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aamva/data_element/base'
4
- require 'aamva/data_element/day'
5
-
6
- module Aamva
7
- module DataElement
8
- end
9
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aamva
4
- module DataElement
5
- class Base
6
- end
7
- end
8
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aamva
4
- module DataElement
5
- class DAY < Base
6
- HAZEL = 'HAZ'
7
-
8
- def initialize(data)
9
- @data = data
10
- end
11
-
12
- def format
13
- "DAY#{@data}"
14
- end
15
- end
16
- end
17
- end