circuitdata 0.3.5 → 0.5.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.
@@ -0,0 +1,106 @@
1
+ def self.compare_files(filehash, validate_origins=false)
2
+ # Prepare the return
3
+ ra = {
4
+ error: false,
5
+ errormessage: "",
6
+ summary: {},
7
+ conflicts: {},
8
+ product: nil,
9
+ columns: [],
10
+ mastercolumn: nil,
11
+ rows: []
12
+ }
13
+
14
+ #parsedfiles
15
+ unless filehash.is_a? Hash
16
+ ra[:error] = true
17
+ ra[:errormessage] = "You have to feed this function with a hash of names and hashes"
18
+ return ra
19
+ end
20
+
21
+ # extend the hash that is received
22
+ nh = {}
23
+ filehash.each do |fhk, fhv|
24
+ nh[fhk] = {
25
+ orig: fhv,
26
+ parsed: nil,
27
+ content: nil,
28
+ has: {}
29
+ }
30
+ # READ THE CONTENT
31
+ ra[:error], ra[:errormessage], nh[fhk][:content] = self.read_json(fhv)
32
+ ra[:summary] = {} if ra[:error]
33
+ ra[:conflicts] = {} if ra[:error]
34
+ return ra if ra[:error]
35
+ # VALIDATE THE FILES
36
+ if validate_origins
37
+ ra[:error], ra[:errormessage], validationserrors = self.validate(nh[fhk][:content])
38
+ ra[:summary] = {} if ra[:error]
39
+ ra[:conflicts] = {} if ra[:error]
40
+ return ra if ra[:error]
41
+ end
42
+
43
+
44
+ # SET THE PRODUCT NAME
45
+ nh[fhk][:has][:products], nh[fhk][:has][:stackup], nh[fhk][:has][:profile_default], nh[fhk][:has][:profile_restricted], nh[fhk][:has][:profile_enforced], nh[fhk][:has][:capabilities], nh[fhk][:has][:product] = self.content(nh[fhk][:content])
46
+ unless nh[fhk][:has][:product].nil?
47
+ #self.iterate(nh[fhk][:content])
48
+
49
+ #root_node = Tree::TreeNode.new("ROOT", "Root Content")
50
+ #root_node.print_tree
51
+
52
+ ra[:product] = nh[fhk][:has][:product] if ra[:product].nil?
53
+ if nh[fhk][:has][:product] != ra[:product]
54
+ ra[:error] = true
55
+ ra[:errormessage] = "Your files contains several different product names"
56
+ ra[:summary] = {}
57
+ ra[:conflicts] = {}
58
+ return ra
59
+ end
60
+ ra[:mastercolumn] = fhk if ra[:mastercolumn].nil?
61
+ end
62
+
63
+ # THIS IS WHERE I NEED THINGS TO HAPPEN
64
+
65
+ end
66
+
67
+ # RETURN IF THERE IS NO PRODUCT
68
+ if ra[:mastercolumn].nil?
69
+ ra[:error] = true
70
+ ra[:errormessage] = "none of the files contains a product"
71
+ ra[:summary] = {}
72
+ ra[:conflicts] = {}
73
+ return ra
74
+ end
75
+
76
+ {
77
+ current_level: 0,
78
+ current_key: nil,
79
+
80
+ }
81
+ # Populate the master column
82
+ #self.iterate(filehash[ra[:mastercolumn].to_sym])
83
+ #ra[:summary] = productjson[:open_trade_transfer_package][:products][ra[:product]][:printed_circuits_fabrication_data]
84
+
85
+ #test = {}
86
+ #self.save_pair(productjson[:open_trade_transfer_package][:products][ra[:product]][:printed_circuits_fabrication_data], test)
87
+ #puts test
88
+ # Populate the product rows
89
+ #productjson[:open_trade_transfer_package]["products"][ra[:product]]["printed_circuits_fabrication_data"].each do |key, value|
90
+ # if value.is_a? Hash
91
+ # value.each do |subkey, subvalue|
92
+ # ra[:rows][]
93
+ #end
94
+
95
+ # Do comparisons
96
+ #number = 1
97
+ #filehash.each do |key, value|
98
+ # unless key.to_s == productfile
99
+ # #puts self.compatibility_checker( productjson, value, false )
100
+ # number += 1
101
+ # end
102
+ # end
103
+ #puts JSON.pretty_generate(ra)
104
+ #puts JSON.pretty_generate(nh)
105
+ return ra
106
+ end
@@ -0,0 +1,119 @@
1
+ class Circuitdata::CompatibilityChecker
2
+ def initialize(product_file, check_file, validate_origins)
3
+ require 'json'
4
+ require 'json-schema'
5
+
6
+ @product_file = product_file
7
+ @check_file = check_file
8
+ @validate_origins = validate_origins
9
+ # Final hash
10
+ @fh = {error: false, message: nil, errors: {validation: {}, restricted: {}, enforced: {}, capabilities: {}}}
11
+ end
12
+
13
+ def start_check
14
+ # Initialize & validate
15
+ @fh[:error], @fh[:message], product_data = Circuitdata.read_json(@product_file)
16
+ return @fh if @fh[:error]
17
+ @fh[:error], @fh[:message], @fh[:errors][:validation] = Circuitdata.validate(product_data)
18
+ return @fh if @fh[:error]
19
+ if @check_file.present?
20
+ @fh[:error], @fh[:message], check_data = Circuitdata.read_json(@check_file)
21
+ return @fh if @fh[:error]
22
+ @fh[:error], @fh[:message], @fh[:errors][:validation] = Circuitdata.validate(check_data)
23
+ return @fh if @fh[:error]
24
+ f2_types = Circuitdata.get_data_summary(check_data)[1]
25
+ # read the schema
26
+ schema_path = File.join(File.dirname(__FILE__), 'schema_files/v1/ottp_circuitdata_skeleton_schema.json')
27
+ restricted_schema = enforced_schema = capability_schema = Circuitdata.read_json(schema_path)[2]
28
+ # Compare the content
29
+ perform_comparison(product_data, check_data, restricted_schema, 'restricted') if f2_types.include? 'profile_restricted'
30
+ perform_comparison(product_data, check_data, enforced_schema, 'enforced') if f2_types.include? 'profile_enforced'
31
+ perform_comparison(product_data, check_data, capability_schema, 'capabilities') if f2_types.include? 'capabilities'
32
+ end
33
+
34
+ @fh
35
+ end
36
+
37
+ def perform_comparison(product_data, check_data, schema, type)
38
+ case type
39
+ when 'restricted'
40
+ check_hash = check_data.dig(:open_trade_transfer_package, :profiles, :restricted, :printed_circuits_fabrication_data)
41
+ when 'enforced'
42
+ check_hash = check_data.dig(:open_trade_transfer_package, :profiles, :enforced, :printed_circuits_fabrication_data)
43
+ when 'capabilities'
44
+ check_hash = check_data.dig(:open_trade_transfer_package, :capabilities, :printed_circuits_fabrication_data)
45
+ else
46
+ check_hash = {}
47
+ end
48
+ # binding.pry
49
+
50
+ check_hash.each do |k, v|
51
+ v.each do |kl1, vl1| # level 1
52
+ common_hash = schema.dig(:properties, :open_trade_transfer_package, :properties, :products, :patternProperties, :'^(?!generic$).*', :properties, :printed_circuits_fabrication_data, :properties)
53
+ # binding.pry
54
+ common_hash[k.to_sym]||= {:type => 'object', :properties => {}}
55
+ common_hash[:stackup][:properties][:specified][:properties][k.to_sym] ||= {:type => 'object', :properties => {}}
56
+
57
+ case vl1.class.name
58
+ when 'String'
59
+ if vl1.match("^(\\d*|\\d*.\\d*)\\.\\.\\.(\\d*|\\d*.\\d*)$") #This is a value range
60
+ from, too = vl1.match("^(\\d*|\\d*.\\d*)\\.\\.\\.(\\d*|\\d*.\\d*)$").captures
61
+ case type
62
+ when 'restricted'
63
+ new_hash = {:not => {:allOf => [{:minimum => from.to_f},{:maximum => too.to_f}]}}
64
+ else
65
+ new_hash = eval("{:minimum => #{from}, :maximum => #{too}}")
66
+ end
67
+ else # This is a normal string - check for commas
68
+ enum = []
69
+ vl1.split(',').each {|enumvalue| enum << enumvalue.strip}
70
+ case type
71
+ when 'restricted'
72
+ new_hash = {:not => {:anyOf => [{ :enum => ''}]}}
73
+ new_hash[:not][:anyOf][0][:enum] = enum
74
+ else
75
+ new_hash = eval("{:enum => #{enum}}")
76
+ end
77
+ end
78
+ when 'Numeric' # This is a normal string
79
+ case type
80
+ when 'restricted'
81
+ new_hash = {:not => {:allOf => [{:minimum => vl1.to_f},{:maximum => vl1.to_f}]}}
82
+ else
83
+ new_hash = eval("{:enum => [#{vl1.to_s}]}")
84
+ end
85
+ end
86
+ common_hash[k.to_sym][:properties][kl1.to_sym] = new_hash
87
+ common_hash[:stackup][:properties][:specified][:properties][k.to_sym][:properties][kl1.to_sym] = new_hash
88
+ end if v.is_a? Hash
89
+ end
90
+
91
+ # perform validations
92
+ begin
93
+ validation_errors = JSON::Validator.fully_validate(schema.to_json, product_data, :errors_as_objects => true)
94
+
95
+ if validation_errors.any?
96
+ @fh[:error] = true
97
+ @fh[:message] = 'The product to check did not meet the requirements'
98
+
99
+ # format the errors well here
100
+
101
+ validation_errors.each do |error|
102
+ error_array = []
103
+ begin
104
+ error_array << error[:message].match("^(The\\sproperty\\s\\'[\\s\\S]*\\'\\s)([\\s\\S]*)(\\sin\\sschema[\\s\\S]*)$").captures[1]
105
+ rescue
106
+ error_array << error[:message]
107
+ end
108
+ @fh[:errors][type.to_sym][error[:fragment]] = error_array
109
+ end
110
+ end
111
+ rescue JSON::Schema::ReadFailed
112
+ @fh[:error] = true
113
+ @fh[:message] = "Could not read the submitted `#{type}` schema" # enforced_schema
114
+ rescue JSON::Schema::SchemaError
115
+ @fh[:error] = true
116
+ @fh[:message] = "Something was wrong with the submitted `#{type}` schema" # enforced_schema
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,243 @@
1
+ class Circuitdata::FileComparer
2
+ def initialize(file_hash, validate_origins)
3
+ @file_hash = file_hash
4
+ @validate_origins = validate_origins
5
+ @rows = {}
6
+ @nh = {} # a new_hash to combine all the data
7
+ @columns = []
8
+ @default_column = nil
9
+ @master_column = nil
10
+ # Final hash
11
+ @fh = {error: false, message: nil, conflict: false, product_name: nil, columns: nil, master_column: nil, rows: nil}
12
+ end
13
+
14
+ def compare
15
+ # Initial check
16
+ unless @file_hash.is_a? Hash
17
+ @fh[:error] = true
18
+ @fh[:message] = 'You have to feed this function with a hash of names and hashes'
19
+ return @fh
20
+ end
21
+
22
+ # Process the hashes
23
+ products_array = []
24
+ @file_hash.each do |k, v|
25
+ # read content
26
+ @fh[:error], @fh[:message], file_content = Circuitdata.read_json(v)
27
+ return @fh if @fh[:error]
28
+ products, types = Circuitdata.get_data_summary(file_content)
29
+ products_array.push(*products) # add products to tracking array
30
+ # populate the new_hash to be used later
31
+ @nh[k] = {types: types, products: products, data: file_content}
32
+ end
33
+
34
+ # check if the files content meet the requirements
35
+ if valid_product?(products_array)
36
+ @fh[:product_name] = products_array.first.to_s
37
+ @columns = @nh.keys
38
+ # get all data with products in it
39
+ product_hashes = @nh.select{|k, v| v[:products].any?}
40
+ product_columns = product_hashes.keys
41
+
42
+ # Add conflicts into the new_hash
43
+ product_hashes.each do |column_k, column_v|
44
+ master_json = column_v.dig(:data)
45
+ @nh.each do |file_k, file_v|
46
+ products, data = file_v[:products], file_v[:data]
47
+ check_results = Circuitdata.compatibility_checker(master_json, data, false)
48
+ # format the conflicts correctly here
49
+ file_v[:conflicts] ||= {}
50
+ file_v[:conflicts][column_k] = get_validation_summary(check_results, file_k)
51
+ # initialize the rows format - for all the product items
52
+ product_hash = data.dig(:open_trade_transfer_package, :products, @fh[:product_name].to_sym, :printed_circuits_fabrication_data)
53
+ if products.any?
54
+ init_row_format(product_hash)
55
+ end
56
+ end
57
+ # Initialize the rows format - for all default profile items
58
+ @default_column, file_v = @nh.select{|k, v| v[:types].include?("profile_defaults")}.first # this should only be a single file
59
+ data = file_v[:data]
60
+ product_hash = data.dig(:open_trade_transfer_package, :profiles, :defaults, :printed_circuits_fabrication_data)
61
+ init_row_format(product_hash)
62
+ end
63
+
64
+ # populate the @rows
65
+ product_columns.each do |column|
66
+ @master_column = column
67
+ process_row_hash('populate')
68
+ end
69
+ # populate the @rows summary
70
+ product_columns.each do |column|
71
+ @master_column = column
72
+ process_row_hash('get_summary')
73
+ end
74
+ process_row_hash('populate_defaults')
75
+ end
76
+
77
+ @fh[:columns] = @columns.unshift(:summary)
78
+ @fh[:rows] = @rows
79
+ @fh
80
+ end
81
+
82
+ def init_row_format(product_hash)
83
+ product_hash.each do |k, v|
84
+ if v.is_a?(Hash)
85
+ @rows[k] ||= {}
86
+ v.each do |kl1, vl1|
87
+ @rows[k][kl1] ||= get_l1_hash(@columns)
88
+ end
89
+ else
90
+ @rows[k] ||= []
91
+ # if array functionality eg Holes
92
+ end if ['Hash', 'Array'].include?(v.class.name)
93
+ end
94
+ end
95
+
96
+ def process_row_hash(action)
97
+ @rows.each do |k, v| # product elements level
98
+ if v.is_a?(Hash)
99
+ v.each do |kl1, vl1| # specification level
100
+ value, conflict, conflicts_with, conflict_message = [], false, [], []
101
+ vl1.each do |kl2, vl2| # the specification column level - call the function from here
102
+ conflicts = @nh.dig(kl2, :conflicts, @master_column)
103
+ case action
104
+ when 'populate'
105
+ check = conflicts.any? && conflicts.dig(:rows, k, kl1).present?
106
+ vl2[:value] = @nh.dig(kl2, :data, :open_trade_transfer_package, :products, @fh[:product_name].to_sym, :printed_circuits_fabrication_data, k, kl1)
107
+ vl2[:conflict] = check unless vl2[:conflict] # update only when the status is false
108
+ vl2[:conflicts_with] = check ? vl2[:conflicts_with] << @master_column : []
109
+ vl2[:conflict_message] = check ? vl2[:conflict_message] + conflicts&.dig(:rows, k, kl1) : []
110
+
111
+ # update master_column conflicts with
112
+ if check
113
+ master_row = @rows.dig(k, kl1, @master_column)
114
+ master_row[:conflicts_with] = master_row[:conflicts_with] + conflicts.dig(:master_conflicts)
115
+ master_row[:conflict] = true
116
+ master_row[:conflict_message] = (master_row[:conflict_message] + vl2[:conflict_message]).uniq
117
+ end
118
+ when 'get_summary'
119
+ # get the summary items
120
+ if kl2 != :summary
121
+ items_v = vl2[:value]
122
+ master_value = vl1.dig(@master_column, :value)
123
+ # dont test if the @master_column value is also nil
124
+ if value.empty? || !value.include?(items_v)
125
+ value << items_v
126
+ conflicts_with << kl2
127
+ # jump the default column
128
+ if kl2 != @master_column # Add errors to the specific rows items
129
+ # get the item type
130
+ col_type = get_column_type(@nh.dig(kl2, :types))
131
+ vl2[:conflict] = true
132
+ vl2[:conflicts_with] = (vl2[:conflicts_with] << @master_column).uniq
133
+ vl2[:conflict_message] = (vl2[:conflict_message] << customize_conflict_message(col_type, kl2, @master_column)).uniq
134
+ # update the master row
135
+ master_row = @rows.dig(k, kl1, @master_column)
136
+ master_row[:conflicts_with] = master_row[:conflicts_with] << kl2
137
+ master_row[:conflict] = true
138
+ # get a customized error message here
139
+ master_row[:conflict_message] = (master_row[:conflict_message] << customize_conflict_message(col_type, @master_column, kl2)).uniq
140
+ end
141
+ end unless items_v.nil? || master_value.nil?
142
+ conflict = true if vl2[:conflict]
143
+ conflicts_with = conflicts_with + vl2[:conflicts_with]
144
+ conflict_message = conflict_message + vl2[:conflict_message]
145
+ end
146
+ when 'populate_defaults'
147
+ if kl2 == @default_column
148
+ vl2[:value] = @nh.dig(kl2, :data, :open_trade_transfer_package, :profiles, :defaults, :printed_circuits_fabrication_data, k, kl1)
149
+ vl2[:conflict] = false
150
+ vl2[:conflicts_with] = []
151
+ vl2[:conflict_message] = []
152
+ end
153
+ end
154
+ end
155
+ case action
156
+ when 'get_summary'
157
+ if value.count > 1
158
+ conflict = true
159
+ else
160
+ value = value.first
161
+ end
162
+ vl1[:summary] = {value: value, conflict: conflict, conflicts_with: conflicts_with.uniq, conflict_message: conflict_message.uniq}
163
+ when 'populate_defaults'
164
+ # if all the values are blank, use the default value
165
+ vl1[:summary][:value] ||= vl1.dig(@default_column, :value)
166
+ end
167
+ if action == 'get_summary'
168
+ end
169
+ @fh[:conflict] = true if conflict
170
+ end
171
+ else
172
+ # if array functionality eg Holes
173
+ end
174
+ end
175
+ end
176
+
177
+ def customize_conflict_message(type, col, conflicting_col)
178
+ case type
179
+ when :product
180
+ "#{col.to_s} value conflicts with value from #{conflicting_col.to_s}"
181
+ when :restricted
182
+ "#{col.to_s} value is restricted in #{conflicting_col.to_s}"
183
+ when :enforced
184
+ "#{col.to_s} value conflicts with the enforced value from #{conflicting_col.to_s}"
185
+ when :capability
186
+ "#{col.to_s} value is outside the capabilities of #{conflicting_col.to_s}"
187
+ else
188
+ "There were some value conflicts"
189
+ end
190
+ end
191
+
192
+ def get_column_type(types)
193
+ types ||= []
194
+ if types.include? "product"
195
+ :product
196
+ elsif types.include? "profile_restricted"
197
+ :restricted
198
+ elsif types.include? "profile_enforced"
199
+ :enforced
200
+ elsif types.include? "capabilities"
201
+ :capability
202
+ end
203
+ end
204
+
205
+ def get_validation_summary(validation, column)
206
+ summary = {}
207
+ if validation[:error]
208
+ summary[:master_conflicts] ||= []
209
+ summary[:master_conflicts] << column
210
+ summary[:conflicts], summary[:rows] = true, {}
211
+ validation[:errors].each do |type, errors| # validation, restricted, enforced, capabilities
212
+ errors.each do |k, v|
213
+ folders_stack = k.split('/')
214
+ folder, spec = folders_stack[5], folders_stack[6]
215
+ summary[:rows][folder.to_sym] ||= {}
216
+ spec_message = summary[:rows][folder.to_sym][spec.to_sym] || []
217
+ summary[:rows][folder.to_sym][spec.to_sym] = spec_message+v
218
+ end if errors.any?
219
+ end
220
+ end
221
+ summary
222
+ end
223
+
224
+ def get_l1_hash(columns)
225
+ l1_hash = {}
226
+ columns.each{|c| l1_hash[c]={} }
227
+ l1_hash
228
+ end
229
+
230
+ def valid_product?(products_array)
231
+ if products_array.uniq.count > 1
232
+ @fh[:error] = true
233
+ @fh[:message] = 'Your files contains several different product names'
234
+ return false # validation fails because of different product names
235
+ end
236
+ if products_array.empty?
237
+ @fh[:error] = true
238
+ @fh[:message] = 'None of the files contains a product'
239
+ return false # c=validation fails because there are no products
240
+ end
241
+ true
242
+ end
243
+ end