usps_intelligent_barcode 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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