usps_intelligent_barcode 0.3.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.markdown +67 -0
  4. data/Gemfile +13 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENSE.md +11 -0
  7. data/README.markdown +64 -0
  8. data/Rakefile +40 -0
  9. data/VERSION +1 -0
  10. data/examples/example.rb +17 -0
  11. data/lib/USPS-intelligent-barcode.rb +17 -0
  12. data/lib/USPS-intelligent-barcode/bar_map.rb +52 -0
  13. data/lib/USPS-intelligent-barcode/bar_position.rb +40 -0
  14. data/lib/USPS-intelligent-barcode/bar_symbol.rb +49 -0
  15. data/lib/USPS-intelligent-barcode/bar_to_character_mapping.yml +66 -0
  16. data/lib/USPS-intelligent-barcode/barcode.rb +152 -0
  17. data/lib/USPS-intelligent-barcode/barcode_id.rb +98 -0
  18. data/lib/USPS-intelligent-barcode/character_position.rb +28 -0
  19. data/lib/USPS-intelligent-barcode/codeword_map.rb +38 -0
  20. data/lib/USPS-intelligent-barcode/codeword_to_character_mapping.yml +1366 -0
  21. data/lib/USPS-intelligent-barcode/crc.rb +52 -0
  22. data/lib/USPS-intelligent-barcode/mailer_id.rb +105 -0
  23. data/lib/USPS-intelligent-barcode/numeric_conversions.rb +22 -0
  24. data/lib/USPS-intelligent-barcode/routing_code.rb +134 -0
  25. data/lib/USPS-intelligent-barcode/serial_number.rb +88 -0
  26. data/lib/USPS-intelligent-barcode/service_type.rb +79 -0
  27. data/lib/usps_intelligent_barcode.rb +17 -0
  28. data/lib/usps_intelligent_barcode/bar_map.rb +52 -0
  29. data/lib/usps_intelligent_barcode/bar_position.rb +40 -0
  30. data/lib/usps_intelligent_barcode/bar_symbol.rb +49 -0
  31. data/lib/usps_intelligent_barcode/bar_to_character_mapping.yml +66 -0
  32. data/lib/usps_intelligent_barcode/barcode.rb +152 -0
  33. data/lib/usps_intelligent_barcode/barcode_id.rb +98 -0
  34. data/lib/usps_intelligent_barcode/character_position.rb +28 -0
  35. data/lib/usps_intelligent_barcode/codeword_map.rb +38 -0
  36. data/lib/usps_intelligent_barcode/codeword_to_character_mapping.yml +1366 -0
  37. data/lib/usps_intelligent_barcode/crc.rb +52 -0
  38. data/lib/usps_intelligent_barcode/mailer_id.rb +105 -0
  39. data/lib/usps_intelligent_barcode/numeric_conversions.rb +22 -0
  40. data/lib/usps_intelligent_barcode/routing_code.rb +134 -0
  41. data/lib/usps_intelligent_barcode/serial_number.rb +88 -0
  42. data/lib/usps_intelligent_barcode/service_type.rb +79 -0
  43. data/spec/bar_map_spec.rb +30 -0
  44. data/spec/bar_position_spec.rb +40 -0
  45. data/spec/bar_symbol_spec.rb +39 -0
  46. data/spec/barcode_id_spec.rb +106 -0
  47. data/spec/barcode_spec.rb +213 -0
  48. data/spec/character_position_spec.rb +25 -0
  49. data/spec/codeword_map_spec.rb +22 -0
  50. data/spec/crc_spec.rb +21 -0
  51. data/spec/mailer_id_spec.rb +124 -0
  52. data/spec/numeric_conversions_spec.rb +23 -0
  53. data/spec/routing_code_spec.rb +180 -0
  54. data/spec/serial_number_spec.rb +117 -0
  55. data/spec/service_type_spec.rb +93 -0
  56. data/spec/spec_helper.rb +8 -0
  57. data/usps_intelligent_barcode.gemspec +117 -0
  58. metadata +216 -0
@@ -0,0 +1,52 @@
1
+ require 'USPS-intelligent-barcode/numeric_conversions'
2
+
3
+ module Imb
4
+
5
+ # @!group Internal
6
+
7
+ # Calculates the Intelligent Mail Barcode CRC.
8
+
9
+ class Crc
10
+
11
+ include NumericConversions
12
+
13
+ # Calculate a CRC.
14
+ # @param [Integer] binary_data A 102-bit integer
15
+ # @return [Integer] An 11-bit CRC
16
+
17
+ def crc(binary_data)
18
+ crc = MASK
19
+ bytes = numeric_to_bytes(binary_data, NUM_INPUT_BYTES)
20
+ crc = crc_byte(crc, bytes.first, LEADING_BITS_TO_IGNORE)
21
+ for byte in bytes[1...NUM_INPUT_BYTES]
22
+ crc = crc_byte(crc, byte, 0)
23
+ end
24
+ crc
25
+ end
26
+
27
+ private
28
+
29
+ LEADING_BITS_TO_IGNORE = 2
30
+ CRC_BITS = 11
31
+ CRC_MSB_MASK = 1 << (CRC_BITS - 1)
32
+ BITS_PER_BYTE = 8
33
+ POLYNOMIAL = 0x0F35
34
+ MASK = (1 << CRC_BITS) - 1
35
+ NUM_INPUT_BYTES = 13
36
+
37
+ def crc_byte(crc, byte, leading_bits_to_ignore)
38
+ num_bits = BITS_PER_BYTE - leading_bits_to_ignore
39
+ data = byte << CRC_BITS - BITS_PER_BYTE + leading_bits_to_ignore
40
+ num_bits.times do
41
+ use_polynomial = (crc ^ data) & CRC_MSB_MASK
42
+ crc <<= 1
43
+ crc ^= POLYNOMIAL if use_polynomial != 0
44
+ crc &= MASK
45
+ data <<= 1
46
+ end
47
+ crc
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,105 @@
1
+ module Imb
2
+
3
+ # This class represents a mailer ID.
4
+
5
+ class MailerId
6
+
7
+ # The allowable range for a short (6-digit) mailer ID
8
+ SHORT_RANGE = 0..899_999
9
+
10
+ # The allowable range for a long (9-digit) mailer ID
11
+ LONG_RANGE = 900_000_000..999_999_999
12
+
13
+ # The list of all allowable ranges for a mailer ID
14
+ RANGES = [SHORT_RANGE, LONG_RANGE]
15
+
16
+ # Turn the argument into a MailerID if possible. Accepts:
17
+ # * {MailerId}
18
+ # * String
19
+ # * Integer
20
+ # @return [MailerId]
21
+ # @raise [ArgumentError] If the argument cannot be coerced
22
+
23
+ def self.coerce(o)
24
+ case o
25
+ when MailerId
26
+ o
27
+ when String
28
+ new(o.to_i)
29
+ when Integer
30
+ new(o)
31
+ else
32
+ raise ArgumentError, 'Cannot coerce to MailerId'
33
+ end
34
+ end
35
+
36
+ # @param [Integer] value
37
+
38
+ def initialize(value)
39
+ @value = value
40
+ end
41
+
42
+ # Return true if this object is equal to o
43
+ # @param [Object] o Any object acceptable to {.coerce}
44
+
45
+ def ==(o)
46
+ MailerId.coerce(o).to_i == to_i
47
+ rescue ArgumentError
48
+ false
49
+ end
50
+
51
+ # @return [Integer] The value of the mailer ID
52
+
53
+ def to_i
54
+ @value
55
+ end
56
+
57
+ # @!group Internal
58
+
59
+ # Return true if this is a long (9 digit) mailer ID
60
+
61
+ def long?
62
+ LONG_RANGE === @value
63
+ end
64
+
65
+ # Validate the value.
66
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
67
+ # @raise ArgumentError if invalid
68
+
69
+ def validate(long_mailer_id)
70
+ unless in_range?
71
+ raise ArgumentError, "Must be #{RANGES.join(' or ')}"
72
+ end
73
+ end
74
+
75
+ # Add this object's value to target, shifting it left as many
76
+ # digts as are needed to make room.
77
+ # @param [Integer] target The target to be shifted and added to
78
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
79
+ # @return [Integer] The new value of the target
80
+
81
+ def shift_and_add_to(target, long_mailer_id)
82
+ target * 10 ** num_digits + to_i
83
+ end
84
+
85
+ # @!endgroup
86
+
87
+ private
88
+
89
+ def in_range?
90
+ RANGES.any? do |range|
91
+ range === @value
92
+ end
93
+ end
94
+
95
+ def num_digits
96
+ if long?
97
+ 9
98
+ else
99
+ 6
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,22 @@
1
+ module Imb
2
+
3
+ # @!group Internal
4
+
5
+ # Numeric conversions
6
+
7
+ module NumericConversions
8
+
9
+ # Convert a numeric to an array of at least +min_bytes+ bytes.
10
+ # @param [Numeric] n
11
+ # @param [Integer] min_bytes
12
+ # @return [[Integer]] Array of bytes
13
+
14
+ def numeric_to_bytes(n, min_bytes=0)
15
+ n.to_s(16).rjust(2 * min_bytes, '0').scan(/../).map do |s|
16
+ s.to_i(16)
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,134 @@
1
+ module Imb
2
+
3
+ # Represents a routing code
4
+
5
+ class RoutingCode
6
+
7
+ # Turn the argument into a RoutingCode if possible. Accepts:
8
+ # * {RoutingCode}
9
+ # * nil (no routing code)
10
+ # * String of length:
11
+ # * 0 - no routing code
12
+ # * 5 - zip
13
+ # * 9 - zip + plus4
14
+ # * 11 - zip + plus4 + delivery point
15
+ # * Array of [zip, plus4, delivery point]
16
+ # @return [RoutingCode]
17
+
18
+ def self.coerce(o)
19
+ case o
20
+ when nil
21
+ coerce('')
22
+ when RoutingCode
23
+ o
24
+ when Array
25
+ RoutingCode.new(*o)
26
+ when String
27
+ RoutingCode.new(*string_to_array(o))
28
+ else
29
+ raise ArgumentError, 'Cannot coerce to RoutingCode'
30
+ end
31
+ end
32
+
33
+ # @return [Integer] The ZIP (or nil)
34
+ attr_accessor :zip
35
+
36
+ # @return [Integer] The plus4 (or nil)
37
+ attr_accessor :plus4
38
+
39
+ # @return [Integer] The delivery point (or nil)
40
+ attr_accessor :delivery_point
41
+
42
+ # Create a RoutingCode. Arguments are:
43
+ # * +zip+ - Integer zip (or nil)
44
+ # * +plus4+ - Integer plus4 (or nil)
45
+ # * +delivery_point+ - Integer delivery poitn (or nil)
46
+
47
+ def initialize(zip, plus4, delivery_point)
48
+ @zip = arg_to_i(zip)
49
+ @plus4 = arg_to_i(plus4)
50
+ @delivery_point = arg_to_i(delivery_point)
51
+ end
52
+
53
+ # Return true if this object is equal to o
54
+ # @param [Object] o Any object acceptable to {.coerce}
55
+
56
+ def ==(o)
57
+ RoutingCode.coerce(o).to_a == to_a
58
+ rescue ArgumentError
59
+ false
60
+ end
61
+
62
+ # @!group Internal
63
+
64
+ # Convert a string representation of a routing code into
65
+ # an array that can be passed to the constructor.
66
+ # +s+ is a string of length:
67
+ # * 0 - no routing code
68
+ # * 5 - zip
69
+ # * 9 - zip + plus4
70
+ # * 11 - zip + plus4 + delivery point
71
+ # The result is an array of [zip, zip4, delivery point]
72
+
73
+ def self.string_to_array(s)
74
+ s = s.gsub(/[\D]/, '')
75
+ match = /^(?:(\d{5})(?:(\d{4})(\d{2})?)?)?$/.match(s)
76
+ unless match
77
+ raise ArgumentError, "Bad routing code: #{s.inspect}"
78
+ end
79
+ zip, plus4, delivery_point = match.to_a[1..-1]
80
+ [zip, plus4, delivery_point]
81
+ end
82
+
83
+ # Validate the value.
84
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
85
+ # @raise ArgumentError if invalid
86
+
87
+ def validate(long_mailer_id)
88
+ end
89
+
90
+ # Add this object's value to target, shifting it left as many
91
+ # digts as are needed to make room.
92
+ # @param [Integer] target The target to be shifted and added to
93
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
94
+ # @return [Integer] The new value of the target
95
+
96
+ def shift_and_add_to(target, long_mailer_id)
97
+ target * 10 ** NUM_DIGITS + convert
98
+ end
99
+
100
+ # @!endgroup
101
+
102
+ protected
103
+
104
+ # Convert to an array of [zip, plus4, delivery point]
105
+
106
+ def to_a
107
+ [@zip, @plus4, @delivery_point]
108
+ end
109
+
110
+ private
111
+
112
+ NUM_DIGITS = 11 #:nodoc:
113
+
114
+ def arg_to_i(o)
115
+ o.andand.to_i
116
+ end
117
+
118
+ # Convert to an integer value
119
+
120
+ def convert
121
+ if @zip && @plus4 && @delivery_point
122
+ @zip * 1000000 + @plus4 * 100 + @delivery_point + 1000100001
123
+ elsif @zip && @plus4
124
+ @zip * 10000 + @plus4 + 100001
125
+ elsif @zip
126
+ @zip + 1
127
+ else
128
+ 0
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,88 @@
1
+ module Imb
2
+
3
+ # This class represents the mail piece's serial number.
4
+
5
+ class SerialNumber
6
+
7
+ # Turn the argument into a SerialNumber if possible. Accepts:
8
+ # * {SerialNumber}
9
+ # * String
10
+ # * Integer
11
+ # @return [SerialNumber]
12
+
13
+ def self.coerce(o)
14
+ case o
15
+ when SerialNumber
16
+ o
17
+ when String
18
+ new(o.to_i)
19
+ when Integer
20
+ new(o)
21
+ else
22
+ raise ArgumentError, 'Cannot coerce to SerialNumber'
23
+ end
24
+ end
25
+
26
+ # @param [Integer] value
27
+
28
+ def initialize(value)
29
+ @value = value
30
+ end
31
+
32
+ # Return true if this object is equal to o
33
+ # @param [Object] o Any object acceptable to {.coerce}
34
+
35
+ def ==(o)
36
+ SerialNumber.coerce(o).to_i == to_i
37
+ rescue ArgumentError
38
+ false
39
+ end
40
+
41
+ # @return [Integer] The value of the serial number
42
+
43
+ def to_i
44
+ @value
45
+ end
46
+
47
+ # @!group Internal
48
+
49
+ # Validate the value.
50
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
51
+ # @raise ArgumentError if invalid
52
+
53
+ def validate(long_mailer_id)
54
+ range = 0..max_value(long_mailer_id)
55
+ unless range === @value
56
+ raise ArgumentError, "Must be #{range}"
57
+ end
58
+ end
59
+
60
+ # Add this object's value to target, shifting it left as many
61
+ # digts as are needed to make room.
62
+ # @param [Integer] target The target to be shifted and added to
63
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
64
+ # @return [Integer] The new value of the target
65
+
66
+ def shift_and_add_to(target, long_mailer_id)
67
+ target * 10 ** num_digits(long_mailer_id) + to_i
68
+ end
69
+
70
+ # @!endgroup
71
+
72
+ private
73
+
74
+ def max_value(long_mailer_id)
75
+ max_value = 10 ** num_digits(long_mailer_id) - 1
76
+ end
77
+
78
+ def num_digits(long_mailer_id)
79
+ if long_mailer_id
80
+ 6
81
+ else
82
+ 9
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,79 @@
1
+ module Imb
2
+
3
+ # This class represents a service type.
4
+
5
+ class ServiceType
6
+
7
+ # The valid range of a service type
8
+ RANGE = 0..999
9
+
10
+ # Turn the argument into a ServiceType if possible. Accepts:
11
+ # * {ServiceType}
12
+ # * String
13
+ # * Integer
14
+
15
+ def self.coerce(o)
16
+ case o
17
+ when ServiceType
18
+ o
19
+ when String
20
+ new(o.to_i)
21
+ when Integer
22
+ new(o)
23
+ else
24
+ raise ArgumentError, 'Cannot coerce to ServiceType'
25
+ end
26
+ end
27
+
28
+ # @param [Integer] value
29
+
30
+ def initialize(value)
31
+ @value = value
32
+ end
33
+
34
+ # Return true if this object is equal to o
35
+ # @param [Object] o Any object acceptable to {.coerce}
36
+
37
+ def ==(o)
38
+ ServiceType.coerce(o).to_i == to_i
39
+ rescue ArgumentError
40
+ false
41
+ end
42
+
43
+ # @return [Integer] The value of the service type
44
+
45
+ def to_i
46
+ @value
47
+ end
48
+
49
+ # @!group Internal
50
+
51
+ # Validate the value.
52
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
53
+ # @raise ArgumentError if invalid
54
+
55
+ def validate(long_mailer_id)
56
+ unless (RANGE) === @value
57
+ raise ArgumentError, "Must be #{RANGE}"
58
+ end
59
+ end
60
+
61
+ # Add this object's value to target, shifting it left as many
62
+ # digts as are needed to make room.
63
+ # @param [Integer] target The target to be shifted and added to
64
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
65
+ # @return [Integer] The new value of the target
66
+
67
+ def shift_and_add_to(target, long_mailer_id)
68
+ target * 10 ** NUM_DIGITS + to_i
69
+ end
70
+
71
+ # @!endgroup
72
+
73
+ private
74
+
75
+ NUM_DIGITS = 3 #:nodoc:
76
+
77
+ end
78
+
79
+ end