USPS-intelligent-barcode 0.1.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.
- 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]]
|