USPS-intelligent-barcode 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.md +11 -0
- data/README.md +56 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/examples/example.rb +17 -0
- data/lib/USPS-intelligent-barcode.rb +5 -0
- data/lib/USPS-intelligent-barcode/BarMap.rb +40 -0
- data/lib/USPS-intelligent-barcode/BarPosition.rb +26 -0
- data/lib/USPS-intelligent-barcode/Barcode.rb +110 -0
- data/lib/USPS-intelligent-barcode/BarcodeId.rb +66 -0
- data/lib/USPS-intelligent-barcode/CharacterPosition.rb +16 -0
- data/lib/USPS-intelligent-barcode/CodewordMap.rb +28 -0
- data/lib/USPS-intelligent-barcode/Crc.rb +42 -0
- data/lib/USPS-intelligent-barcode/MailerId.rb +70 -0
- data/lib/USPS-intelligent-barcode/NumericConversions.rb +13 -0
- data/lib/USPS-intelligent-barcode/RoutingCode.rb +77 -0
- data/lib/USPS-intelligent-barcode/SerialNumber.rb +61 -0
- data/lib/USPS-intelligent-barcode/ServiceType.rb +51 -0
- data/lib/USPS-intelligent-barcode/autoload.rb +61 -0
- data/lib/USPS-intelligent-barcode/bar_to_character_mapping.yml +66 -0
- data/lib/USPS-intelligent-barcode/codeword_to_character_mapping.yml +1366 -0
- data/spec/BarMap_spec.rb +30 -0
- data/spec/BarPosition_spec.rb +52 -0
- data/spec/BarcodeId_spec.rb +118 -0
- data/spec/Barcode_spec.rb +213 -0
- data/spec/CharacterPosition_spec.rb +25 -0
- data/spec/CodewordMap_spec.rb +22 -0
- data/spec/Crc_spec.rb +21 -0
- data/spec/MailerId_spec.rb +114 -0
- data/spec/NumericConversions_spec.rb +23 -0
- data/spec/RoutingCode_spec.rb +180 -0
- data/spec/SerialNumber_spec.rb +117 -0
- data/spec/ServiceType_spec.rb +93 -0
- data/spec/spec_helper.rb +1 -0
- metadata +160 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class CodewordMap
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@characters = load_characters
|
7
|
+
end
|
8
|
+
|
9
|
+
def characters(codewords)
|
10
|
+
codewords.map do |codeword|
|
11
|
+
@characters[codeword]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def load_characters
|
18
|
+
YAML.load_file(characters_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def characters_path
|
22
|
+
File.expand_path('codeword_to_character_mapping.yml',
|
23
|
+
File.dirname(__FILE__))
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class Crc
|
4
|
+
|
5
|
+
extend NumericConversions
|
6
|
+
|
7
|
+
def self.crc(binary_data)
|
8
|
+
crc = MASK
|
9
|
+
bytes = numeric_to_bytes(binary_data, NUM_INPUT_BYTES)
|
10
|
+
crc = crc_byte(crc, bytes.first, LEADING_BITS_TO_IGNORE)
|
11
|
+
for byte in bytes[1...NUM_INPUT_BYTES]
|
12
|
+
crc = crc_byte(crc, byte, 0)
|
13
|
+
end
|
14
|
+
crc
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
LEADING_BITS_TO_IGNORE = 2
|
20
|
+
CRC_BITS = 11
|
21
|
+
CRC_MSB_MASK = 1 << (CRC_BITS - 1)
|
22
|
+
BITS_PER_BYTE = 8
|
23
|
+
POLYNOMIAL = 0x0F35
|
24
|
+
MASK = (1 << CRC_BITS) - 1
|
25
|
+
NUM_INPUT_BYTES = 13
|
26
|
+
|
27
|
+
def self.crc_byte(crc, byte, leading_bits_to_ignore)
|
28
|
+
num_bits = BITS_PER_BYTE - leading_bits_to_ignore
|
29
|
+
data = byte << CRC_BITS - BITS_PER_BYTE + leading_bits_to_ignore
|
30
|
+
num_bits.times do
|
31
|
+
use_polynomial = (crc ^ data) & CRC_MSB_MASK
|
32
|
+
crc <<= 1
|
33
|
+
crc ^= POLYNOMIAL if use_polynomial != 0
|
34
|
+
crc &= MASK
|
35
|
+
data <<= 1
|
36
|
+
end
|
37
|
+
crc
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class MailerId
|
4
|
+
|
5
|
+
def self.coerce(o)
|
6
|
+
case o
|
7
|
+
when MailerId
|
8
|
+
o
|
9
|
+
when String
|
10
|
+
new(o.to_i)
|
11
|
+
when Integer
|
12
|
+
new(o)
|
13
|
+
else
|
14
|
+
raise ArgumentError, 'Cannot coerce to MailerId'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(value)
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(long_mailer_id)
|
23
|
+
unless in_range?
|
24
|
+
raise ArgumentError, "Must be #{RANGES.join(' or ')}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(o)
|
29
|
+
MailerId.coerce(o).to_i == to_i
|
30
|
+
rescue ArgumentError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def long?
|
35
|
+
LONG_RANGE === @value
|
36
|
+
end
|
37
|
+
|
38
|
+
def shift_and_add_to(target, long_mailer_id)
|
39
|
+
target * 10 ** num_digits + to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def to_i
|
45
|
+
@value
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
SHORT_RANGE = 0..899_999
|
51
|
+
LONG_RANGE = 900_000_000..999_999_999
|
52
|
+
RANGES = [SHORT_RANGE, LONG_RANGE]
|
53
|
+
|
54
|
+
def in_range?
|
55
|
+
RANGES.any? do |range|
|
56
|
+
range === @value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def num_digits
|
61
|
+
if long?
|
62
|
+
9
|
63
|
+
else
|
64
|
+
6
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class RoutingCode
|
4
|
+
|
5
|
+
def self.coerce(o)
|
6
|
+
case o
|
7
|
+
when nil
|
8
|
+
coerce('')
|
9
|
+
when RoutingCode
|
10
|
+
o
|
11
|
+
when Array
|
12
|
+
RoutingCode.new(*o)
|
13
|
+
when String
|
14
|
+
RoutingCode.new(*string_to_array(o))
|
15
|
+
else
|
16
|
+
raise ArgumentError, 'Cannot coerce to RoutingCode'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.string_to_array(s)
|
21
|
+
s = s.gsub(/[\D]/, '')
|
22
|
+
match = /^(?:(\d{5})(?:(\d{4})(\d{2})?)?)?$/.match(s)
|
23
|
+
unless match
|
24
|
+
raise ArgumentError, "Bad routing code: #{s.inspect}"
|
25
|
+
end
|
26
|
+
zip, plus4, delivery_point = match.to_a[1..-1]
|
27
|
+
[zip, plus4, delivery_point]
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :zip, :plus4, :delivery_point
|
31
|
+
|
32
|
+
def initialize(zip, plus4, delivery_point)
|
33
|
+
@zip = arg_to_i(zip)
|
34
|
+
@plus4 = arg_to_i(plus4)
|
35
|
+
@delivery_point = arg_to_i(delivery_point)
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate(long_mailer_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert
|
42
|
+
if @zip && @plus4 && @delivery_point
|
43
|
+
@zip * 1000000 + @plus4 * 100 + @delivery_point + 1000100001
|
44
|
+
elsif @zip && @plus4
|
45
|
+
@zip * 10000 + @plus4 + 100001
|
46
|
+
elsif @zip
|
47
|
+
@zip + 1
|
48
|
+
else
|
49
|
+
0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_a
|
54
|
+
[@zip, @plus4, @delivery_point]
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(o)
|
58
|
+
RoutingCode.coerce(o).to_a == to_a
|
59
|
+
rescue ArgumentError
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def shift_and_add_to(target, long_mailer_id)
|
64
|
+
target * 10 ** NUM_DIGITS + convert
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
NUM_DIGITS = 11
|
70
|
+
|
71
|
+
def arg_to_i(o)
|
72
|
+
o.andand.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class SerialNumber
|
4
|
+
|
5
|
+
def self.coerce(o)
|
6
|
+
case o
|
7
|
+
when SerialNumber
|
8
|
+
o
|
9
|
+
when String
|
10
|
+
new(o.to_i)
|
11
|
+
when Integer
|
12
|
+
new(o)
|
13
|
+
else
|
14
|
+
raise ArgumentError, 'Cannot coerce to SerialNumber'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(value)
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(long_mailer_id)
|
23
|
+
range = 0..max_value(long_mailer_id)
|
24
|
+
unless range === @value
|
25
|
+
raise ArgumentError, "Must be #{range}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(o)
|
30
|
+
SerialNumber.coerce(o).to_i == to_i
|
31
|
+
rescue ArgumentError
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def shift_and_add_to(target, long_mailer_id)
|
36
|
+
target * 10 ** num_digits(long_mailer_id) + to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def to_i
|
42
|
+
@value
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def max_value(long_mailer_id)
|
48
|
+
max_value = 10 ** num_digits(long_mailer_id) - 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def num_digits(long_mailer_id)
|
52
|
+
if long_mailer_id
|
53
|
+
6
|
54
|
+
else
|
55
|
+
9
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Imb
|
2
|
+
|
3
|
+
class ServiceType
|
4
|
+
|
5
|
+
def self.coerce(o)
|
6
|
+
case o
|
7
|
+
when ServiceType
|
8
|
+
o
|
9
|
+
when String
|
10
|
+
new(o.to_i)
|
11
|
+
when Integer
|
12
|
+
new(o)
|
13
|
+
else
|
14
|
+
raise ArgumentError, 'Cannot coerce to ServiceType'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(value)
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(long_mailer_id)
|
23
|
+
unless (RANGE) === @value
|
24
|
+
raise ArgumentError, "Must be #{RANGE}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(o)
|
29
|
+
ServiceType.coerce(o).to_i == to_i
|
30
|
+
rescue ArgumentError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def shift_and_add_to(target, long_mailer_id)
|
35
|
+
target * 10 ** NUM_DIGITS + to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def to_i
|
41
|
+
@value
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
RANGE = 0..999
|
47
|
+
NUM_DIGITS = 3
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# class Autoload courtesy of www.databill.com
|
2
|
+
|
3
|
+
class Autoload
|
4
|
+
|
5
|
+
def self.setup(*args)
|
6
|
+
new(*args).setup
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(namespace, path)
|
10
|
+
@namespace = namespace
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
autoload_classes_and_modules
|
16
|
+
require_utilities
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def require_utilities
|
22
|
+
utility_paths.each do |path|
|
23
|
+
require File.expand_path(path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def autoload_classes_and_modules
|
28
|
+
class_and_module_paths.each do |path|
|
29
|
+
module_name = File.basename(path).chomp('.rb')
|
30
|
+
@namespace.instance_eval do
|
31
|
+
autoload(module_name, File.expand_path(path))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def utility_paths
|
37
|
+
ruby_paths - class_and_module_paths - [@path]
|
38
|
+
end
|
39
|
+
|
40
|
+
def ruby_paths
|
41
|
+
dirname = File.directory?(@path) ? @path : File.dirname(@path)
|
42
|
+
paths = Dir[File.join(dirname, '*.rb')]
|
43
|
+
paths.reject do |path|
|
44
|
+
File.basename(path) == File.basename(@path)
|
45
|
+
end
|
46
|
+
# Force require statements to happen in a random order to discover
|
47
|
+
# missing direct dependencies
|
48
|
+
paths.sort_by {rand}
|
49
|
+
end
|
50
|
+
|
51
|
+
def class_and_module_paths
|
52
|
+
ruby_paths.find_all do |path|
|
53
|
+
File.basename(path) =~ /[A-Z]/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
module Imb ; end
|
60
|
+
|
61
|
+
Autoload.setup(Imb, __FILE__)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
---
|
2
|
+
- [[7, 2], [4, 3]]
|
3
|
+
- [[1, 10], [0, 0]]
|
4
|
+
- [[9, 12], [2, 8]]
|
5
|
+
- [[5, 5], [6, 11]]
|
6
|
+
- [[8, 9], [3, 1]]
|
7
|
+
- [[0, 1], [5, 12]]
|
8
|
+
- [[2, 5], [1, 8]]
|
9
|
+
- [[4, 4], [9, 11]]
|
10
|
+
- [[6, 3], [8, 10]]
|
11
|
+
- [[3, 9], [7, 6]]
|
12
|
+
- [[5, 11], [1, 4]]
|
13
|
+
- [[8, 5], [2, 12]]
|
14
|
+
- [[9, 10], [0, 2]]
|
15
|
+
- [[7, 1], [6, 7]]
|
16
|
+
- [[3, 6], [4, 9]]
|
17
|
+
- [[0, 3], [8, 6]]
|
18
|
+
- [[6, 4], [2, 7]]
|
19
|
+
- [[1, 1], [9, 9]]
|
20
|
+
- [[7, 10], [5, 2]]
|
21
|
+
- [[4, 0], [3, 8]]
|
22
|
+
- [[6, 2], [0, 4]]
|
23
|
+
- [[8, 11], [1, 0]]
|
24
|
+
- [[9, 8], [3, 12]]
|
25
|
+
- [[2, 6], [7, 7]]
|
26
|
+
- [[5, 1], [4, 10]]
|
27
|
+
- [[1, 12], [6, 9]]
|
28
|
+
- [[7, 3], [8, 0]]
|
29
|
+
- [[5, 8], [9, 7]]
|
30
|
+
- [[4, 6], [2, 10]]
|
31
|
+
- [[3, 4], [0, 5]]
|
32
|
+
- [[8, 4], [5, 7]]
|
33
|
+
- [[7, 11], [1, 9]]
|
34
|
+
- [[6, 0], [9, 6]]
|
35
|
+
- [[0, 6], [4, 8]]
|
36
|
+
- [[2, 1], [3, 2]]
|
37
|
+
- [[5, 9], [8, 12]]
|
38
|
+
- [[4, 11], [6, 1]]
|
39
|
+
- [[9, 5], [7, 4]]
|
40
|
+
- [[3, 3], [1, 2]]
|
41
|
+
- [[0, 7], [2, 0]]
|
42
|
+
- [[1, 3], [4, 1]]
|
43
|
+
- [[6, 10], [3, 5]]
|
44
|
+
- [[8, 7], [9, 4]]
|
45
|
+
- [[2, 11], [5, 6]]
|
46
|
+
- [[0, 8], [7, 12]]
|
47
|
+
- [[4, 2], [8, 1]]
|
48
|
+
- [[5, 10], [3, 0]]
|
49
|
+
- [[9, 3], [0, 9]]
|
50
|
+
- [[6, 5], [2, 4]]
|
51
|
+
- [[7, 8], [1, 7]]
|
52
|
+
- [[5, 0], [4, 5]]
|
53
|
+
- [[2, 3], [0, 10]]
|
54
|
+
- [[6, 12], [9, 2]]
|
55
|
+
- [[3, 11], [1, 6]]
|
56
|
+
- [[8, 8], [7, 9]]
|
57
|
+
- [[5, 4], [0, 11]]
|
58
|
+
- [[1, 5], [2, 2]]
|
59
|
+
- [[9, 1], [4, 12]]
|
60
|
+
- [[8, 3], [6, 6]]
|
61
|
+
- [[7, 0], [3, 7]]
|
62
|
+
- [[4, 7], [7, 5]]
|
63
|
+
- [[0, 12], [1, 11]]
|
64
|
+
- [[2, 9], [9, 0]]
|
65
|
+
- [[6, 8], [5, 3]]
|
66
|
+
- [[3, 10], [8, 2]]
|