circuitdata 0.6.4 → 0.7.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.
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