├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── barkick.gemspec ├── lib ├── barkick.rb └── barkick │ ├── gtin.rb │ └── version.rb └── test ├── barkick_test.rb └── test_helper.rb /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: ruby/setup-ruby@v1 9 | with: 10 | ruby-version: 3.4 11 | bundler-cache: true 12 | - run: bundle exec rake test 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.1 (2025-01-30) 2 | 3 | - Fixed `valid?` method returning `nil` instead of `false` in some cases 4 | 5 | ## 0.3.0 (2024-10-23) 6 | 7 | - Dropped support for Ruby < 3.1 8 | 9 | ## 0.2.0 (2022-10-09) 10 | 11 | - Dropped support for Ruby < 2.7 12 | 13 | ## 0.1.0 (2018-07-27) 14 | 15 | - `GTIN` is now `Barkick::GTIN` 16 | - 8-digit codes now raise an `ArgumentError` if `type` is not specified 17 | - Added `type` option 18 | 19 | ## 0.0.4 (2014-04-17) 20 | 21 | - Fixed false positives for `valid?` 22 | 23 | ## 0.0.3 (2014-01-22) 24 | 25 | - Added support for UPC-E 26 | - Added `country_code` method 27 | 28 | ## 0.0.2 (2013-09-29) 29 | 30 | - Added more prefixes 31 | 32 | ## 0.0.1 (2013-09-19) 33 | 34 | - First release 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rake" 6 | gem "minitest" 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025 Andrew Kane 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Barkick 2 | 3 | Barcodes made easy 4 | 5 | Works with: 6 | 7 | - [UPC](https://en.wikipedia.org/wiki/Universal_Product_Code) (UPC-A and UPC-E) 8 | - [EAN](https://en.wikipedia.org/wiki/International_Article_Number_%28EAN%29) (EAN-13 and EAN-8) 9 | - [GTIN](https://en.wikipedia.org/wiki/Global_Trade_Item_Number) 10 | - [ISBN](https://en.wikipedia.org/wiki/International_Standard_Book_Number) 11 | 12 | For PLU codes, check out the [plu gem](https://github.com/ankane/plu) 13 | 14 | [![Build Status](https://github.com/ankane/barkick/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/barkick/actions) 15 | 16 | ## Installation 17 | 18 | Add this line to your Gemfile: 19 | 20 | ```ruby 21 | gem "barkick" 22 | ``` 23 | 24 | ## How To Use 25 | 26 | ```ruby 27 | gtin = Barkick::GTIN.new("016000275263") 28 | gtin.valid? # true 29 | gtin.gtin14 # "00016000275263" 30 | gtin.ean13 # "0016000275263" 31 | gtin.upc # "016000275263" 32 | gtin.prefix # "001" 33 | gtin.prefix_name # "GS1 US" 34 | gtin.country_code # "US" 35 | ``` 36 | 37 | Variable items 38 | 39 | ```ruby 40 | gtin = Barkick::GTIN.new("299265108631") 41 | gtin.variable? # true 42 | gtin.restricted? # true 43 | gtin.price # 8.63 44 | gtin.base_gtin14 # "00299265000003" 45 | ``` 46 | 47 | UPC-E 48 | 49 | ```ruby 50 | gtin = Barkick::GTIN.new("03744806", type: :upc_e) 51 | gtin.base_gtin14 # "00037000004486" 52 | ``` 53 | 54 | EAN-8 55 | 56 | ```ruby 57 | gtin = Barkick::GTIN.new("01234565", type: :ean8) 58 | gtin.base_gtin14 # "00000001234565" 59 | ``` 60 | 61 | Calculate check digit 62 | 63 | ```ruby 64 | Barkick::GTIN.check_digit("01600027526") # "3" 65 | ``` 66 | 67 | > For UPC-E, convert to UPC-A before passing to this method 68 | 69 | ## Resources 70 | 71 | - [GS1 General Specifications](https://www.gs1.org/docs/barcodes/GS1_General_Specifications.pdf) 72 | 73 | ## History 74 | 75 | View the [changelog](https://github.com/ankane/barkick/blob/master/CHANGELOG.md) 76 | 77 | ## Contributing 78 | 79 | Everyone is encouraged to help improve this project. Here are a few ways you can help: 80 | 81 | - [Report bugs](https://github.com/ankane/barkick/issues) 82 | - Fix bugs and [submit pull requests](https://github.com/ankane/barkick/pulls) 83 | - Write, clarify, or fix documentation 84 | - Suggest or add new features 85 | 86 | To get started with development: 87 | 88 | ```sh 89 | git clone https://github.com/ankane/barkick.git 90 | cd barkick 91 | bundle install 92 | bundle exec rake test 93 | ``` 94 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | task default: :test 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.pattern = "test/**/*_test.rb" 8 | end 9 | -------------------------------------------------------------------------------- /barkick.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/barkick/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "barkick" 5 | spec.version = Barkick::VERSION 6 | spec.summary = "Barcodes made easy" 7 | spec.homepage = "https://github.com/ankane/barkick" 8 | spec.license = "MIT" 9 | 10 | spec.author = "Andrew Kane" 11 | spec.email = "andrew@ankane.org" 12 | 13 | spec.files = Dir["*.{md,txt}", "{lib}/**/*"] 14 | spec.require_path = "lib" 15 | 16 | spec.required_ruby_version = ">= 3.1" 17 | end 18 | -------------------------------------------------------------------------------- /lib/barkick.rb: -------------------------------------------------------------------------------- 1 | require_relative "barkick/gtin" 2 | require_relative "barkick/version" 3 | -------------------------------------------------------------------------------- /lib/barkick/gtin.rb: -------------------------------------------------------------------------------- 1 | module Barkick 2 | class GTIN 3 | def initialize(number, type: nil) 4 | @number = number.to_s 5 | 6 | if @number.length == 8 7 | raise ArgumentError, "Must specify type for 8-digit codes" unless type 8 | 9 | if type == :upc_e 10 | upc_a = 11 | case @number[-2] 12 | when "0" 13 | "#{@number[1..2]}00000#{@number[3..5]}" 14 | when "1", "2" 15 | "#{@number[1..2]}#{@number[-2]}0000#{@number[3..5]}" 16 | when "3" 17 | "#{@number[1..3]}00000#{@number[4..5]}" 18 | when "4" 19 | "#{@number[1..4]}00000#{@number[5]}" 20 | else 21 | "#{@number[1..5]}0000#{@number[-2]}" 22 | end 23 | 24 | upc_a = "0#{upc_a}#{@number[-1]}" 25 | 26 | if self.class.check_digit(upc_a[0..-2]) == @number[-1] 27 | @number = upc_a 28 | end 29 | end 30 | end 31 | 32 | @type = type 33 | end 34 | 35 | def gtin14 36 | if valid? 37 | @number.rjust(14, "0") 38 | else 39 | nil 40 | end 41 | end 42 | 43 | def gtin13 44 | gtin14[0] == "0" ? gtin14[1..-1] : nil 45 | end 46 | alias_method :ean13, :gtin13 47 | 48 | def gtin12 49 | gtin14[0..1] == "00" ? gtin14[2..-1] : nil 50 | end 51 | alias_method :upc, :gtin12 52 | 53 | def check_digit 54 | gtin14[-1] 55 | end 56 | 57 | def valid? 58 | @number.match?(/\A\d{8}(\d{4,6})?\z/) && self.class.check_digit(@number.rjust(14, "0")[0..-2]) == @number[-1] 59 | end 60 | 61 | def base_gtin14 62 | if variable? 63 | base = gtin14[0..-7] + "00000" 64 | base + self.class.check_digit(base) 65 | else 66 | gtin14 67 | end 68 | end 69 | 70 | # prefix 71 | 72 | def prefix 73 | gtin14[1..3] 74 | end 75 | 76 | # https://www.gs1.org/barcodes/support/prefix_list 77 | def prefix_name 78 | case prefix.to_i 79 | when 0..19, 30..39, 60..139 80 | if @type == :ean8 81 | nil # GTIN-8 82 | else 83 | "GS1 US" 84 | end 85 | when 20..29, 40..49, 200..299 then "Restricted distribution" 86 | when 50..59 then "Coupons" 87 | when 300..379 then "GS1 France" 88 | when 380 then "GS1 Bulgaria" 89 | when 383 then "GS1 Slovenija" 90 | when 385 then "GS1 Croatia" 91 | when 387 then "GS1 BIH (Bosnia-Herzegovina)" 92 | when 389 then "GS1 Montenegro" 93 | when 400..440 then "GS1 Germany" 94 | when 450..459, 490..499 then "GS1 Japan" 95 | when 460..469 then "GS1 Russia" 96 | when 470 then "GS1 Kyrgyzstan" 97 | when 471 then "GS1 Taiwan" 98 | when 474 then "GS1 Estonia" 99 | when 475 then "GS1 Latvia" 100 | when 476 then "GS1 Azerbaijan" 101 | when 477 then "GS1 Lithuania" 102 | when 478 then "GS1 Uzbekistan" 103 | when 479 then "GS1 Sri Lanka" 104 | when 480 then "GS1 Philippines" 105 | when 481 then "GS1 Belarus" 106 | when 482 then "GS1 Ukraine" 107 | when 484 then "GS1 Moldova" 108 | when 485 then "GS1 Armenia" 109 | when 486 then "GS1 Georgia" 110 | when 487 then "GS1 Kazakstan" 111 | when 488 then "GS1 Tajikistan" 112 | when 489 then "GS1 Hong Kong" 113 | when 500..509 then "GS1 UK" 114 | when 520..521 then "GS1 Association Greece" 115 | when 528 then "GS1 Lebanon" 116 | when 529 then "GS1 Cyprus" 117 | when 530 then "GS1 Albania" 118 | when 531 then "GS1 MAC (FYR Macedonia)" 119 | when 535 then "GS1 Malta" 120 | when 539 then "GS1 Ireland" 121 | when 540..549 then "GS1 Belgium & Luxembourg" 122 | when 560 then "GS1 Portugal" 123 | when 569 then "GS1 Iceland" 124 | when 570..579 then "GS1 Denmark" 125 | when 590 then "GS1 Poland" 126 | when 594 then "GS1 Romania" 127 | when 599 then "GS1 Hungary" 128 | when 600..601 then "GS1 South Africa" 129 | when 603 then "GS1 Ghana" 130 | when 604 then "GS1 Senegal" 131 | when 608 then "GS1 Bahrain" 132 | when 609 then "GS1 Mauritius" 133 | when 611 then "GS1 Morocco" 134 | when 613 then "GS1 Algeria" 135 | when 615 then "GS1 Nigeria" 136 | when 616 then "GS1 Kenya" 137 | when 618 then "GS1 Ivory Coast" 138 | when 619 then "GS1 Tunisia" 139 | when 620 then "GS1 Tanzania" 140 | when 621 then "GS1 Syria" 141 | when 622 then "GS1 Egypt" 142 | when 623 then "GS1 Brunei" 143 | when 624 then "GS1 Libya" 144 | when 625 then "GS1 Jordan" 145 | when 626 then "GS1 Iran" 146 | when 627 then "GS1 Kuwait" 147 | when 628 then "GS1 Saudi Arabia" 148 | when 629 then "GS1 Emirates" 149 | when 640..649 then "GS1 Finland" 150 | when 690..699 then "GS1 China" 151 | when 700..709 then "GS1 Norway" 152 | when 729 then "GS1 Israel" 153 | when 730..739 then "GS1 Sweden" 154 | when 740 then "GS1 Guatemala" 155 | when 741 then "GS1 El Salvador" 156 | when 742 then "GS1 Honduras" 157 | when 743 then "GS1 Nicaragua" 158 | when 744 then "GS1 Costa Rica" 159 | when 745 then "GS1 Panama" 160 | when 746 then "GS1 Republica Dominicana" 161 | when 750 then "GS1 Mexico" 162 | when 754..755 then "GS1 Canada" 163 | when 759 then "GS1 Venezuela" 164 | when 760..769 then "GS1 Schweiz, Suisse, Svizzera" 165 | when 770..771 then "GS1 Colombia" 166 | when 773 then "GS1 Uruguay" 167 | when 775 then "GS1 Peru" 168 | when 777 then "GS1 Bolivia" 169 | when 778..779 then "GS1 Argentina" 170 | when 780 then "GS1 Chile" 171 | when 784 then "GS1 Paraguay" 172 | when 786 then "GS1 Ecuador" 173 | when 789..790 then "GS1 Brasil" 174 | when 800..839 then "GS1 Italy" 175 | when 840..849 then "GS1 Spain" 176 | when 850 then "GS1 Cuba" 177 | when 858 then "GS1 Slovakia" 178 | when 859 then "GS1 Czech" 179 | when 860 then "GS1 Serbia" 180 | when 865 then "GS1 Mongolia" 181 | when 867 then "GS1 North Korea" 182 | when 868..869 then "GS1 Turkey" 183 | when 870..879 then "GS1 Netherlands" 184 | when 880 then "GS1 South Korea" 185 | when 884 then "GS1 Cambodia" 186 | when 885 then "GS1 Thailand" 187 | when 888 then "GS1 Singapore" 188 | when 890 then "GS1 India" 189 | when 893 then "GS1 Vietnam" 190 | when 896 then "GS1 Pakistan" 191 | when 899 then "GS1 Indonesia" 192 | when 900..919 then "GS1 Austria" 193 | when 930..939 then "GS1 Australia" 194 | when 940..949 then "GS1 New Zealand" 195 | when 950 then "GS1 Global Office" 196 | when 951 then "GS1 Global Office (EPCglobal)" 197 | when 955 then "GS1 Malaysia" 198 | when 958 then "GS1 Macau" 199 | when 960..969 then "Global Office (GTIN-8s)" 200 | when 977 then "Serial publications (ISSN)" 201 | when 978..979 then "Bookland" 202 | when 980 then "Refund receipts" 203 | when 981..984 then "GS1 coupon identification for common currency areas" 204 | when 990..999 then "GS1 coupon identification" 205 | end 206 | end 207 | 208 | def country_code 209 | case prefix_name 210 | when "GS1 US" then "US" 211 | when "GS1 UK" then "GB" 212 | when "GS1 Germany" then "DE" 213 | when "GS1 Netherlands" then "NL" 214 | when "GS1 Schweiz, Suisse, Svizzera" then "CH" 215 | when "GS1 Italy" then "IT" 216 | when "GS1 France" then "FR" 217 | end 218 | end 219 | 220 | def book? 221 | prefix_name == "Bookland" 222 | end 223 | 224 | def restricted? 225 | prefix_name == "Restricted distribution" 226 | end 227 | 228 | # variable weight 229 | 230 | def variable? 231 | (20..29).cover?(prefix.to_i) 232 | end 233 | 234 | def price 235 | if variable? 236 | gtin14[-5..-2].to_f / 100 237 | else 238 | nil 239 | end 240 | end 241 | 242 | def self.check_digit(number) 243 | number = number.to_s 244 | if [7, 11, 12, 13].include?(number.length) 245 | # https://www.gs1.org/barcodes/support/check_digit_calculator#how 246 | digits = number.rjust(13, "0").split("").map(&:to_i) 247 | # digit at position 0 is odd (first digit) for the purpose of this calculation 248 | odd_digits, even_digits = digits.partition.each_with_index { |_digit, i| i.even? } 249 | ((10 - (odd_digits.sum * 3 + even_digits.sum) % 10) % 10).to_s 250 | else 251 | nil 252 | end 253 | end 254 | end 255 | end 256 | -------------------------------------------------------------------------------- /lib/barkick/version.rb: -------------------------------------------------------------------------------- 1 | module Barkick 2 | VERSION = "0.3.1" 3 | end 4 | -------------------------------------------------------------------------------- /test/barkick_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class BarkickTest < Minitest::Test 4 | def test_gtin 5 | gtin = Barkick::GTIN.new("016000275263") 6 | assert_equal true, gtin.valid? 7 | assert_equal "00016000275263", gtin.gtin14 8 | assert_equal "0016000275263", gtin.gtin13 9 | assert_equal "016000275263", gtin.gtin12 10 | assert_equal "0016000275263", gtin.ean13 11 | assert_equal "016000275263", gtin.upc 12 | assert_equal "001", gtin.prefix 13 | assert_equal "GS1 US", gtin.prefix_name 14 | end 15 | 16 | def test_invalid 17 | assert_equal false, Barkick::GTIN.new("1").valid? 18 | assert_equal false, Barkick::GTIN.new(" 016000275263").valid? 19 | end 20 | 21 | def test_variable 22 | gtin = Barkick::GTIN.new("299265108631") 23 | assert_equal true, gtin.valid? 24 | assert_equal true, gtin.variable? 25 | assert_equal true, gtin.restricted? 26 | assert_equal 8.63, gtin.price 27 | assert_equal "00299265000003", gtin.base_gtin14 28 | assert_equal true, Barkick::GTIN.new(gtin.base_gtin14).valid? 29 | end 30 | 31 | def test_no_type 32 | assert_raises(ArgumentError) do 33 | Barkick::GTIN.new("03744806") 34 | end 35 | end 36 | 37 | def test_upc_e 38 | gtin = Barkick::GTIN.new("03744806", type: :upc_e) 39 | assert_equal true, gtin.valid? 40 | assert_equal "00037000004486", gtin.base_gtin14 41 | end 42 | 43 | def test_ean8 44 | gtin = Barkick::GTIN.new("00511292", type: :ean8) 45 | assert_equal "00000000511292", gtin.gtin14 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | Bundler.require(:default) 3 | require "minitest/autorun" 4 | require "minitest/pride" 5 | --------------------------------------------------------------------------------