upc_tools 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ed3fa91c8d56d01afc5b7c44b768a2fcec6011de
4
+ data.tar.gz: 331515539dd02cab7be75e08befb01c62b0cb607
5
+ SHA512:
6
+ metadata.gz: a6e9c7e542fdfd7aa4f12e16fa1c1e98e958e3fc7c7643706eb3c73f8868223aae0668cc1d95d3323afad1613b935e148e7b622cd6fa8fbc1a4bafbf9a29df6b
7
+ data.tar.gz: 2d46596725e21caf305d2d9fc91f661329ddf3a373810594d8af4ddce0290006d8c9c104169a230d06a214080a1de635c1c4846485b1c898241b11c76ea414a3
data/.gitignore ADDED
@@ -0,0 +1,42 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /spec/reports/
6
+ /test/tmp/
7
+ /test/version_tmp/
8
+ /tmp/
9
+
10
+ ## Documentation cache and generated files:
11
+ /.yardoc/
12
+ /_yardoc/
13
+ /doc/
14
+ /rdoc/
15
+
16
+ ## Environment normalisation:
17
+ /.bundle/
18
+ /lib/bundler/man/
19
+
20
+ # for a library or gem, you might want to ignore these files since the code is
21
+ # intended to run in multiple environments; otherwise, check them in:
22
+ Gemfile.lock
23
+ .ruby-version
24
+ .ruby-gemset
25
+
26
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
27
+ .rvmrc
28
+
29
+ # Mac OS
30
+ .DS_Store
31
+ .AppleDouble
32
+ .LSOverride
33
+ Icon
34
+ ._*
35
+ .Spotlight-V100
36
+ .Trashes
37
+
38
+ #sublime text
39
+ *.sublime-workspace
40
+
41
+ #RubyMine
42
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - "1.9.3"
5
+ - "2.1.9"
6
+ - "2.2.4"
7
+ - "2.3.1"
8
+ - "jruby-9.0.5.0"
9
+ before_install:
10
+ - gem install bundler
11
+ # uncomment this line if your project needs to run something other than `rake`:
12
+ # script: bundle exec rspec spec
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at jhart@onyxraven.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in upc_tools.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ upc_tools (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rake (10.5.0)
11
+ rspec (3.4.0)
12
+ rspec-core (~> 3.4.0)
13
+ rspec-expectations (~> 3.4.0)
14
+ rspec-mocks (~> 3.4.0)
15
+ rspec-core (3.4.4)
16
+ rspec-support (~> 3.4.0)
17
+ rspec-expectations (3.4.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.4.0)
20
+ rspec-mocks (3.4.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.4.0)
23
+ rspec-support (3.4.1)
24
+ yard (0.8.7.6)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bundler (~> 1.12)
31
+ rake (~> 10.0)
32
+ rspec (~> 3.0)
33
+ upc_tools!
34
+ yard
35
+
36
+ BUNDLED WITH
37
+ 1.12.4
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ibotta, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # UpcTools
2
+
3
+ [![Build Status](https://travis-ci.org/Ibotta/ruby_upc_tools.svg?branch=master)](https://travis-ci.org/Ibotta/ruby_upc_tools)
4
+
5
+ Utilities to generate, convert and validate UPCs.
6
+
7
+ Includes handling of type-2 (random weight) UPCs, and conversion between UPC-A and UPC-E formats.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'upc_tools'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install upc_tools
24
+
25
+ ## Usage
26
+
27
+ See the YARD docs
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Ibotta/ruby_upc_tools. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
38
+
39
+
40
+ ## License
41
+
42
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "upc_tools"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ module UpcTools
2
+ VERSION = "0.2.0"
3
+ end
data/lib/upc_tools.rb ADDED
@@ -0,0 +1,289 @@
1
+ require "upc_tools/version"
2
+
3
+ #UPC Tools
4
+ module UpcTools
5
+
6
+ #Generate one UPC check digit
7
+ # @see http://www.gs1.org/barcodes/support/check_digit_calculator/
8
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.1
9
+ # @param num [String] base number to generate check digit for
10
+ # @return [Integer] check digit (always between 0-9)
11
+ def self.generate_upc_check_digit(num)
12
+ even = odd = 0
13
+ #pad everything to max (13)
14
+ num.to_s.rjust(13, '0').split('').each_with_index do |item, index|
15
+ item = item.to_i
16
+ even += item if index.odd? #opposite because of 0 indexing
17
+ odd += item if index.even?
18
+ end
19
+ chk_total = (odd * 3) + even
20
+ (10 - (chk_total % 10)) % 10
21
+ end
22
+
23
+ #Validate UPC check digit
24
+ # @see http://www.gs1.org/barcodes/support/check_digit_calculator/
25
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.1
26
+ # @param upc [String] UPC with check digit to check
27
+ # @return [Boolean] truth of valid check digit
28
+ def self.valid_upc_check_digit?(upc)
29
+ full_upc = upc.to_s.rjust(14, '0') #extend to full 14 digits first
30
+ gen_check = generate_upc_check_digit(full_upc[0, full_upc.size - 1])
31
+ full_upc[-1] == gen_check.to_s
32
+ end
33
+
34
+ #Add check digit and properly pad
35
+ # @param num [String] base number to extend
36
+ # @param extended_length [Integer] resulting target to pad number to
37
+ # @return [String] resulting UPC with check digit
38
+ def self.extend_upc_with_check_digit(num, extended_length=12)
39
+ upc = num.to_s << generate_upc_check_digit(num).to_s
40
+ upc.rjust(extended_length, '0') #extend to at least the given length
41
+ end
42
+
43
+ #Type 2 format 2 | ID3456 | X | 000P | C (Check X is optional, price can overflow in some cases)
44
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.2
45
+
46
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 1 Weight Factor 2-
47
+ WEIGHT_FACTOR_2 = [0,2,4,6,8,9,1,3,5,7]
48
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 2 Weight Factor 3
49
+ WEIGHT_FACTOR_3 = [0,3,6,9,2,5,8,1,4,7]
50
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 2 Weight Factor 5+
51
+ WEIGHT_FACTOR_5plus = [0,5,1,6,2,7,3,8,4,9]
52
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 4 Weight Factor 5-
53
+ WEIGHT_FACTOR_5mins = [0,5,9,4,8,3,7,2,6,1]
54
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 4 inverse Weight Factor 5-
55
+ WEIGHT_FACTOR_5mins_opposite = [0,9,7,5,3,1,8,6,4,2]
56
+
57
+ #Trim UPC to proper length for type2 checking
58
+ # @param upc [String] UPC
59
+ # @return [String] trimmed string
60
+ def self.trim_type2_upc(upc)
61
+ #if length is > 12, strip leading 0
62
+ upc = upc.to_s
63
+ upc = upc.gsub(/^0+/, '') if upc.size > 12
64
+ upc
65
+ end
66
+
67
+ #Is this a type2 UPC?
68
+ # @param upc [String] upc to check
69
+ # @return [Boolean] is UPC a type-2?
70
+ def self.type2_upc?(upc)
71
+ upc = trim_type2_upc(upc)
72
+ return false if upc.size > 13 || upc.size < 12 #length is wrong
73
+ upc.start_with?('2')
74
+ end
75
+
76
+ #Validate UPC and Price check digit for a type 2 upc. Does NOT also check the UPC itself
77
+ # @param upc [String] Type 2 UPC to check with check digit(s)
78
+ # @return [Boolean] matching check digit(s)?
79
+ def self.valid_type2_upc_check_digit?(upc)
80
+ upc = trim_type2_upc(upc)
81
+ return false unless type2_upc?(upc)
82
+ plu, price, chk, price_chk = split_type2_upc(upc)
83
+ price_chk_calc = if price.size == 4
84
+ generate_type2_upc_price_check_digit_4(price)
85
+ elsif price.size == 5
86
+ generate_type2_upc_price_check_digit_5(price)
87
+ else
88
+ raise ArgumentError, "Price is an unknown size"
89
+ end
90
+ price_chk == price_chk_calc.to_s
91
+ end
92
+
93
+ #Convenience method validates that upc is type2 with valid check digit
94
+ # @param upc [String] Type 2 UPC to check
95
+ # @return [Boolean] is UPC a type-2 with valid check digit(s)?
96
+ def self.valid_type2_upc?(upc)
97
+ type2_upc?(upc) && valid_type2_upc_check_digit?(upc)
98
+ end
99
+
100
+ #Convert item ID (PLU) and price to type2 UPC string
101
+ # @param plu [String] item identifier (not including leading 2)
102
+ # @param price [String] price as integer (in cents). Will be 0 padded if necessary
103
+ # @param opts [Hash] options hash
104
+ # @option opts [Integer] :price_length (4) price length (4 or 5). Will override given price length.
105
+ # @option opts [Integer] :upc_length (12) price length (12 or 13)
106
+ def self.item_price_to_type2(plu, price, opts={})
107
+ upc_length = opts[:upc_length] || 12
108
+ price_length = opts[:price_length] || 4
109
+ raise ArgumentError, "opts[:upc_length] must be 12 or 13" if upc_length != 12 && upc_length != 13
110
+
111
+ if upc_length == 13
112
+ raise ArgumentError, "Price length cannot be 4 if UPC length is 13" if opts[:price_length] == 4
113
+ price_length = 5
114
+ raise ArgumentError, "opts[:price_length] must be 4 or 5" if price_length != 4 && price_length != 5
115
+ end
116
+
117
+ plu = plu.to_s
118
+ raise ArgumentError, "plu must be 5 digits long" if plu.size != 5
119
+
120
+ price = price.to_s.rjust(price_length, '0')
121
+ raise ArgumentError, "price must be less than or equal to 5 digits long" if price.size > 5
122
+
123
+ price_chk_calc = if price.size == 4
124
+ generate_type2_upc_price_check_digit_4(price)
125
+ elsif price.size == 5 && upc_length == 13
126
+ generate_type2_upc_price_check_digit_5(price)
127
+ else
128
+ ''
129
+ end
130
+
131
+ upc = "2#{plu}#{price_chk_calc}#{price}"
132
+ upc << generate_upc_check_digit(upc).to_s
133
+ end
134
+
135
+ #Split a Type2 UPC into its component parts
136
+ # @see http://www.meattrack.com/Background/UPC.php
137
+ # @see http://www.iddba.org/upccharacter2.aspx
138
+ # @param upc [String] UPC to split up
139
+ # @param skip_price_check [Boolean] Ignore price check digit (include digit in price field)
140
+ # @return [Array<String>] elements of array: ItemID/PLU (not including leading 2), Price, UPC Check Digit, Price Check Digit
141
+ def self.split_type2_upc(upc, skip_price_check=false)
142
+ upc = trim_type2_upc(upc)
143
+ plu = upc[1,5]
144
+ chk = upc[-1]
145
+ if upc.size == 13 || skip_price_check
146
+ price = upc[-6, 5]
147
+ price_chk = upc[-7] unless skip_price_check
148
+ else
149
+ price = upc[-5,4]
150
+ price_chk = upc[-6] unless skip_price_check
151
+ end
152
+ [plu, price, chk, price_chk]
153
+ end
154
+
155
+ #Get the float price from a Type2 UPC
156
+ # @param upc [String] UPC to get price from
157
+ # @param skip_price_check [Boolean] Ignore price check digit (include digit in price field)
158
+ # @return [Float] calculated price (rounded to nearest cent)
159
+ def self.get_price_from_type2_upc(upc, skip_price_check=false)
160
+ _, price = UpcTools.split_type2_upc(upc, skip_price_check)
161
+ (price.to_f / 100.0).round(2)
162
+ end
163
+
164
+ # Split a type2 UPC into the UPC itself and the price contained therein.
165
+ # @param number [String] upc to check
166
+ # @return [Array<String,Float>] elements of array: type2 UPC string, Price. The UPC ends up with a 0 price if it is type2. The Price will be nil if the number passed in is not type2.
167
+ def self.type2_number_price(number)
168
+ if type2_upc?(number) && valid_type2_upc_check_digit?(number)
169
+ #looks like a type-2 and the price chk is valid
170
+ item_code, price = split_type2_upc(number)
171
+ price = (price.to_f / 100.0).round(2)
172
+
173
+ upc = item_price_to_type2(item_code, 0).rjust(14, '0')
174
+ [upc, price]
175
+ else
176
+ [number, nil]
177
+ end
178
+ end
179
+
180
+ #Generate price check digit for type 2 upc price of 4 digits
181
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.3
182
+ # @see http://barcodes.gs1us.org/GS1%20US%20BarCodes%20and%20eCom%20-%20The%20Global%20Language%20of%20Business.htm
183
+ # @param price [String] price as integer (in cents)
184
+ # @return [Integer] calculated price check digit
185
+ def self.generate_type2_upc_price_check_digit_4(price)
186
+ #digit weighting factors 2-, 2-, 3, 5-
187
+ digits = price.to_s.rjust(4, '0').split('').map(&:to_i)
188
+ sum = 0
189
+ sum += WEIGHT_FACTOR_2[digits[0]]
190
+ sum += WEIGHT_FACTOR_2[digits[1]]
191
+ sum += WEIGHT_FACTOR_3[digits[2]]
192
+ sum += WEIGHT_FACTOR_5mins[digits[3]]
193
+ (sum * 3) % 10
194
+ end
195
+
196
+ #Generate price check digit for type 2 upc price of 5 digits
197
+ # @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.4
198
+ # @param price [String] price as integer (in cents)
199
+ # @return [Integer] calculated price check digit
200
+ def self.generate_type2_upc_price_check_digit_5(price)
201
+ #digit weighting factors 5+, 2-, 5-, 5+, 2- => opposite of 5-
202
+ digits = price.to_s.rjust(5, '0').split('').map(&:to_i)
203
+ sum = 0
204
+ sum += WEIGHT_FACTOR_5plus[digits[0]]
205
+ sum += WEIGHT_FACTOR_2[digits[1]]
206
+ sum += WEIGHT_FACTOR_5mins[digits[2]]
207
+ sum += WEIGHT_FACTOR_5plus[digits[3]]
208
+ sum += WEIGHT_FACTOR_2[digits[4]]
209
+ sum = (10 - (sum % 10)) % 10
210
+ WEIGHT_FACTOR_5mins_opposite[sum]
211
+ end
212
+
213
+ # UPC-E = ABCDEFGH
214
+ # seen as 0 123456 7
215
+ # maps as A BCDEFG H
216
+ # ABC and H never change
217
+
218
+ #position conversions
219
+ # G = 0 XXNNN0 A + BC000-00DEF + H
220
+ # G = 1 XXNNN1 A + BC100-00DEF + H
221
+ # G = 2 XXNNN2 A + BC200-00DEF + H
222
+ # G = 3 XXXNN3 A + BCD00-000EF + H
223
+ # G = 4 XXXXN4 A + BCDE0-0000F + H
224
+ # G = 5 XXXXX5 A + BCDEF-00005 + H
225
+ # G = 6 XXXXX6 A + BCDEF-00006 + H
226
+ # G = 7 XXXXX7 A + BCDEF-00007 + H
227
+ # G = 8 XXXXX8 A + BCDEF-00008 + H
228
+ # G = 9 XXXXX9 A + BCDEF-00009 + H
229
+
230
+ #Convert short (8 digit) UPC-E to 12 digit UPC-A
231
+ # @see http://www.taltech.com/barcodesoftware/symbologies/upc
232
+ # @see http://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
233
+ # @param upc_e [String] 8 digit UPC-E to convert
234
+ # @return [String] 12 digit UPC-A
235
+ def self.convert_upce_to_upca(upc_e)
236
+ #todo should i zero pad upc_e?
237
+ #todo allow without check digit?
238
+ upc_e = upc_e.to_s
239
+ raise ArgumentError, "UPC-E must be 8 digits" unless upc_e.size == 8
240
+
241
+ map_id = upc_e[-2].to_i #G
242
+ chk = upc_e[-1] #H
243
+ prefix = upc_e[0,3] #ABC
244
+ prefix_next = upc_e[3,3] #DEF
245
+
246
+ if map_id >= 5
247
+ "#{prefix}#{prefix_next}0000#{map_id}#{chk}"
248
+ elsif map_id <= 2
249
+ "#{prefix}#{map_id}0000#{prefix_next}#{chk}"
250
+ elsif map_id == 3
251
+ "#{prefix}#{upc_e[3]}00000#{upc_e[4,2]}#{chk}"
252
+ elsif map_id == 4
253
+ "#{prefix}#{upc_e[3,2]}00000#{upc_e[5]}#{chk}"
254
+ end
255
+ end
256
+
257
+ #Convert (zero-suppress) 12 digit UPC-A to 8 digit UPC-E
258
+ # @see http://www.taltech.com/barcodesoftware/symbologies/upc
259
+ # @see http://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
260
+ # @see http://www.barcodeisland.com/upce.phtml#Conversion
261
+ # @param upc_a [String] 12 digit UPC-A to convert
262
+ # @return [String] 8 digit UPC-E
263
+ def self.convert_upca_to_upce(upc_a)
264
+ #todo should i zero pad upc_a?
265
+ #todo allow without check digit?
266
+ upc_a = upc_a.to_s
267
+ raise ArgumentError, "Must be 12 characters long" unless upc_a.size == 12
268
+ start = upc_a[0] #first char
269
+ raise ArgumentError, "Must be type 0 or 1" unless ["0", "1"].include?(start)
270
+
271
+ chk = upc_a[-1] #last char
272
+ mfr = upc_a[1...6] #next 5 characters
273
+ prod = upc_a[6...11] #last 4 characters w/o chk
274
+
275
+ upc_e = if ["000", "100", "200"].include?(mfr[-3,3])
276
+ "#{mfr[0,2]}#{prod[-3,3]}#{mfr[2]}"
277
+ elsif mfr[-2,2] == '00' && prod.to_i <= 99
278
+ "#{mfr[0,3]}#{prod[-2,2]}3"
279
+ elsif mfr[-1] == '0' && prod.to_i <= 9
280
+ "#{mfr[0,4]}#{prod[-1]}4"
281
+ elsif mfr[-1] != '0' && [5,6,7,8,9].include?(prod.to_i)
282
+ "#{mfr}#{prod[-1]}"
283
+ end
284
+ raise ArgumentError, "Must meet formatting requirements" unless upc_e
285
+
286
+ "#{start}#{upc_e}#{chk}"
287
+ end
288
+
289
+ end
data/upc_tools.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'upc_tools/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "upc_tools"
8
+ spec.version = UpcTools::VERSION
9
+ spec.authors = ["Justin Hart", "Ibotta, Inc."]
10
+ spec.email = ["jhart@ibotta.com"]
11
+ spec.summary = %q{UPC validation and creation utilities}
12
+ spec.description = %q{create, validate, convert UPCs}
13
+ spec.homepage = "https://github.com/Ibotta/ruby_upc_tools"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "yard"
25
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: upc_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Hart
8
+ - Ibotta, Inc.
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2016-05-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.12'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.12'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: yard
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: create, validate, convert UPCs
71
+ email:
72
+ - jhart@ibotta.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - Guardfile
84
+ - LICENSE
85
+ - README.md
86
+ - Rakefile
87
+ - bin/console
88
+ - bin/setup
89
+ - lib/upc_tools.rb
90
+ - lib/upc_tools/version.rb
91
+ - upc_tools.gemspec
92
+ homepage: https://github.com/Ibotta/ruby_upc_tools
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.2.5
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: UPC validation and creation utilities
116
+ test_files: []
117
+ has_rdoc: