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.
Files changed (37) hide show
  1. data/Gemfile +9 -0
  2. data/Gemfile.lock +33 -0
  3. data/LICENSE.md +11 -0
  4. data/README.md +56 -0
  5. data/Rakefile +46 -0
  6. data/VERSION +1 -0
  7. data/examples/example.rb +17 -0
  8. data/lib/USPS-intelligent-barcode.rb +5 -0
  9. data/lib/USPS-intelligent-barcode/BarMap.rb +40 -0
  10. data/lib/USPS-intelligent-barcode/BarPosition.rb +26 -0
  11. data/lib/USPS-intelligent-barcode/Barcode.rb +110 -0
  12. data/lib/USPS-intelligent-barcode/BarcodeId.rb +66 -0
  13. data/lib/USPS-intelligent-barcode/CharacterPosition.rb +16 -0
  14. data/lib/USPS-intelligent-barcode/CodewordMap.rb +28 -0
  15. data/lib/USPS-intelligent-barcode/Crc.rb +42 -0
  16. data/lib/USPS-intelligent-barcode/MailerId.rb +70 -0
  17. data/lib/USPS-intelligent-barcode/NumericConversions.rb +13 -0
  18. data/lib/USPS-intelligent-barcode/RoutingCode.rb +77 -0
  19. data/lib/USPS-intelligent-barcode/SerialNumber.rb +61 -0
  20. data/lib/USPS-intelligent-barcode/ServiceType.rb +51 -0
  21. data/lib/USPS-intelligent-barcode/autoload.rb +61 -0
  22. data/lib/USPS-intelligent-barcode/bar_to_character_mapping.yml +66 -0
  23. data/lib/USPS-intelligent-barcode/codeword_to_character_mapping.yml +1366 -0
  24. data/spec/BarMap_spec.rb +30 -0
  25. data/spec/BarPosition_spec.rb +52 -0
  26. data/spec/BarcodeId_spec.rb +118 -0
  27. data/spec/Barcode_spec.rb +213 -0
  28. data/spec/CharacterPosition_spec.rb +25 -0
  29. data/spec/CodewordMap_spec.rb +22 -0
  30. data/spec/Crc_spec.rb +21 -0
  31. data/spec/MailerId_spec.rb +114 -0
  32. data/spec/NumericConversions_spec.rb +23 -0
  33. data/spec/RoutingCode_spec.rb +180 -0
  34. data/spec/SerialNumber_spec.rb +117 -0
  35. data/spec/ServiceType_spec.rb +93 -0
  36. data/spec/spec_helper.rb +1 -0
  37. 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,13 @@
1
+ module Imb
2
+
3
+ module NumericConversions
4
+
5
+ def numeric_to_bytes(n, min_bytes=0)
6
+ n.to_s(16).rjust(2 * min_bytes, '0').scan(/../).map do |s|
7
+ s.to_i(16)
8
+ end
9
+ end
10
+
11
+ end
12
+
13
+ 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]]