barcodevalidation 2.5.0 → 2.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1472674218493f67abaf42f2c94c4009ca1aabb07180cee0bfd9ef3c6c2fed5
4
- data.tar.gz: f8aa280b1ca3ad091d13c4a1391d7598b82cbcd5db3d9a3c92c65cdb4ce2407b
3
+ metadata.gz: b6949efb809df8a20b1d2816bd27f5fada9ed3273f5ed52e1008c85169a29151
4
+ data.tar.gz: 120483828346597e8e418737d8bba2f7348104117008caf2e69245720f4f9eaa
5
5
  SHA512:
6
- metadata.gz: 87096371c819a9efa4f2493edf47ae2d21cccb16922e9b1661aa11de5fcf82aab7a2031ba8ec2660d6026003f905488c793d996c65bc21ab3e0fd44255e672f4
7
- data.tar.gz: 7eaebe15a7d9ff6c8922b46ec6c1cea2b392235c5b2d245038456ae10c3e72618c5b20c465f1f13094cda06fae3aa123dd3c1fdd189c8f717e9fa04875c6c390
6
+ metadata.gz: 451437593aad8dbf1ccd2c180be239c5b87c7503760cccae4e37583e1fdde899ddcbd8b3a3ab5eb4b53a1e1c6e6f7c3ae7c5155bfab6bd2ea83839c6da092a47
7
+ data.tar.gz: 84024a6d521aa3dbe7a205d37fbb3f7c5efaec448fb2ab6d43a8b4e58b2d9ccae349ba91020cec18f2d02a4806a8ff646a6eb33e1494dd5f2476d50335a6675a
data/README.md CHANGED
@@ -63,6 +63,46 @@ bad.to_all_valid # => []
63
63
  ```
64
64
 
65
65
 
66
+ Custom GTINs
67
+ ------------
68
+
69
+ If the standard GTINs provided are not enough for your needs, you can implement your own by subclassing `BarcodeValidation::GTIN::Base` or any of its subclasses. If your custom class overlaps with a default class or one of your other custom classes, you can declare `prioritize_before <other class>` to re-order their evaluation order.
70
+
71
+ An example:
72
+
73
+ ```ruby
74
+ # A custom class that handles any length GTIN as long as it starts with "123".
75
+ # Note that we must still provide a VALID_LENGTH to allow transcoding to other GTINs by zero-padding.
76
+ # Due to this inheriting from Base, it is automatically registered and added to the end of the list of classes to check if it `handles?` an input.
77
+ class MyCustomGTIN < BarcodeValidation::GTIN::Base
78
+ VALID_LENGTH = 20
79
+
80
+ def self.handles?(input)
81
+ input.start_with?("123") && input.length <= VALID_LENGTH
82
+ end
83
+
84
+ # Custom validity check
85
+ def valid?
86
+ self.class.handles?(input) && check_digit.valid?
87
+ end
88
+ end
89
+
90
+ # A custom implementation of GTIN13, which addresses a subset of the GTIN13 range.
91
+ class MyCustomGTIN13 < BarcodeValidation::GTIN::GTIN13
92
+ # Ensure we get a chance to handle GTINs before our parent,
93
+ # so we can handle the subset we care about and have our parent handle the rest.
94
+ prioritize_before BarcodeValidation::GTIN::GTIN13
95
+
96
+ def self.handles?(input)
97
+ input.start_with?("123") && super
98
+ end
99
+
100
+ def valid?
101
+ input.start_with?("123") && super
102
+ end
103
+ end
104
+ ```
105
+
66
106
 
67
107
  Development
68
108
  -----------
@@ -94,11 +134,10 @@ bin/rake
94
134
 
95
135
  #### Code Quality Checks
96
136
 
97
- Rubocop is used to enforce coding standards.
137
+ [StandardRB](https://github.com/standardrb/standard) is used to enforce coding standards.
98
138
 
99
139
  ```
100
- bin/rubocop
101
- bin/rubocop --help
140
+ bin/standardrb
102
141
  ```
103
142
 
104
143
 
@@ -123,7 +162,7 @@ Gem, created using `bundler gem barcodevalidation`.
123
162
  * `bundle`: Runs Bundler, in the correct way
124
163
  * `console`: development console (equiv. to `bin/bundle exec pry`)
125
164
  * `rake`: Runs Rake (equivalent to `bin/bundle exec rake`)
126
- * `rubocop`: Runs Rubocop (equivalent to `bin/bundle exec rubocop`)
165
+ * `standardrb`: Runs standardrb (equivalent to `bin/bundle exec standardrb`)
127
166
  * `setup`: Sets up the project to be ready for development
128
167
  * `config/boot.rb`: Prepares dependencies before loading the library
129
168
  * `lib/`: Source files; this directory is added to Ruby's load path
@@ -5,23 +5,23 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "barcodevalidation/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.required_ruby_version = ">= 2.5"
9
- spec.name = "barcodevalidation"
10
- spec.version = BarcodeValidation::VERSION
11
- spec.authors = ["Marketplacer"]
12
- spec.email = ["it@marketplacer.com"]
8
+ spec.required_ruby_version = ">= 3.1"
9
+ spec.name = "barcodevalidation"
10
+ spec.version = BarcodeValidation::VERSION
11
+ spec.authors = ["Marketplacer"]
12
+ spec.email = ["it@marketplacer.com"]
13
13
 
14
- spec.summary = "Parses and validates barcodes"
15
- spec.description = "A RubyGem to parse and validate barcodes"
16
- spec.homepage = "https://github.com/marketplacer/#{spec.name}"
17
- spec.license = "MIT"
14
+ spec.summary = "Parses and validates barcodes"
15
+ spec.description = "A RubyGem to parse and validate barcodes"
16
+ spec.homepage = "https://github.com/marketplacer/#{spec.name}"
17
+ spec.license = "MIT"
18
18
 
19
- spec.files = %w[LICENSE.md README.md barcodevalidation.gemspec
20
- config/*.rb lib/**/*.rb]
21
- .flat_map { |pattern| Dir.glob(pattern) }
22
- .reject { |f| File.directory?(f) }
23
- spec.bindir = "exe"
24
- spec.executables = spec.files
25
- .grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.files = %w[LICENSE.md README.md barcodevalidation.gemspec
20
+ config/*.rb lib/**/*.rb]
21
+ .flat_map { |pattern| Dir.glob(pattern) }
22
+ .reject { |f| File.directory?(f) }
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files
25
+ .grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
27
  end
data/config/boot.rb CHANGED
@@ -24,9 +24,9 @@ require "pathname"
24
24
  project_root = Pathname.new(__FILE__).parent.parent
25
25
 
26
26
  # Add lib directory to load path
27
- lib = project_root + "lib"
27
+ lib = project_root / "lib"
28
28
  $LOAD_PATH.unshift(lib.to_s) unless $LOAD_PATH.include?(lib.to_s)
29
29
 
30
30
  # Tell Bundler explicitly which Gemfile to use
31
- ENV["BUNDLE_GEMFILE"] ||= (project_root + "Gemfile").to_s
31
+ ENV["BUNDLE_GEMFILE"] ||= (project_root / "Gemfile").to_s
32
32
  require "bundler"
@@ -18,7 +18,7 @@ module BarcodeValidation
18
18
  raise Digit::ArgumentError, input
19
19
  end
20
20
 
21
- alias to_i __getobj__
21
+ alias_method :to_i, :__getobj__
22
22
 
23
23
  ArgumentError = Error::ArgumentErrorClass.new(self)
24
24
  end
@@ -31,6 +31,10 @@ module BarcodeValidation
31
31
  end
32
32
  end
33
33
 
34
+ def all_zeros?
35
+ all? { |digit| digit.to_i.zero? }
36
+ end
37
+
34
38
  def *(other)
35
39
  self.class.new(super)
36
40
  end
@@ -15,7 +15,7 @@ module BarcodeValidation
15
15
  end
16
16
 
17
17
  def initialize(input)
18
- super "invalid value for #{klass}(): #{input.inspect}"
18
+ super("invalid value for #{klass}(): #{input.inspect}")
19
19
  end
20
20
  end
21
21
  end
@@ -19,12 +19,49 @@ module BarcodeValidation
19
19
  BarcodeValidation::InvalidGTIN.new(input, error: e)
20
20
  end
21
21
 
22
+ # Does this class (potentially) handle a GTIN that matches the input?
23
+ # Subclasses can choose to implement their own logic. The default is to look at +VALID_LENGTH+ and use that to match the length of the input the class handles.
24
+ def self.handles?(input)
25
+ return false unless const_defined?(:VALID_LENGTH)
26
+
27
+ input.length == self::VALID_LENGTH
28
+ end
29
+
30
+ # Upon inheritance, register the subclass so users of the library can dynamically add more GTINs in their own code.
31
+ def self.inherited(subclass)
32
+ BarcodeValidation::GTIN.append_gtin_class(subclass)
33
+ super
34
+ end
35
+
36
+ # Ensure this class is earlier in the GTIN classes list than +other_gtin_class+ and thus will get asked earlier if it handles a GTIN.
37
+ def self.prioritize_before(other_gtin_class)
38
+ unless GTIN.gtin_class?(other_gtin_class)
39
+ raise ArgumentError,
40
+ "The class you want to prioritize before is not a registered prioritized GTIN class."
41
+ end
42
+
43
+ GTIN.reprioritize_before(self, other_gtin_class)
44
+ end
45
+
46
+ # This class is abstract and should not be included in the list of GTIN classes that actually implement a GTIN.
47
+ def self.abstract_class
48
+ BarcodeValidation::GTIN.remove_gtin_class(self)
49
+ end
50
+
51
+ # GTIN::Base is an abstract class. See GTIN8/12/13/14 for implementations of actual GTINs.
52
+ abstract_class
53
+
22
54
  def valid?
23
- valid_length == length && check_digit.valid?
55
+ valid_length == length && !all_zeros? && check_digit.valid?
24
56
  end
25
57
 
26
58
  def valid_length
27
- raise(AbstractMethodError, "Concrete classes must define the VALID_LENGTH constant") unless self.class.const_defined?(:VALID_LENGTH)
59
+ unless self.class.const_defined?(:VALID_LENGTH)
60
+ raise(
61
+ AbstractMethodError,
62
+ "Concrete classes must define the VALID_LENGTH constant"
63
+ )
64
+ end
28
65
 
29
66
  self.class::VALID_LENGTH
30
67
  end
@@ -36,7 +73,7 @@ module BarcodeValidation
36
73
  to_gtin_8,
37
74
  to_gtin_12,
38
75
  to_gtin_13,
39
- to_gtin_14,
76
+ to_gtin_14
40
77
  ].select(&:valid?)
41
78
  end
42
79
 
@@ -84,14 +121,14 @@ module BarcodeValidation
84
121
  # BarcodeValidation::InvalidGTIN with valid? = false and a meaningful
85
122
  # error message.
86
123
  def transcode_to(klass)
87
- gtin = klass.new(format("%0#{klass::VALID_LENGTH}d", to_s.gsub(/^0+/, "")))
124
+ gtin = klass.new(format("%0#{klass::VALID_LENGTH}d", to_s.gsub(/^0+(?=.)/, "")))
88
125
 
89
126
  if gtin.valid?
90
127
  gtin
91
128
  else
92
129
  BarcodeValidation::InvalidGTIN.new(
93
130
  input,
94
- error: klass::ConversionError.new(klass).exception(input),
131
+ error: klass::ConversionError.new(klass).exception(input)
95
132
  )
96
133
  end
97
134
  end
@@ -2,27 +2,62 @@
2
2
 
3
3
  require "forwardable"
4
4
  require_relative "invalid_gtin"
5
- require_relative "gtin/base"
6
- require_relative "gtin/check_digit"
7
- require_relative "gtin/gtin8"
8
- require_relative "gtin/gtin12"
9
- require_relative "gtin/gtin13"
10
- require_relative "gtin/gtin14"
11
5
 
12
6
  module BarcodeValidation
7
+ # GTIN is responsible for wrapping input in an appropriate GTIN::Base sub-class.
8
+ # An important part of this involves managing the prioritized list of GTIN classes we use for handling input.
9
+ # The methods implemented here are used by GTIN::Base to manage this list and prioritize classes.
13
10
  module GTIN
14
11
  class << self
15
12
  def new(input)
16
13
  (class_for_input(input) || BarcodeValidation::InvalidGTIN).new(input)
17
14
  end
18
15
 
16
+ # Adds the provided class to the back of the list of prioritized GTIN classes.
17
+ def append_gtin_class(gtin_class)
18
+ prioritized_gtin_classes.push(gtin_class) unless gtin_class?(gtin_class)
19
+ nil
20
+ end
21
+
22
+ # Ensure the provided class is removed from the list of prioritized GTIN classes.
23
+ def remove_gtin_class(gtin_class)
24
+ prioritized_gtin_classes.delete(gtin_class)
25
+ nil
26
+ end
27
+
28
+ # Is this a registered prioritized GTIN class?
29
+ # @return [true, false]
30
+ def gtin_class?(gtin_class)
31
+ prioritized_gtin_classes.include?(gtin_class)
32
+ end
33
+
34
+ # @param [Class] high_priority_class The higher priority GTIN class you want to move before the low priority class
35
+ # @param [Class] low_priority_class The low priority GTIN class that the high priority one is moved before
36
+ def reprioritize_before(high_priority_class, low_priority_class)
37
+ low_priority_index = prioritized_gtin_classes.index(low_priority_class)
38
+ remove_gtin_class(high_priority_class)
39
+ prioritized_gtin_classes.insert(low_priority_index, high_priority_class)
40
+ nil
41
+ end
42
+
19
43
  private
20
44
 
45
+ def prioritized_gtin_classes
46
+ @prioritized_gtin_classes ||= []
47
+ end
48
+
21
49
  def class_for_input(input)
22
- [GTIN8, GTIN12, GTIN13, GTIN14].find do |klass|
23
- input.to_s.size == klass::VALID_LENGTH
24
- end
50
+ input = input.to_s.freeze
51
+ prioritized_gtin_classes.find { |klass| klass.handles?(input) }
25
52
  end
26
53
  end
27
54
  end
28
55
  end
56
+
57
+ # Load GTIN implementations after we have our registration setup
58
+ require_relative "gtin/base"
59
+ require_relative "gtin/check_digit"
60
+ require_relative "gtin/gtin8"
61
+ require_relative "gtin/gtin12"
62
+ require_relative "gtin/gtin13"
63
+ require_relative "gtin/gtin14"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BarcodeValidation
4
- VERSION = "2.5.0"
4
+ VERSION = "2.7.0"
5
5
  end
@@ -34,7 +34,7 @@ module BarcodeValidation
34
34
  include Error
35
35
 
36
36
  def initialize(input)
37
- super "Invalid GTIN #{input.inspect}"
37
+ super("Invalid GTIN #{input.inspect}")
38
38
  end
39
39
  end
40
40
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barcodevalidation
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marketplacer
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-08-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: A RubyGem to parse and validate barcodes
14
13
  email:
@@ -42,7 +41,6 @@ homepage: https://github.com/marketplacer/barcodevalidation
42
41
  licenses:
43
42
  - MIT
44
43
  metadata: {}
45
- post_install_message:
46
44
  rdoc_options: []
47
45
  require_paths:
48
46
  - lib
@@ -50,15 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
48
  requirements:
51
49
  - - ">="
52
50
  - !ruby/object:Gem::Version
53
- version: '2.5'
51
+ version: '3.1'
54
52
  required_rubygems_version: !ruby/object:Gem::Requirement
55
53
  requirements:
56
54
  - - ">="
57
55
  - !ruby/object:Gem::Version
58
56
  version: '0'
59
57
  requirements: []
60
- rubygems_version: 3.2.33
61
- signing_key:
58
+ rubygems_version: 3.6.7
62
59
  specification_version: 4
63
60
  summary: Parses and validates barcodes
64
61
  test_files: []