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.
- checksums.yaml +5 -5
- data/README.md +25 -122
- data/Rakefile +4 -6
- data/lib/circuitdata.rb +32 -120
- data/lib/circuitdata/bury/bury.rb +61 -0
- data/lib/circuitdata/dereferencer.rb +49 -17
- data/lib/circuitdata/exposed_area.rb +84 -0
- data/lib/circuitdata/json_schema.rb +14 -0
- data/lib/circuitdata/json_validator.rb +56 -0
- data/lib/circuitdata/json_validator/json_schema_error_parser.rb +57 -0
- data/lib/circuitdata/material_validator.rb +40 -0
- data/lib/circuitdata/product.rb +125 -0
- data/lib/circuitdata/product_id_validator.rb +81 -0
- data/lib/circuitdata/profile.rb +31 -69
- data/lib/circuitdata/schema.rb +145 -0
- data/lib/circuitdata/schema_files/schema_v1_dereferenced.json +107155 -0
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema.json +68 -5307
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_generics.json +23 -0
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_materials.json +99 -0
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_products.json +779 -0
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_profiles_and_capabilities.json +323 -0
- data/lib/circuitdata/summary.rb +96 -0
- data/lib/circuitdata/validator.rb +28 -0
- data/lib/circuitdata/version.rb +2 -1
- metadata +113 -20
- data/lib/circuitdata/bk_comparer.rb +0 -106
- data/lib/circuitdata/compatibility_checker.rb +0 -160
- data/lib/circuitdata/file_comparer.rb +0 -276
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_schema_definitions.json +0 -1249
- data/lib/circuitdata/schema_files/v1/ottp_circuitdata_skeleton_schema.json +0 -94
- data/lib/circuitdata/schema_files/v1/ottp_schema_definitions.json +0 -102
- 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
|
-
|
23
|
-
|
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
|
28
|
-
file_path
|
29
|
-
|
30
|
-
|
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
|