USPS-intelligent-barcode 0.1.0

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