gs1 0.1.1 → 0.1.2

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
  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