cosing 0.1.2 → 0.2.1

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.
@@ -12,11 +12,10 @@ module Cosing
12
12
  end
13
13
 
14
14
  def add_rule(params)
15
- return unless params[:reference_number]
15
+ return unless reference = params[:reference_number]
16
16
 
17
- @rules[params[:reference_number]] = self.class::Rule.new(
18
- **params.merge(annex: self.class.name.gsub("::", " "))
19
- )
17
+ annex = self.class.name.gsub("::", " ")
18
+ @rules[reference] = self.class::Rule.new(**params.merge(annex:))
20
19
  end
21
20
 
22
21
  def lookup(reference_number)
@@ -24,15 +23,8 @@ module Cosing
24
23
  reference_number.to_s,
25
24
  fuzzy_find(reference_number.to_s)
26
25
  )
27
- rescue KeyError => e
28
- # Known missing data from original source
29
- return nil if reference_number == "19" && instance_of?(Annex::VI)
30
- return nil if reference_number == "41" && instance_of?(Annex::VI)
31
- return nil if reference_number == "31" && instance_of?(Annex::VI)
32
- return nil if reference_number == "44" && instance_of?(Annex::VI)
33
- return nil if reference_number == "268" && instance_of?(Annex::III)
34
-
35
- raise e
26
+ rescue KeyError
27
+ nil
36
28
  end
37
29
 
38
30
  private
@@ -40,8 +32,7 @@ module Cosing
40
32
  def fuzzy_find(reference_number)
41
33
  return @rules[reference_number] if @rules.key?(reference_number)
42
34
 
43
- candidates = @rules.keys.grep(/#{reference_number}[abcd]/)
44
- candidates
35
+ @rules.keys.grep(/#{reference_number}[abcd]/)
45
36
  .map { |candidate| @rules[candidate] }
46
37
  .tap do |candidates|
47
38
  if candidates.empty?
@@ -6,6 +6,24 @@ module Cosing
6
6
  class Rule < Rule
7
7
  attribute :inn, Types::String
8
8
  end
9
+
10
+ def self.load
11
+ new.tap do |annex|
12
+ Annex.parse("data/annex.II.csv") do |row|
13
+ ingredients = Cosing::Parser.transform_array!(
14
+ row,
15
+ key: :identified_ingredients,
16
+ split: ";"
17
+ )
18
+
19
+ annex.add_rule(
20
+ row.merge(
21
+ identified_ingredients: ingredients.compact
22
+ )
23
+ )
24
+ end
25
+ end
26
+ end
9
27
  end
10
28
  end
11
29
  end
@@ -10,6 +10,31 @@ module Cosing
10
10
  attribute :other_restrictions, Types::String
11
11
  attribute :product_type, Types::String
12
12
  end
13
+
14
+ def self.load
15
+ new.tap do |annex|
16
+ Annex.parse("data/annex.III.csv") do |row|
17
+ common_ingredients = Cosing::Parser.transform_array!(
18
+ row,
19
+ key: :common_ingredients,
20
+ split: ";"
21
+ )
22
+ identified_ingredients = Cosing::Parser.transform_array!(
23
+ row,
24
+ key: :identified_ingredients,
25
+ split: ";"
26
+ )
27
+
28
+ annex.add_rule(
29
+ row.merge(
30
+ common_ingredients: common_ingredients.compact,
31
+ identified_ingredients: identified_ingredients.compact,
32
+ other_restrictions: row[:other]
33
+ )
34
+ )
35
+ end
36
+ end
37
+ end
13
38
  end
14
39
  end
15
40
  end
@@ -11,6 +11,25 @@ module Cosing
11
11
  attribute :product_type, Types::String
12
12
  attribute :wording_of_conditions, Types::String
13
13
  end
14
+
15
+ def self.load
16
+ new.tap do |annex|
17
+ Annex.parse("data/annex.IV.csv") do |row|
18
+ ingredients = Cosing::Parser.transform_array!(
19
+ row,
20
+ key: :identified_ingredients,
21
+ split: ";"
22
+ )
23
+
24
+ annex.add_rule(
25
+ row.merge(
26
+ identified_ingredients: ingredients.compact,
27
+ other_restrictions: row[:other]
28
+ )
29
+ )
30
+ end
31
+ end
32
+ end
14
33
  end
15
34
  end
16
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cosing
2
4
  module Annex
3
5
  class Rule < Dry::Struct
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cosing
2
4
  module Annex
3
5
  class SccsOpinion < Dry::Struct
@@ -10,6 +10,31 @@ module Cosing
10
10
  attribute :product_type, Types::String
11
11
  attribute :wording_of_conditions, Types::String
12
12
  end
13
+
14
+ def self.load
15
+ new.tap do |annex|
16
+ Annex.parse("data/annex.V.csv") do |row|
17
+ common_ingredients = Cosing::Parser.transform_array!(
18
+ row,
19
+ key: :common_ingredients,
20
+ split: ";"
21
+ )
22
+ identified_ingredients = Cosing::Parser.transform_array!(
23
+ row,
24
+ key: :identified_ingredients,
25
+ split: ";"
26
+ )
27
+
28
+ annex.add_rule(
29
+ row.merge(
30
+ common_ingredients: common_ingredients.compact,
31
+ identified_ingredients: identified_ingredients.compact,
32
+ other_restrictions: row[:other]
33
+ )
34
+ )
35
+ end
36
+ end
37
+ end
13
38
  end
14
39
  end
15
40
  end
@@ -11,6 +11,31 @@ module Cosing
11
11
  attribute :product_type, Types::String
12
12
  attribute :wording_of_conditions, Types::String
13
13
  end
14
+
15
+ def self.load
16
+ new.tap do |annex|
17
+ Annex.parse("data/annex.VI.csv") do |row|
18
+ common_ingredients = Cosing::Parser.transform_array!(
19
+ row,
20
+ key: :common_ingredients,
21
+ split: ";"
22
+ )
23
+ identified_ingredients = Cosing::Parser.transform_array!(
24
+ row,
25
+ key: :identified_ingredients,
26
+ split: ";"
27
+ )
28
+
29
+ annex.add_rule(
30
+ row.merge(
31
+ common_ingredients: common_ingredients.compact,
32
+ identified_ingredients: identified_ingredients.compact,
33
+ other_restrictions: row[:other]
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
14
39
  end
15
40
  end
16
41
  end
data/lib/cosing/annex.rb CHANGED
@@ -33,133 +33,25 @@ module Cosing
33
33
  yield row.merge(
34
34
  cas_numbers: cas_numbers.compact,
35
35
  ec_numbers: ec_numbers.compact,
36
- sccs_opinions: sccs_opinions.compact,
36
+ sccs_opinions: sccs_opinions.compact
37
37
  )
38
38
  end
39
39
  end
40
40
 
41
41
  def load
42
- annex_ii = Annex::II.new.tap do |annex|
43
- parse("data/annex.II.csv") do |row|
44
- identified_ingredients = transform_array!(
45
- row,
46
- key: :identified_ingredients,
47
- split: ";"
48
- )
49
-
50
- annex.add_rule(
51
- row.merge(
52
- identified_ingredients: identified_ingredients.compact
53
- )
54
- )
55
- end
56
- end
57
-
58
- annex_iii = Annex::III.new.tap do |annex|
59
- parse("data/annex.III.csv") do |row|
60
- common_ingredients = transform_array!(
61
- row,
62
- key: :common_ingredients,
63
- split: ";"
64
- )
65
- identified_ingredients = transform_array!(
66
- row,
67
- key: :identified_ingredients,
68
- split: ";"
69
- )
70
-
71
- annex.add_rule(
72
- row.merge(
73
- common_ingredients: common_ingredients.compact,
74
- identified_ingredients: identified_ingredients.compact,
75
- other_restrictions: row[:other]
76
- )
77
- )
78
- end
79
- end
80
-
81
- annex_iv = Annex::IV.new.tap do |annex|
82
- parse("data/annex.IV.csv") do |row|
83
- identified_ingredients = transform_array!(
84
- row,
85
- key: :identified_ingredients,
86
- split: ";"
87
- )
88
-
89
- annex.add_rule(
90
- row.merge(
91
- identified_ingredients: identified_ingredients.compact,
92
- other_restrictions: row[:other]
93
- )
94
- )
95
- end
96
- end
97
-
98
- annex_v = Annex::V.new.tap do |annex|
99
- parse("data/annex.V.csv") do |row|
100
- common_ingredients = transform_array!(
101
- row,
102
- key: :common_ingredients,
103
- split: ";"
104
- )
105
- identified_ingredients = transform_array!(
106
- row,
107
- key: :identified_ingredients,
108
- split: ";"
109
- )
110
-
111
- annex.add_rule(
112
- row.merge(
113
- common_ingredients: common_ingredients.compact,
114
- identified_ingredients: identified_ingredients.compact,
115
- other_restrictions: row[:other]
116
- )
117
- )
118
- end
119
- end
120
-
121
- annex_vi = Annex::VI.new.tap do |annex|
122
- parse("data/annex.VI.csv") do |row|
123
- common_ingredients = transform_array!(
124
- row,
125
- key: :common_ingredients,
126
- split: ";"
127
- )
128
- identified_ingredients = transform_array!(
129
- row,
130
- key: :identified_ingredients,
131
- split: ";"
132
- )
133
-
134
- annex.add_rule(
135
- row.merge(
136
- common_ingredients: common_ingredients.compact,
137
- identified_ingredients: identified_ingredients.compact,
138
- other_restrictions: row[:other]
139
- )
140
- )
141
- end
142
- end
143
-
144
- {
145
- ii: annex_ii,
146
- iii: annex_iii,
147
- iv: annex_iv,
148
- v: annex_v,
149
- vi: annex_vi
150
- }
151
- end
152
-
153
- def transform_array!(params, key:, split:)
154
- params
155
- .delete(key)
156
- .split(split)
157
- .map(&:strip)
158
- .reject { |n| n == "-" }
42
+ ii, iii, iv, v, vi = [
43
+ Annex::II,
44
+ Annex::III,
45
+ Annex::IV,
46
+ Annex::V,
47
+ Annex::VI
48
+ ].map(&:load)
49
+
50
+ { ii:, iii:, iv:, v:, vi: }
159
51
  end
160
52
 
161
53
  def build_sccs_opinions(row)
162
- transform_array!(
54
+ Cosing::Parser.transform_array!(
163
55
  row,
164
56
  key: :sccs_opinions,
165
57
  split: ";"
@@ -174,23 +66,23 @@ module Cosing
174
66
  end
175
67
 
176
68
  def build_cas_numbers(row)
177
- transform_array!(
69
+ Cosing::Parser.transform_array!(
178
70
  row,
179
71
  key: :cas_number,
180
72
  split: "/"
181
73
  ).map do |cas_number|
182
- match = cas_number.match(/(?<cas_number>\d{2,7}-\d{2}-\d)/)
74
+ match = cas_number.match(Patterns::CAS_NUMBER)
183
75
  match[:cas_number] if match
184
76
  end
185
77
  end
186
78
 
187
79
  def build_ec_numbers(row)
188
- transform_array!(
80
+ Cosing::Parser.transform_array!(
189
81
  row,
190
82
  key: :ec_number,
191
83
  split: "/"
192
84
  ).map do |ec_number|
193
- match = ec_number.match(/(?<ec_number>\d{3}-\d{3}-\d)/)
85
+ match = ec_number.match(Patterns::EC_NUMBER)
194
86
  match[:ec_number] if match
195
87
  end
196
88
  end
@@ -4,80 +4,86 @@ module Cosing
4
4
  class Database
5
5
  attr_reader :annexes, :ingredients
6
6
 
7
+ ANNOTATION_PATTERN = %r{([IVX]+)/([IVX]*/)?([\dabcd,]+)}
8
+
7
9
  def initialize(annexes)
8
10
  @annexes = annexes
9
11
  @ingredients = {}
10
12
  end
11
13
 
12
14
  def add_ingredient(params)
13
- restrictions = transform_array!(params, key: :restriction, split: "\n")
14
- annotation_pattern = %r{([IVX]+)/([IVX]*/)?([\dabcd,]+)}
15
+ restrictions = Parser
16
+ .transform_array!(params, key: :restriction, split: "\n")
17
+ regulations = extract_regulations(restrictions).compact
18
+ cas_numbers = extract_cas_numbers(params).compact
19
+ einecs_numbers = extract_einecs_numbers(params).compact
20
+ functions = Parser
21
+ .transform_array!(params, key: :functions, split: ",")
22
+ .compact
15
23
 
16
- regulations = restrictions
17
- .flat_map do |restriction|
18
- matches = restriction.scan(annotation_pattern)
19
- next unless matches.any?
24
+ @ingredients[params[:reference_number]] = Ingredient.new(
25
+ functions:,
26
+ restrictions:,
27
+ regulations:,
28
+ cas_numbers:,
29
+ einecs_numbers:,
30
+ **params
31
+ )
32
+ end
20
33
 
21
- hits = matches.flat_map do |match|
22
- numeral, _, reference_number = match
34
+ def save(filepath, pretty: false)
35
+ output = if pretty
36
+ JSON.pretty_generate(@ingredients.to_h)
37
+ else
38
+ JSON.dump(@ingredients.to_h)
39
+ end
23
40
 
24
- reference_number.split(",").flat_map do |number|
25
- #debugger if number == "41" && numeral == "VI"
26
- @annexes[numeral.downcase.to_sym].lookup(number)
27
- end
28
- end
41
+ File.write(filepath, output)
42
+ end
29
43
 
30
- hits.compact
31
- end
44
+ private
32
45
 
33
- cas_numbers = transform_array!(
46
+ def extract_cas_numbers(params)
47
+ Parser.transform_array!(
34
48
  params,
35
49
  key: :cas_number,
36
50
  split: "/"
37
51
  ).map do |cas_number|
38
- match = cas_number.match(/(?<cas_number>\d{2,7}-\d{2}-\d)/)
52
+ match = cas_number.match(Patterns::CAS_NUMBER)
39
53
  match[:cas_number] if match
40
54
  end
55
+ end
41
56
 
42
- functions = transform_array!(params, key: :functions, split: ",")
43
-
44
- einecs_numbers = transform_array!(
57
+ def extract_einecs_numbers(params)
58
+ Parser.transform_array!(
45
59
  params,
46
60
  key: :einecs_number,
47
61
  split: "/"
48
62
  ).map do |einecs_number|
49
- match = einecs_number.match(/(?<einecs_number>\d{3}-\d{3}-\d)/)
63
+ match = einecs_number.match(Patterns::EINECS_NUMBER)
50
64
  match[:einecs_number] if match
51
65
  end
52
-
53
- @ingredients[params[:reference_number]] = Ingredient.new(
54
- functions:,
55
- restrictions: restrictions.compact,
56
- regulations: regulations.compact,
57
- cas_numbers: cas_numbers.compact,
58
- einecs_numbers: einecs_numbers.compact,
59
- **params
60
- )
61
66
  end
62
67
 
63
- def save(filepath, pretty: false)
64
- output = if pretty
65
- JSON.pretty_generate(@ingredients.to_h)
66
- else
67
- JSON.dump(@ingredients.to_h)
68
- end
68
+ def extract_regulations(restrictions)
69
+ restrictions
70
+ .flat_map do |restriction|
71
+ matches = restriction.scan(ANNOTATION_PATTERN)
72
+ next if matches.none?
69
73
 
70
- File.write(filepath, output)
74
+ extract_hits(matches).compact
75
+ end
71
76
  end
72
77
 
73
- private
78
+ def extract_hits(matches)
79
+ matches.flat_map do |match|
80
+ numeral, _, reference_number = match
74
81
 
75
- def transform_array!(params, key:, split:)
76
- params
77
- .delete(key)
78
- .split(split)
79
- .map(&:strip)
80
- .reject { |n| n == "-" }
82
+ reference_number.split(",").flat_map do |number|
83
+ # debugger if number == "41" && numeral == "VI"
84
+ @annexes[numeral.downcase.to_sym].lookup(number)
85
+ end
86
+ end
81
87
  end
82
88
  end
83
89
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosing
4
+ module Parser
5
+ module_function
6
+
7
+ def transform_array!(params, key:, split:)
8
+ params
9
+ .delete(key)
10
+ .split(split)
11
+ .map!(&:strip)
12
+ .reject { |n| n == "-" }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Cosing
2
+ module Patterns
3
+ CAS_NUMBER = /(?<cas_number>\d{2,7}-\d{2}-\d)/
4
+ EINECS_NUMBER = /(?<einecs_number>\d{3}-\d{3}-\d)/
5
+ EC_NUMBER = /(?<ec_number>\d{3}-\d{3}-\d)/
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cosing
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/cosing.rb CHANGED
@@ -7,6 +7,8 @@ require "dry/types"
7
7
  require "dry/struct"
8
8
  require_relative "cosing/version"
9
9
  require_relative "cosing/types"
10
+ require_relative "cosing/parser"
11
+ require_relative "cosing/patterns"
10
12
  require_relative "cosing/ingredient"
11
13
  require_relative "cosing/annex"
12
14
  require_relative "cosing/database"
@@ -30,13 +32,10 @@ module Cosing
30
32
  liberal_parsing: true,
31
33
  header_converters: :symbol
32
34
  ) do |row|
33
- row =
34
- row
35
- .to_h
36
- .transform_values(&:to_s)
37
- .transform_values(&:strip)
38
-
39
- database.add_ingredient(row)
35
+ row
36
+ .to_h
37
+ .transform_values! { |value| value.to_s.strip }
38
+ .then { |row| database.add_ingredient(row) }
40
39
  end
41
40
  end
42
41
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cosing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nolan J Tait
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-03 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: csv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: dry-struct
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -45,12 +59,8 @@ executables: []
45
59
  extensions: []
46
60
  extra_rdoc_files: []
47
61
  files:
48
- - ".rspec"
49
- - ".rubocop.yml"
50
- - CHANGELOG.md
51
62
  - LICENSE.txt
52
63
  - README.md
53
- - Rakefile
54
64
  - cosing.gemspec
55
65
  - data/annex.II.csv
56
66
  - data/annex.III.csv
@@ -70,6 +80,8 @@ files:
70
80
  - lib/cosing/annex/vi.rb
71
81
  - lib/cosing/database.rb
72
82
  - lib/cosing/ingredient.rb
83
+ - lib/cosing/parser.rb
84
+ - lib/cosing/patterns.rb
73
85
  - lib/cosing/types.rb
74
86
  - lib/cosing/version.rb
75
87
  homepage: https://github.com/inhouse-work/cosing
@@ -88,14 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
100
  requirements:
89
101
  - - ">="
90
102
  - !ruby/object:Gem::Version
91
- version: '3'
103
+ version: '3.1'
92
104
  required_rubygems_version: !ruby/object:Gem::Requirement
93
105
  requirements:
94
106
  - - ">="
95
107
  - !ruby/object:Gem::Version
96
108
  version: '0'
97
109
  requirements: []
98
- rubygems_version: 3.4.10
110
+ rubygems_version: 3.5.7
99
111
  signing_key:
100
112
  specification_version: 4
101
113
  summary: COSING database
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,6 +0,0 @@
1
- require:
2
- - rubocop-inhouse
3
-
4
- inherit_gem:
5
- rubocop-inhouse:
6
- - config/default.yml
data/CHANGELOG.md DELETED
@@ -1,13 +0,0 @@
1
- ## [Unreleased]
2
-
3
- ## [0.1.2] - 2023-12-03
4
-
5
- - Fixes missing pathname require
6
-
7
- ## [0.1.1] - 2023-12-03
8
-
9
- - Fixes gem paths
10
-
11
- ## [0.1.0] - 2023-12-03
12
-
13
- - Initial release
data/Rakefile DELETED
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
9
-
10
- desc "Output the cosing database into a json format"
11
- task :export do
12
- database = Cosing.load
13
- database.save
14
- end