circuitdata 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|