circuitdata 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +25 -122
  3. data/Rakefile +4 -6
  4. data/lib/circuitdata.rb +32 -120
  5. data/lib/circuitdata/bury/bury.rb +61 -0
  6. data/lib/circuitdata/dereferencer.rb +49 -17
  7. data/lib/circuitdata/exposed_area.rb +84 -0
  8. data/lib/circuitdata/json_schema.rb +14 -0
  9. data/lib/circuitdata/json_validator.rb +56 -0
  10. data/lib/circuitdata/json_validator/json_schema_error_parser.rb +57 -0
  11. data/lib/circuitdata/material_validator.rb +40 -0
  12. data/lib/circuitdata/product.rb +125 -0
  13. data/lib/circuitdata/product_id_validator.rb +81 -0
  14. data/lib/circuitdata/profile.rb +31 -69
  15. data/lib/circuitdata/schema.rb +145 -0
  16. data/lib/circuitdata/schema_files/schema_v1_dereferenced.json +107155 -0
  17. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema.json +68 -5307
  18. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_generics.json +23 -0
  19. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_materials.json +99 -0
  20. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_products.json +779 -0
  21. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_profiles_and_capabilities.json +323 -0
  22. data/lib/circuitdata/summary.rb +96 -0
  23. data/lib/circuitdata/validator.rb +28 -0
  24. data/lib/circuitdata/version.rb +2 -1
  25. metadata +113 -20
  26. data/lib/circuitdata/bk_comparer.rb +0 -106
  27. data/lib/circuitdata/compatibility_checker.rb +0 -160
  28. data/lib/circuitdata/file_comparer.rb +0 -276
  29. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_definitions.json +0 -1249
  30. data/lib/circuitdata/schema_files/v1/ottp_circuitdata_skeleton_schema.json +0 -94
  31. data/lib/circuitdata/schema_files/v1/ottp_schema_definitions.json +0 -102
  32. data/lib/circuitdata/tools.rb +0 -207
@@ -1,6 +1,7 @@
1
+ require "net/http"
2
+
1
3
  module Circuitdata
2
4
  class Dereferencer
3
-
4
5
  def self.dereference(schema, base_path)
5
6
  d = new(base_path)
6
7
  d.start(schema)
@@ -11,7 +12,7 @@ module Circuitdata
11
12
  end
12
13
 
13
14
  def start(schema)
14
- hash_iterator(schema)
15
+ hash_iterator(schema, schema)
15
16
  end
16
17
 
17
18
  private
@@ -19,15 +20,35 @@ module Circuitdata
19
20
  attr_reader :base_path
20
21
 
21
22
  def read_file(file_path)
22
- full_path = File.expand_path(file_path, base_path)
23
- file = File.read(full_path)
23
+ if file_path.start_with?("https://")
24
+ file = get_remote_file(file_path)
25
+ else
26
+ file = File.read(file_path)
27
+ end
24
28
  JSON.parse(file, symbolize_names: true)
29
+ rescue => e
30
+ puts file_path
31
+ raise e
25
32
  end
26
33
 
27
- def get_ref(ref)
28
- file_path, pointer = ref.split('#')
29
- data = read_file(file_path)
30
- pointer_parts = pointer.split('/').reject(&:blank?)
34
+ def dereferenced_read_file(file_path)
35
+ if file_path.start_with?("https://")
36
+ full_path = file_path
37
+ else
38
+ full_path = File.expand_path(file_path, base_path)
39
+ end
40
+ file_data = read_file(full_path)
41
+ self.class.dereference(file_data, File.dirname(full_path))
42
+ end
43
+
44
+ def get_ref(ref, original_schema)
45
+ file_path, pointer = ref.split("#")
46
+ if file_path == ""
47
+ data = original_schema
48
+ else
49
+ data = dereferenced_read_file(file_path)
50
+ end
51
+ pointer_parts = pointer.split("/").reject(&:blank?)
31
52
  result = data.dig(*pointer_parts.map(&:to_sym))
32
53
  if result.nil?
33
54
  fail "Unable to dereference ref=#{ref}"
@@ -35,23 +56,34 @@ module Circuitdata
35
56
  result
36
57
  end
37
58
 
59
+ def get_remote_file(url_str)
60
+ url = URI.parse(url_str)
61
+ req = Net::HTTP::Get.new(url.to_s)
62
+ http = Net::HTTP.new(url.host, url.port)
63
+ http.use_ssl = true
64
+ res = http.request(req)
65
+ if res.code != "200"
66
+ raise StandardError.new("Expected 200 status got #{res.code.inspect} for #{url_str}")
67
+ end
68
+ res.body
69
+ end
38
70
 
39
- def hash_iterator(h)
71
+ def hash_iterator(h, original_schema)
40
72
  h = h.clone
41
- h.each_pair do |k,v|
73
+ h.each_pair do |k, v|
42
74
  if v.is_a?(Hash)
43
- res = hash_iterator(v)
75
+ res = hash_iterator(v, original_schema)
44
76
  if res[:"$ref"]
45
77
  h[k] = res[:"$ref"]
46
78
  else
47
79
  h[k] = res
48
80
  end
49
81
  elsif v.is_a?(Array)
50
- h[k] = array_iterator(v)
82
+ h[k] = array_iterator(v, original_schema)
51
83
  else
52
84
  if k == :"$ref"
53
- ref_schema = get_ref(v)
54
- return hash_iterator(ref_schema)
85
+ ref_schema = get_ref(v, original_schema)
86
+ return hash_iterator(ref_schema, original_schema)
55
87
  else
56
88
  h[k] = v
57
89
  end
@@ -60,12 +92,12 @@ module Circuitdata
60
92
  h
61
93
  end
62
94
 
63
- def array_iterator(arr)
95
+ def array_iterator(arr, original_schema)
64
96
  arr.map do |v|
65
97
  if v.is_a?(Hash)
66
- hash_iterator(v)
98
+ hash_iterator(v, original_schema)
67
99
  elsif v.is_a?(Array)
68
- array_iterator(arr)
100
+ array_iterator(arr, original_schema)
69
101
  else
70
102
  v
71
103
  end
@@ -0,0 +1,84 @@
1
+ module Circuitdata
2
+ class ExposedArea
3
+
4
+ def initialize(product)
5
+ @product = product
6
+ end
7
+
8
+ def exposed_copper_area
9
+ return nil if board_area.nil?
10
+ exposed_layer_copper_area+barrel_area
11
+ end
12
+
13
+ def barrel_area
14
+ return 0 if board_thickness.nil?
15
+ plated_through_holes.map{ |hole| sum_holes_area(hole)}.sum
16
+ end
17
+
18
+ private
19
+
20
+ def exposed_layer_copper_area
21
+ coverage = []
22
+ unless top_final_finish.nil?
23
+ if top_final_finish[:coverage].is_a? Numeric
24
+ coverage << top_final_finish[:coverage]
25
+ end
26
+ end
27
+ unless bottom_final_finish.nil?
28
+ if bottom_final_finish[:coverage].is_a? Numeric
29
+ coverage << bottom_final_finish[:coverage]
30
+ end
31
+ end
32
+ coverage.map{ |percent| percent/100.0*board_area}.sum
33
+ end
34
+
35
+ def sum_holes_area(hole)
36
+ diameter = hole[:function_attributes][:finished_size]
37
+ number_of_holes = hole[:function_attributes][:number_of_holes]
38
+ hole_area(diameter)*number_of_holes
39
+ end
40
+
41
+ def hole_area(finished_size)
42
+ (finished_size/1000)*Math::PI*board_thickness
43
+ end
44
+
45
+ def board_thickness
46
+ @product.question_answer([:metrics, :board, :thickness])
47
+ end
48
+
49
+ def board_area
50
+ @product.question_answer([:metrics, :board, :area])
51
+ end
52
+
53
+ def plated_through_holes
54
+ @product.processes
55
+ .select{|process| process[:function] == "holes"}
56
+ .select{|process| process[:function_attributes][:plated] == true}
57
+ .select{|process| process[:function_attributes][:hole_type] == "through"}
58
+ .select{|process| process[:function_attributes][:number_of_holes].present?}
59
+ .select{|process| process[:function_attributes][:finished_size].present?}
60
+ end
61
+
62
+ def layers
63
+ @product.layers
64
+ end
65
+
66
+ def top_final_finish
67
+ return nil if conductive_final_finish_layers.first.nil?
68
+ return nil if conductive_final_finish_layers.first[:function] != "final_finish"
69
+ conductive_final_finish_layers.first
70
+ end
71
+
72
+ def bottom_final_finish
73
+ return nil if conductive_final_finish_layers.last.nil?
74
+ return nil if conductive_final_finish_layers.last[:function] != "final_finish"
75
+ conductive_final_finish_layers.last
76
+ end
77
+
78
+ # We are using the knowledge that at least one conductive layer must
79
+ # be present to separate the top and bottom solder masks from each other.
80
+ def conductive_final_finish_layers
81
+ layers.select{ |layer| ["conductive", "final_finish"].include?(layer[:function]) }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,14 @@
1
+ require "json-schema"
2
+
3
+ module Circuitdata
4
+ class UuidChecker
5
+ UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
6
+ def self.call(value)
7
+ unless UUID_REGEX.match?(value)
8
+ raise JSON::Schema::CustomFormatError.new("is not a uuid")
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ JSON::Validator.register_format_validator("uuid", Circuitdata::UuidChecker)
@@ -0,0 +1,56 @@
1
+ require_relative "./json_validator/json_schema_error_parser"
2
+
3
+ module Circuitdata
4
+ class JsonValidator
5
+ def self.validate(schema, data)
6
+ errors = JSON::Validator.fully_validate(
7
+ schema, data, errors_as_objects: true,
8
+ )
9
+ simple_errors = errors.select { |e| error_is_simple?(e) }
10
+ complex_errors = errors - simple_errors
11
+ convert_simple_errors(simple_errors) +
12
+ convert_complex_errors(complex_errors, schema, data)
13
+ end
14
+
15
+ private
16
+
17
+ def self.convert_simple_errors(schema_errors)
18
+ JsonSchemaErrorParser.translate_all(schema_errors)
19
+ end
20
+
21
+ def self.error_is_simple?(error)
22
+ path = error[:fragment]
23
+ !path.include?("circuitdata/layers") && !path.include?("circuitdata/processes")
24
+ end
25
+
26
+ def self.convert_complex_errors(errors, schema, data)
27
+ errors.flat_map do |error|
28
+ path = error[:fragment].slice(2..-1)
29
+ parts = path.split("/")
30
+
31
+ schema_element, data_element = get_element(parts, schema, data)
32
+ func = data_element.fetch(:function)
33
+ actual_schema = schema_element.fetch(:oneOf).find { |s| s.dig(:properties, :function, :enum).first == func }
34
+ simpler_errors = JSON::Validator.fully_validate(
35
+ actual_schema, data_element, errors_as_objects: true,
36
+ )
37
+ convert_simple_errors(simpler_errors).map do |err|
38
+ err[:source_path] = "/#{path}#{err[:source_path]}"
39
+ err
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.get_element(parts, schema, data)
45
+ return [schema, data] if parts.empty?
46
+ part = parts.first
47
+ if data.is_a?(Hash)
48
+ sub_schema = schema.dig(:properties, part.to_sym)
49
+ sub_schema ||= schema.dig(:patternProperties).values.first
50
+ get_element(parts[1..-1], sub_schema, data[part.to_sym])
51
+ else
52
+ get_element(parts[1..-1], schema.dig(:items), data[part.to_i])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,57 @@
1
+ module Circuitdata
2
+ class JsonValidator
3
+ class JsonSchemaErrorParser
4
+ class << self
5
+ def translate_all(errors)
6
+ errors.map(&method(:translate)).reject do |error|
7
+ error[:problem] == "pattern_mismatch" &&
8
+ error[:pattern] == "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
9
+ end
10
+ end
11
+
12
+ def translate(error)
13
+ additional_data = extract_data(error[:message], error[:failed_attribute])
14
+ if additional_data.nil?
15
+ fail "Unhandled error: #{error.inspect}"
16
+ end
17
+
18
+ path = error[:fragment].gsub("#", "")
19
+ {
20
+ source_path: path,
21
+ field: path.split("/").last,
22
+ }.merge(additional_data)
23
+ end
24
+
25
+ def extract_data(message, failed_attribute)
26
+ case failed_attribute
27
+ when "Required"
28
+ field = message.match(/of '(.*)'/)[1]
29
+ if !field
30
+ fail "Unable to extract field from #{message.inspect}"
31
+ end
32
+ return {field: field, problem: "required_property_missing"}
33
+ when "TypeV4"
34
+ if message.include?("did not match the following type")
35
+ matches = message.match(/of type (\S*) did not match the following type: (\S*)/)
36
+ actual, expected = matches[1..2]
37
+ return {actual: actual, expected: expected, problem: "type_mismatch"}
38
+ end
39
+ when "AdditionalProperties"
40
+ matches = message.match(/contains additional properties (\[.*\]) outside/)
41
+ additional_properties = JSON.parse(matches[1])
42
+ return {additional_properties: additional_properties, problem: "additional_properties"}
43
+ when "Enum"
44
+ return {problem: "not_in_enum"}
45
+ when "Pattern"
46
+ regex = message.match(/did not match the regex '(\S*)' /)[1]
47
+ return {problem: "pattern_mismatch", pattern: regex}
48
+ else
49
+ if message.match?(/is not a uuid/)
50
+ return {problem: "format_mismatch", expected: "uuid"}
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ module Circuitdata
2
+ class MaterialValidator
3
+ attr_reader :errors
4
+
5
+ MATERIAL_SCHEMA_PATH = [
6
+ :properties,
7
+ :open_trade_transfer_package,
8
+ :properties,
9
+ :custom,
10
+ :properties,
11
+ :materials,
12
+ :properties,
13
+ :circuitdata,
14
+ :patternProperties,
15
+ :".*",
16
+ ]
17
+
18
+ def initialize(data)
19
+ @data = data
20
+ end
21
+
22
+ def valid?
23
+ return @valid if defined? @valid
24
+ @valid = run_checks
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :data
30
+
31
+ def run_checks
32
+ @errors = JsonValidator.validate(schema, data)
33
+ @errors.empty?
34
+ end
35
+
36
+ def schema
37
+ Circuitdata.dereferenced_schema.dig(*MATERIAL_SCHEMA_PATH)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,125 @@
1
+ module Circuitdata
2
+ class Product
3
+ BASIC_PRODUCT_STRUCTURE = {
4
+ open_trade_transfer_package: {
5
+ version: SCHEMA_VERSION,
6
+ products: {},
7
+ custom: {
8
+ materials: {
9
+ circuitdata: {},
10
+ },
11
+ },
12
+ },
13
+ }
14
+ BASE_PATH = [:open_trade_transfer_package, :products]
15
+ attr_accessor :id
16
+
17
+ def self.from_data(data)
18
+ products_hash = data.dig(*BASE_PATH)
19
+ return [] if products_hash.nil?
20
+ products_hash.keys.map do |k|
21
+ self.new(id: k, data: data)
22
+ end
23
+ end
24
+
25
+ def initialize(id:, data:)
26
+ @id = id
27
+ @data = data
28
+ end
29
+
30
+ def update_id(new_id)
31
+ product_map = data.dig(*BASE_PATH)
32
+ current_data = product_data
33
+ product_map.delete(id.to_sym)
34
+ product_map[new_id.to_sym] = {
35
+ circuitdata: current_data,
36
+ }
37
+ @id = new_id
38
+ end
39
+
40
+ def product_data
41
+ data.dig(*product_data_path)
42
+ end
43
+
44
+ def product_data=(new_data)
45
+ Bury.bury(data, *product_data_path, new_data)
46
+ product_data.merge!(version: SCHEMA_VERSION)
47
+ end
48
+
49
+ def materials_data
50
+ data.dig(*materials_data_path)
51
+ end
52
+
53
+ def materials_data=(new_data)
54
+ Bury.bury(data, *materials_data_path, new_data)
55
+ end
56
+
57
+ def data=(new_data)
58
+ @data = new_data
59
+ end
60
+
61
+ def data
62
+ @data ||= setup_basic_data
63
+ end
64
+
65
+ def question_answer(path)
66
+ return nil if path.empty?
67
+ path = path.map { |p| p.is_a?(String) ? p.to_sym : p }
68
+ value = Bury.dig(product_data, *path)
69
+ value
70
+ end
71
+
72
+ def set_question_answer(*path, value)
73
+ return if value.nil? && question_answer(path).nil?
74
+ Bury.bury(product_data, *path, value)
75
+ end
76
+
77
+ def layers
78
+ product_data.fetch(:layers, [])
79
+ end
80
+
81
+ def processes
82
+ product_data.fetch(:processes, [])
83
+ end
84
+
85
+ def sections
86
+ product_data.fetch(:sections, [])
87
+ end
88
+
89
+ def metrics
90
+ product_data.fetch(:metrics, {})
91
+ end
92
+
93
+ def exposed_copper
94
+ exposed_area.exposed_copper_area
95
+ end
96
+
97
+ def product_data_path
98
+ [:open_trade_transfer_package, :products, id.to_sym, :circuitdata]
99
+ end
100
+
101
+ def layer_name(uuid)
102
+ layers.find { |l| l[:uuid] == uuid }&.fetch(:name, nil)
103
+ end
104
+
105
+ private
106
+
107
+ def exposed_area
108
+ @exposed_area ||= ExposedArea.new(self)
109
+ end
110
+
111
+ def materials_data_path
112
+ [:open_trade_transfer_package, :custom, :materials, :circuitdata]
113
+ end
114
+
115
+ def setup_basic_data
116
+ new_data = BASIC_PRODUCT_STRUCTURE.deep_dup
117
+ new_data.dig(:open_trade_transfer_package, :products)[id.to_sym] = {
118
+ circuitdata: {
119
+ version: SCHEMA_VERSION,
120
+ },
121
+ }
122
+ new_data
123
+ end
124
+ end
125
+ end