gs1 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d50c9655be44cc5487286e9bc934f75c21f3872
4
- data.tar.gz: 289498f478e765b504fb6006fbe8dcd2f7010d0b
3
+ metadata.gz: 524381a860829321e654f5751256113a19ba135a
4
+ data.tar.gz: e134536208ea7e10bce1e53ead1d2e57ea31c4a8
5
5
  SHA512:
6
- metadata.gz: f411ca39dc709ba23ceef51ba78984d52dc26b2130e773ebbe767fe124796a01912db4086fe8771e231921a2da9b7b45eba83a326f215d10b487f5f12d26f9ef
7
- data.tar.gz: 4067cd00f2397d755b6a44b24b15b4a53487faa55906ee4a3033fa354ad834328a0ec69417ec541312cce533466101b2472451b4500e562ddf0b92f7a2eaec1d
6
+ metadata.gz: fdc1e69f3e91208c624e14bb1c7300f0e81b6c1a371aa909afe44bb644b1ecef54aa8dd62fad7274fb9dc111f0a33917a85eeffcb05ce241cc52e7447148f037
7
+ data.tar.gz: 1b1ca6dd02ac6220a2fef92884c22f8a900030c285df815da1bc11b8890f8d347e533d2de2b11cf6e179ccfdb630f2c59839d44163ad699bb207083f3fb2d091
@@ -5,77 +5,33 @@ module GS1
5
5
  class Base
6
6
  include Definitions
7
7
 
8
- class UnknownRecordError < StandardError; end
9
-
10
- attr_reader :errors
11
-
12
8
  def initialize(options = {})
13
9
  self.class.records.each do |record|
14
10
  data = options.fetch(record.underscore_name, nil)
15
11
 
16
12
  instance_variable_set("@#{record.underscore_name}", record.new(data))
17
13
  end
14
+ end
18
15
 
19
- @errors = []
16
+ def errors
17
+ @errors ||= []
20
18
  end
21
19
 
22
20
  class << self
23
- def from_scan(barcode, separator: "\u001E")
24
- new(scan_to_params(barcode, separator: separator))
25
- end
26
-
27
- def scan_to_params(barcode, separator: "\u001E")
28
- data = barcode.chars
29
-
30
- {}.tap do |params|
31
- params.merge!(scan_to_param(data, separator)) until data.empty?
32
- end
21
+ def from_scan!(barcode, separator: DEFAULT_SEPARATOR)
22
+ new(scan_to_params!(barcode, separator: separator))
33
23
  end
34
24
 
35
- private
36
-
37
- def scan_to_param(data, separator)
38
- record = record_from_data(data)
39
-
40
- { record.underscore_name => shift_length(data, record, separator) }
41
- end
42
-
43
- # Fetch the two first characters (interpreted as AI) from the remaining
44
- # data and try to find record class. If no record class was found, fetch
45
- # a third character and try again, and then finally a forth, as no AI
46
- # currently have more then 4 characters.
47
- def record_from_data(data)
48
- ai_variants = []
49
-
50
- record = process_ai_variants(ai_variants, data, 2) ||
51
- process_ai_variants(ai_variants, data, 1) ||
52
- process_ai_variants(ai_variants, data, 1)
53
-
54
- return record if record
55
-
56
- raise UnknownRecordError, "Unable to retrieve record from application identifier(s) #{ai_variants.join(', ')}"
57
- end
58
-
59
- def process_ai_variants(ai_variants, data, shifts)
60
- ai_variants << ai_variants.last.to_s + data.shift(shifts).join
61
-
62
- AI_CLASSES[ai_variants.last]
25
+ def from_scan(barcode, separator: DEFAULT_SEPARATOR)
26
+ new(scan_to_params(barcode, separator: separator))
63
27
  end
64
28
 
65
- def shift_length(data, record, separator)
66
- return data.shift(record.barcode_length).join if record.barcode_length
67
-
68
- if data.find_index(separator) && data.find_index(separator) < record.barcode_max_length
69
- shift_variable_length(data, separator)
70
- else
71
- data.shift(record.barcode_max_length).join
72
- end
29
+ def scan_to_params!(barcode, separator: DEFAULT_SEPARATOR)
30
+ Tokenizer.new(barcode, separator: separator).to_params!
73
31
  end
74
32
 
75
- def shift_variable_length(data, separator)
76
- data.shift(data.find_index(separator)).join.tap do
77
- data.shift unless data.empty? # Shift separator character
78
- end
33
+ def scan_to_params(barcode, separator: DEFAULT_SEPARATOR)
34
+ Tokenizer.new(barcode, separator: separator).to_params
79
35
  end
80
36
  end
81
37
  end
@@ -5,7 +5,7 @@ module GS1
5
5
  class Healthcare < Base
6
6
  define_records GTIN, ExpirationDate, Batch, SerialNumber
7
7
 
8
- def to_s(level: AIDCMarketingLevels::ENHANCED, separator: "\u001E")
8
+ def to_s(level: AIDCMarketingLevels::ENHANCED, separator: DEFAULT_SEPARATOR)
9
9
  return unless valid?(level: level)
10
10
 
11
11
  [
@@ -0,0 +1,126 @@
1
+ module GS1
2
+ module Barcode
3
+ # A segment of a barcode. Retrives a single segment and keeps the rest.
4
+ #
5
+ class Segment
6
+ attr_reader :data, :separator
7
+
8
+ def initialize(data, separator: DEFAULT_SEPARATOR)
9
+ @data = data.chars
10
+ @separator = separator
11
+ end
12
+
13
+ def to_params
14
+ return {} unless record
15
+
16
+ { record.underscore_name => record_data }
17
+ end
18
+
19
+ # Fetch the two first characters (interpreted as AI) from the remaining
20
+ # data and try to find record class. If no record class was found, fetch
21
+ # a third character and try again, and then finally a forth, as no AI
22
+ # currently have more then 4 characters.
23
+ def record
24
+ @record ||= process_ai_variants(2) ||
25
+ process_ai_variants(1) ||
26
+ process_ai_variants(1)
27
+ end
28
+
29
+ def record_data
30
+ return unless record
31
+
32
+ @record_data ||= _record_data
33
+ end
34
+
35
+ def rest
36
+ record_data # Trigger segment retrieval
37
+
38
+ @data.join
39
+ end
40
+
41
+ def valid?
42
+ errors.clear
43
+
44
+ validate
45
+
46
+ errors.empty?
47
+ end
48
+
49
+ def validate
50
+ if record
51
+ errors << "Unable to retrieve data to #{record}" unless record_data
52
+ else
53
+ errors << "Unable to retrieve record from application identifier(s) #{ai_variants.join(', ')}"
54
+ end
55
+ end
56
+
57
+ def validate!
58
+ return true if valid?
59
+
60
+ raise InvalidTokenError, errors.join(', ')
61
+ end
62
+
63
+ def errors
64
+ @errors ||= []
65
+ end
66
+
67
+ private
68
+
69
+ def ai_variants
70
+ @ai_variants ||= []
71
+ end
72
+
73
+ def process_ai_variants(shifts)
74
+ return unless can_shift_ai_data?(shifts)
75
+
76
+ ai_variants << ai_variants.last.to_s + data.shift(shifts).join
77
+
78
+ AI_CLASSES[ai_variants.last]
79
+ end
80
+
81
+ def can_shift_ai_data?(shifts)
82
+ data.size >= shifts
83
+ end
84
+
85
+ def _record_data
86
+ shift_barcode_length ||
87
+ shift_separator_length ||
88
+ shift_max_barcode_length
89
+ end
90
+
91
+ # Record has a fixed barcode length
92
+ def shift_barcode_length
93
+ return unless record.barcode_length
94
+
95
+ shift_fixed_length(record.barcode_length)
96
+ end
97
+
98
+ # Record has a variable barcode length
99
+ def shift_separator_length
100
+ separator_index = data.find_index(separator)
101
+
102
+ return unless separator_index && separator_index < record.barcode_max_length
103
+
104
+ shift_fixed_length(separator_index).tap do
105
+ data.shift # Shift separator character
106
+ end
107
+ end
108
+
109
+ def shift_max_barcode_length
110
+ shift_fixed_length(record.barcode_max_length)
111
+ end
112
+
113
+ def shift_fixed_length(shifts)
114
+ normalized_shifts = [data.size, shifts].min
115
+
116
+ return unless can_shift_record_data?(normalized_shifts)
117
+
118
+ data.shift(normalized_shifts).join
119
+ end
120
+
121
+ def can_shift_record_data?(shifts)
122
+ data.size >= shifts && record.allowed_lengths.include?(shifts)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,46 @@
1
+ module GS1
2
+ module Barcode
3
+ # Class for parsing barcodes. Uses {Segment} for splitting up the individual
4
+ # parts.
5
+ #
6
+ class Tokenizer
7
+ attr_reader :data, :separator, :params
8
+
9
+ def initialize(data, separator: DEFAULT_SEPARATOR)
10
+ @data = data
11
+ @separator = separator
12
+ @params = {}
13
+ end
14
+
15
+ def to_params
16
+ @to_params ||= segment_to_params(data)
17
+ end
18
+
19
+ def to_params!
20
+ @to_params ||= segment_to_params(data, true)
21
+ end
22
+
23
+ private
24
+
25
+ def segment_to_params(input, bang = false)
26
+ segment_from_input(input, bang) do |segment|
27
+ next if segment.to_params.empty?
28
+
29
+ params.merge!(segment.to_params)
30
+ next if segment.rest.empty?
31
+
32
+ segment_to_params(segment.rest, bang)
33
+ end
34
+
35
+ params
36
+ end
37
+
38
+ def segment_from_input(input, bang)
39
+ Segment.new(input, separator: separator).tap do |segment|
40
+ segment.validate! if bang
41
+ yield segment if block_given?
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/gs1/barcode.rb CHANGED
@@ -2,3 +2,13 @@ require 'gs1/barcode/definitions'
2
2
 
3
3
  require 'gs1/barcode/base'
4
4
  require 'gs1/barcode/healthcare'
5
+ require 'gs1/barcode/segment'
6
+ require 'gs1/barcode/tokenizer'
7
+
8
+ module GS1
9
+ module Barcode
10
+ DEFAULT_SEPARATOR = "\u001E".freeze
11
+
12
+ class InvalidTokenError < StandardError; end
13
+ end
14
+ end
@@ -2,6 +2,7 @@ module GS1
2
2
  # Module for handling definitions.
3
3
  #
4
4
  module Definitions
5
+ class InvalidDefinitionType < StandardError; end
5
6
  class MissingLengthDefinition < StandardError; end
6
7
  class UnknownDefinition < StandardError; end
7
8
 
@@ -37,9 +38,9 @@ module GS1
37
38
  # Defaults barcode length to allowed length if not explicitly defined, only
38
39
  # if there is one significant allowed.
39
40
  def normalize_length_options(options)
40
- options[:allowed] = normalize_multiple_option(options[:allowed])
41
+ options[:allowed] = normalize_multiple_option(options[:allowed] || options[:barcode])
41
42
  options[:barcode] = normalize_singlural_option(options[:barcode])
42
- options[:max_barcode] = normalize_singlural_option(options[:barcode] || options[:allowed])
43
+ options[:max_barcode] = options[:allowed]&.last
43
44
 
44
45
  options
45
46
  end
@@ -54,10 +55,11 @@ module GS1
54
55
  end
55
56
 
56
57
  def normalize_singlural_option(option_value)
57
- case option_value
58
- when Array then option_value.last
59
- else option_value
60
- end
58
+ return unless option_value
59
+
60
+ raise InvalidDefinitionType unless option_value.is_a?(Numeric)
61
+
62
+ option_value
61
63
  end
62
64
 
63
65
  def barcode_length
@@ -16,6 +16,8 @@ module GS1
16
16
 
17
17
  # Default to GTIN-14 since it is the most common format.
18
18
  def to_s
19
+ return unless data
20
+
19
21
  to_gtin_14
20
22
  end
21
23
  end
data/lib/gs1/record.rb CHANGED
@@ -43,7 +43,7 @@ module GS1
43
43
  end
44
44
 
45
45
  def to_ai
46
- "#{ai}#{to_s}" # rubocop:disable Lint/StringConversionInInterpolation
46
+ ai + to_s
47
47
  end
48
48
 
49
49
  def ==(other)
data/lib/gs1/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module GS1
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.1.2'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gs1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Åhman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-08 00:00:00.000000000 Z
11
+ date: 2018-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -134,6 +134,8 @@ files:
134
134
  - lib/gs1/barcode/base.rb
135
135
  - lib/gs1/barcode/definitions.rb
136
136
  - lib/gs1/barcode/healthcare.rb
137
+ - lib/gs1/barcode/segment.rb
138
+ - lib/gs1/barcode/tokenizer.rb
137
139
  - lib/gs1/batch.rb
138
140
  - lib/gs1/check_digit_calculator.rb
139
141
  - lib/gs1/content.rb