modspec 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +118 -0
- data/Gemfile +4 -2
- data/README.adoc +508 -17
- data/lib/modspec/conformance_class.rb +39 -10
- data/lib/modspec/conformance_test.rb +40 -13
- data/lib/modspec/identifier.rb +5 -3
- data/lib/modspec/normative_statement.rb +49 -14
- data/lib/modspec/normative_statements_class.rb +39 -12
- data/lib/modspec/suite.rb +242 -5
- data/lib/modspec/version.rb +1 -1
- data/lib/modspec.rb +14 -4
- data/modspec.gemspec +8 -14
- data/spec/conformance_class.liquid +98 -0
- data/spec/fixtures/advanced-cc.yaml +52 -0
- data/spec/fixtures/advanced-json-cc.yaml +24 -0
- data/spec/fixtures/advanced-json-rc.yaml +16 -0
- data/spec/fixtures/advanced-rc.yaml +43 -0
- data/spec/fixtures/basic-quaternion-cc.yaml +44 -0
- data/spec/fixtures/basic-quaternion-json-cc.yaml +24 -0
- data/spec/fixtures/basic-quaternion-json-rc.yaml +17 -0
- data/spec/fixtures/basic-quaternion-json-strict-cc.yaml +22 -0
- data/spec/fixtures/basic-quaternion-json-strict-rc.yaml +19 -0
- data/spec/fixtures/basic-quaternion-rc.yaml +34 -0
- data/spec/fixtures/basic-ypr-cc.yaml +39 -0
- data/spec/fixtures/basic-ypr-json-cc.yaml +23 -0
- data/spec/fixtures/basic-ypr-json-rc.yaml +21 -0
- data/spec/fixtures/basic-ypr-rc.yaml +32 -0
- data/spec/fixtures/chain-cc.yaml +50 -0
- data/spec/fixtures/chain-json-cc.yaml +24 -0
- data/spec/fixtures/chain-json-rc.yaml +20 -0
- data/spec/fixtures/chain-rc.yaml +36 -0
- data/spec/fixtures/frame-spec-cc.yaml +43 -0
- data/spec/fixtures/frame-spec-rc.yaml +27 -0
- data/spec/fixtures/global-cc.yaml +38 -0
- data/spec/fixtures/global-rc.yaml +23 -0
- data/spec/fixtures/graph-cc.yaml +48 -0
- data/spec/fixtures/graph-json-cc.yaml +24 -0
- data/spec/fixtures/graph-json-rc.yaml +20 -0
- data/spec/fixtures/graph-rc.yaml +38 -0
- data/spec/fixtures/series-irregular-cc.yaml +58 -0
- data/spec/fixtures/series-irregular-json-cc.yaml +24 -0
- data/spec/fixtures/series-irregular-json-rc.yaml +20 -0
- data/spec/fixtures/series-irregular-rc.yaml +41 -0
- data/spec/fixtures/series-regular-cc.yaml +63 -0
- data/spec/fixtures/series-regular-json-cc.yaml +24 -0
- data/spec/fixtures/series-regular-json-rc.yaml +20 -0
- data/spec/fixtures/series-regular-rc.yaml +46 -0
- data/spec/fixtures/stream-cc.yaml +49 -0
- data/spec/fixtures/stream-json-cc.yaml +48 -0
- data/spec/fixtures/stream-json-rc.yaml +32 -0
- data/spec/fixtures/stream-rc.yaml +36 -0
- data/spec/fixtures/tangent-point-cc.yaml +43 -0
- data/spec/fixtures/tangent-point-rc.yaml +38 -0
- data/spec/fixtures/time-cc.yaml +32 -0
- data/spec/fixtures/time-rc.yaml +20 -0
- data/spec/modspec/conformance_class_spec.rb +154 -0
- data/spec/modspec/conformance_test_spec.rb +76 -0
- data/spec/modspec/normative_statement_spec.rb +81 -0
- data/spec/modspec/normative_statements_class_spec.rb +61 -0
- data/spec/modspec/suite_spec.rb +109 -0
- data/spec/modspec_spec.rb +25 -0
- data/spec/requirements_class.liquid +93 -0
- data/spec/spec_helper.rb +16 -0
- metadata +67 -57
@@ -1,19 +1,21 @@
|
|
1
|
-
|
2
|
-
require_relative "./conformance_test"
|
3
|
-
require_relative "./identifier"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
3
|
+
require "lutaml/model"
|
4
|
+
require_relative "conformance_test"
|
5
|
+
require_relative "identifier"
|
6
6
|
|
7
|
-
|
7
|
+
module Modspec
|
8
|
+
class ConformanceClass < Lutaml::Model::Serializable
|
8
9
|
attribute :identifier, Identifier
|
9
|
-
attribute :name,
|
10
|
-
attribute :description,
|
11
|
-
attribute :
|
10
|
+
attribute :name, :string
|
11
|
+
attribute :description, :string
|
12
|
+
attribute :guidance, :string, collection: true
|
13
|
+
attribute :classification, :string
|
12
14
|
attribute :dependencies, Identifier, collection: true
|
13
15
|
attribute :target, Identifier, collection: true
|
14
16
|
attribute :tests, ConformanceTest, collection: true
|
15
17
|
attribute :belongs_to, Identifier, collection: true
|
16
|
-
attribute :reference,
|
18
|
+
attribute :reference, :string
|
17
19
|
|
18
20
|
xml do
|
19
21
|
root "conformance-class"
|
@@ -25,8 +27,35 @@ module Modspec
|
|
25
27
|
map_element "tests", to: :tests
|
26
28
|
map_element "belongs_to", to: :belongs_to
|
27
29
|
map_element "description", to: :description
|
30
|
+
map_element "guidance", to: :guidance
|
28
31
|
map_element "reference", to: :reference
|
29
32
|
end
|
30
|
-
end
|
31
33
|
|
34
|
+
def validate
|
35
|
+
errors = super()
|
36
|
+
errors.concat(validate_identifier_prefix)
|
37
|
+
errors.concat(validate_class_children_mapping)
|
38
|
+
errors.concat(tests.flat_map(&:validate))
|
39
|
+
errors
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def validate_class_children_mapping
|
45
|
+
if tests.empty?
|
46
|
+
["Conformance class #{identifier} has no child conformance tests"]
|
47
|
+
else
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_identifier_prefix
|
53
|
+
errors = []
|
54
|
+
expected_prefix = "#{identifier}/"
|
55
|
+
tests.each do |test|
|
56
|
+
errors << "Conformance test #{test.identifier} does not share the expected prefix #{expected_prefix}" unless test.identifier.to_s.start_with?(expected_prefix)
|
57
|
+
end
|
58
|
+
errors
|
59
|
+
end
|
60
|
+
end
|
32
61
|
end
|
@@ -1,21 +1,22 @@
|
|
1
|
-
|
2
|
-
require_relative "./identifier"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require "lutaml/model"
|
4
|
+
require_relative "identifier"
|
5
5
|
|
6
|
-
|
6
|
+
module Modspec
|
7
|
+
class ConformanceTest < Lutaml::Model::Serializable
|
7
8
|
attribute :identifier, Identifier
|
8
|
-
attribute :name,
|
9
|
+
attribute :name, :string
|
9
10
|
attribute :dependencies, Identifier, collection: true
|
10
11
|
attribute :targets, Identifier, collection: true
|
11
12
|
attribute :belongs_to, Identifier, collection: true
|
12
|
-
attribute :description,
|
13
|
-
attribute :guidance,
|
14
|
-
attribute :purpose,
|
15
|
-
attribute :method,
|
16
|
-
attribute :type,
|
17
|
-
attribute :reference,
|
18
|
-
attribute :abstract,
|
13
|
+
attribute :description, :string
|
14
|
+
attribute :guidance, :string, collection: true
|
15
|
+
attribute :purpose, :string
|
16
|
+
attribute :method, :string # Inspection, etc.
|
17
|
+
attribute :type, :string
|
18
|
+
attribute :reference, :string
|
19
|
+
attribute :abstract, :boolean
|
19
20
|
|
20
21
|
xml do
|
21
22
|
root "conformance-test"
|
@@ -32,6 +33,32 @@ module Modspec
|
|
32
33
|
map_element "type", to: :type
|
33
34
|
map_element "reference", to: :reference
|
34
35
|
end
|
35
|
-
end
|
36
36
|
|
37
|
+
attr_accessor :corresponding_requirements, :parent_class
|
38
|
+
|
39
|
+
def validate
|
40
|
+
errors = super()
|
41
|
+
errors.concat(validate_requirement_mapping)
|
42
|
+
errors.concat(validate_class_mapping)
|
43
|
+
errors
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate_requirement_mapping
|
49
|
+
if corresponding_requirements.empty?
|
50
|
+
["Conformance test #{identifier} has no corresponding requirements"]
|
51
|
+
else
|
52
|
+
[]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_class_mapping
|
57
|
+
if parent_class.nil? || !parent_class.tests.include?(self)
|
58
|
+
["Conformance test #{identifier} does not belong to its parent class"]
|
59
|
+
else
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
37
64
|
end
|
data/lib/modspec/identifier.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "lutaml/model"
|
2
4
|
|
3
5
|
module Modspec
|
4
|
-
class Identifier <
|
5
|
-
# attribute :identifier,
|
6
|
+
class Identifier < Lutaml::Model::Type::String
|
7
|
+
# attribute :identifier, :string
|
6
8
|
end
|
7
9
|
end
|
@@ -1,28 +1,32 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "lutaml/model"
|
4
|
+
require_relative "identifier"
|
3
5
|
|
4
6
|
module Modspec
|
5
|
-
class NormativeStatementPart <
|
6
|
-
attribute :statement,
|
7
|
+
class NormativeStatementPart < Lutaml::Model::Serializable
|
8
|
+
attribute :statement, :string
|
7
9
|
end
|
8
10
|
|
9
|
-
class NormativeStatement <
|
11
|
+
class NormativeStatement < Lutaml::Model::Serializable
|
10
12
|
attribute :identifier, Identifier
|
11
|
-
attribute :name,
|
12
|
-
attribute :subject,
|
13
|
-
attribute :statement,
|
14
|
-
attribute :condition,
|
15
|
-
attribute :guidance,
|
13
|
+
attribute :name, :string
|
14
|
+
attribute :subject, :string
|
15
|
+
attribute :statement, :string
|
16
|
+
attribute :condition, :string
|
17
|
+
attribute :guidance, :string, collection: true
|
16
18
|
attribute :inherit, Identifier, collection: true
|
17
19
|
attribute :indirect_dependency, Identifier, collection: true
|
18
20
|
attribute :implements, Identifier, collection: true
|
19
21
|
attribute :dependencies, Identifier, collection: true
|
20
22
|
attribute :belongs_to, Identifier, collection: true
|
21
|
-
attribute :reference,
|
22
|
-
attribute :parts, NormativeStatementPart, collection:true
|
23
|
+
attribute :reference, :string
|
24
|
+
attribute :parts, NormativeStatementPart, collection: true
|
23
25
|
|
24
26
|
# in the future validate: recommendation, permission, requirement
|
25
|
-
attribute :obligation,
|
27
|
+
attribute :obligation, :string,
|
28
|
+
values: %w[recommendation permission requirement],
|
29
|
+
default: -> { "requirement" }
|
26
30
|
|
27
31
|
xml do
|
28
32
|
root "normative-statement"
|
@@ -41,6 +45,37 @@ module Modspec
|
|
41
45
|
map_element "obligation", to: :obligation
|
42
46
|
map_element "parts", to: :parts
|
43
47
|
end
|
44
|
-
end
|
45
48
|
|
49
|
+
def validate(suite = nil)
|
50
|
+
errors = super()
|
51
|
+
errors.concat(validate_dependencies(suite)) if suite
|
52
|
+
errors.concat(validate_nested_requirement)
|
53
|
+
errors
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate_dependencies(suite)
|
59
|
+
errors = []
|
60
|
+
all_dependencies = (dependencies + indirect_dependency + implements).flatten.compact.map(&:to_s)
|
61
|
+
all_identifiers = suite.all_identifiers.map(&:to_s)
|
62
|
+
all_dependencies.each do |dep|
|
63
|
+
errors << "Requirement #{identifier} has an invalid dependency: #{dep}" unless all_identifiers.include?(dep)
|
64
|
+
end
|
65
|
+
errors
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_nested_requirement
|
69
|
+
if has_parent_requirement?
|
70
|
+
["Nested requirement detected: #{identifier}"]
|
71
|
+
else
|
72
|
+
[]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_parent_requirement?
|
77
|
+
# Implementation depends on how you determine if a requirement is nested
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
46
81
|
end
|
@@ -1,20 +1,21 @@
|
|
1
|
-
|
2
|
-
require_relative "./normative_statement"
|
3
|
-
require_relative "./identifier"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
3
|
+
require "lutaml/model"
|
4
|
+
require_relative "normative_statement"
|
5
|
+
require_relative "identifier"
|
6
6
|
|
7
|
-
|
7
|
+
module Modspec
|
8
|
+
class NormativeStatementsClass < Lutaml::Model::Serializable
|
8
9
|
attribute :identifier, Identifier
|
9
|
-
attribute :name,
|
10
|
-
attribute :description,
|
11
|
-
attribute :subject,
|
12
|
-
attribute :guidance,
|
10
|
+
attribute :name, :string
|
11
|
+
attribute :description, :string
|
12
|
+
attribute :subject, :string
|
13
|
+
attribute :guidance, :string, collection: true
|
13
14
|
attribute :dependencies, Identifier, collection: true
|
14
15
|
attribute :normative_statements, NormativeStatement, collection: true
|
15
16
|
attribute :belongs_to, Identifier, collection: true
|
16
|
-
attribute :reference,
|
17
|
-
attribute :source,
|
17
|
+
attribute :reference, :string
|
18
|
+
attribute :source, :string
|
18
19
|
|
19
20
|
xml do
|
20
21
|
root "normative-statements-class"
|
@@ -29,6 +30,32 @@ module Modspec
|
|
29
30
|
map_element "reference", to: :reference
|
30
31
|
map_element "source", to: :source
|
31
32
|
end
|
32
|
-
end
|
33
33
|
|
34
|
+
def validate(suite = nil)
|
35
|
+
errors = super()
|
36
|
+
errors.concat(validate_identifier_prefix)
|
37
|
+
errors.concat(validate_class_children_mapping)
|
38
|
+
errors.concat(normative_statements.flat_map { |n| n.validate(suite) })
|
39
|
+
errors
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def validate_identifier_prefix
|
45
|
+
errors = []
|
46
|
+
expected_prefix = "#{identifier}/"
|
47
|
+
normative_statements.each do |statement|
|
48
|
+
errors << "Normative statement #{statement.identifier} does not share the expected prefix #{expected_prefix}" unless statement.identifier.to_s.start_with?(expected_prefix)
|
49
|
+
end
|
50
|
+
errors
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_class_children_mapping
|
54
|
+
if normative_statements.empty?
|
55
|
+
["Requirement class #{identifier} has no child requirements"]
|
56
|
+
else
|
57
|
+
[]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
34
61
|
end
|
data/lib/modspec/suite.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "lutaml/model"
|
2
4
|
|
3
5
|
module Modspec
|
4
|
-
class Suite <
|
6
|
+
class Suite < Lutaml::Model::Serializable
|
5
7
|
attribute :identifier, Identifier
|
6
|
-
attribute :name,
|
7
|
-
attribute :normative_statements_classes, NormativeStatementsClass, collection:true
|
8
|
-
attribute :conformance_classes, ConformanceClass, collection:true
|
8
|
+
attribute :name, :string
|
9
|
+
attribute :normative_statements_classes, NormativeStatementsClass, collection: true
|
10
|
+
attribute :conformance_classes, ConformanceClass, collection: true
|
9
11
|
|
10
12
|
xml do
|
11
13
|
root "suite"
|
@@ -14,5 +16,240 @@ module Modspec
|
|
14
16
|
map_element "normative-statements-classes", to: :normative_statements_classes
|
15
17
|
map_element "conformance-classes", to: :conformance_classes
|
16
18
|
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
setup_relationships
|
22
|
+
self.all_identifiers = nil
|
23
|
+
errors = super()
|
24
|
+
errors.concat(validate_cycles)
|
25
|
+
errors.concat(validate_label_uniqueness)
|
26
|
+
errors.concat(validate_dependencies)
|
27
|
+
errors.concat(normative_statements_classes.flat_map { |n| n.validate(self) })
|
28
|
+
errors.concat(conformance_classes.flat_map(&:validate))
|
29
|
+
errors
|
30
|
+
end
|
31
|
+
|
32
|
+
def combine(other_suite)
|
33
|
+
raise ArgumentError, "Argument must be a Modspec::Suite" unless other_suite.is_a?(Modspec::Suite)
|
34
|
+
|
35
|
+
combined_suite = dup
|
36
|
+
combined_suite.all_identifiers = nil
|
37
|
+
combined_suite.normative_statements_classes += other_suite.normative_statements_classes
|
38
|
+
combined_suite.conformance_classes += other_suite.conformance_classes
|
39
|
+
|
40
|
+
# Ensure uniqueness of identifiers
|
41
|
+
combined_suite.normative_statements_classes.uniq!(&:identifier)
|
42
|
+
combined_suite.conformance_classes.uniq!(&:identifier)
|
43
|
+
|
44
|
+
combined_suite.name = "#{name} + #{other_suite.name}"
|
45
|
+
|
46
|
+
combined_suite
|
47
|
+
end
|
48
|
+
|
49
|
+
def all_identifiers
|
50
|
+
@all_identifiers ||= (normative_statements_classes.flat_map(&:normative_statements) +
|
51
|
+
conformance_classes.flat_map(&:tests) +
|
52
|
+
normative_statements_classes +
|
53
|
+
conformance_classes).map(&:identifier)
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_writer :all_identifiers
|
57
|
+
|
58
|
+
def resolve_conflicts(other_suite)
|
59
|
+
resolve_conflicts_for(normative_statements_classes, other_suite.normative_statements_classes)
|
60
|
+
resolve_conflicts_for(conformance_classes, other_suite.conformance_classes)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.from_yaml_files(*files)
|
64
|
+
combined_suite = new
|
65
|
+
files.each do |file|
|
66
|
+
suite = from_yaml(File.read(file))
|
67
|
+
combined_suite = combined_suite.combine(suite)
|
68
|
+
end
|
69
|
+
combined_suite.name = "Combined Suite"
|
70
|
+
combined_suite
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup_relationships
|
74
|
+
all_requirements = normative_statements_classes.flat_map(&:normative_statements)
|
75
|
+
|
76
|
+
conformance_classes.each do |cc|
|
77
|
+
cc.tests.each do |ct|
|
78
|
+
ct.corresponding_requirements = all_requirements.select do |r|
|
79
|
+
ct.targets.map(&:to_s).include?(r.identifier.to_s)
|
80
|
+
end
|
81
|
+
ct.parent_class = cc
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def resolve_conflicts_for(self_collection, other_collection)
|
89
|
+
other_collection.each do |other_item|
|
90
|
+
existing_item = self_collection.find { |item| item.identifier == other_item.identifier }
|
91
|
+
if existing_item
|
92
|
+
# Merge attributes of conflicting items
|
93
|
+
merge_attributes(existing_item, other_item)
|
94
|
+
else
|
95
|
+
self_collection << other_item
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge_attributes(existing_item, other_item)
|
101
|
+
existing_item.class.attribute_names.each do |attr|
|
102
|
+
next if %i[identifier name].include?(attr)
|
103
|
+
|
104
|
+
if existing_item.send(attr).is_a?(Array)
|
105
|
+
existing_item.send(attr).concat(other_item.send(attr)).uniq!
|
106
|
+
elsif existing_item.send(attr).nil?
|
107
|
+
existing_item.send("#{attr}=", other_item.send(attr))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def validate_cycles
|
113
|
+
graph = build_dependency_graph
|
114
|
+
cycles = detect_cycles(graph)
|
115
|
+
cycles.map { |cycle| "Cycle detected: #{cycle.join(" -> ")}" }
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_dependency_graph
|
119
|
+
graph = {}
|
120
|
+
all_statements = normative_statements_classes.flat_map(&:normative_statements) +
|
121
|
+
conformance_classes.flat_map(&:tests)
|
122
|
+
|
123
|
+
all_statements.each do |statement|
|
124
|
+
id = statement.identifier.to_s
|
125
|
+
graph[id] = Set.new
|
126
|
+
graph[id].merge(statement.dependencies.map(&:to_s)) if statement.respond_to?(:dependencies)
|
127
|
+
graph[id].merge(statement.indirect_dependency.map(&:to_s)) if statement.respond_to?(:indirect_dependency)
|
128
|
+
graph[id].merge(statement.implements.map(&:to_s)) if statement.respond_to?(:implements)
|
129
|
+
graph[id].merge(statement.targets.map(&:to_s)) if statement.respond_to?(:targets)
|
130
|
+
end
|
131
|
+
|
132
|
+
graph
|
133
|
+
end
|
134
|
+
|
135
|
+
def detect_cycles(graph)
|
136
|
+
cycles = []
|
137
|
+
visited = Set.new
|
138
|
+
recursion_stack = Set.new
|
139
|
+
|
140
|
+
graph.each_key do |node|
|
141
|
+
unless visited.include?(node)
|
142
|
+
cycle = detect_cycle_util(node, graph, visited, recursion_stack, [])
|
143
|
+
cycles << cycle if cycle
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
cycles
|
148
|
+
end
|
149
|
+
|
150
|
+
def detect_cycle_util(node, graph, visited, recursion_stack, path)
|
151
|
+
visited.add(node)
|
152
|
+
recursion_stack.add(node)
|
153
|
+
path.push(node)
|
154
|
+
|
155
|
+
# Check if the node exists in the graph and has dependencies
|
156
|
+
if graph[node]
|
157
|
+
graph[node].each do |neighbor|
|
158
|
+
if !visited.include?(neighbor)
|
159
|
+
cycle = detect_cycle_util(neighbor, graph, visited, recursion_stack, path)
|
160
|
+
return cycle if cycle
|
161
|
+
elsif recursion_stack.include?(neighbor)
|
162
|
+
return path[path.index(neighbor)..] + [neighbor]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
# If the node doesn't exist in the graph, log a warning
|
167
|
+
puts "Warning: Node #{node} referenced but not found in the graph"
|
168
|
+
end
|
169
|
+
|
170
|
+
path.pop
|
171
|
+
recursion_stack.delete(node)
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
|
175
|
+
def validate_label_uniqueness
|
176
|
+
labels = {}
|
177
|
+
errors = []
|
178
|
+
all_statements = normative_statements_classes.flat_map(&:normative_statements) +
|
179
|
+
conformance_classes.flat_map(&:tests)
|
180
|
+
all_statements.each do |statement|
|
181
|
+
if labels[statement.identifier]
|
182
|
+
errors << "Duplicate identifier found: #{statement.identifier}"
|
183
|
+
else
|
184
|
+
labels[statement.identifier] = true
|
185
|
+
end
|
186
|
+
end
|
187
|
+
errors
|
188
|
+
end
|
189
|
+
|
190
|
+
def validate_dependencies
|
191
|
+
all_identifiers = collect_all_identifiers
|
192
|
+
|
193
|
+
errors = []
|
194
|
+
normative_statements_classes.each do |nsc|
|
195
|
+
errors.concat(validate_class_dependencies(nsc, all_identifiers))
|
196
|
+
nsc.normative_statements.each do |ns|
|
197
|
+
errors.concat(validate_statement_dependencies(ns, all_identifiers))
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
conformance_classes.each do |cc|
|
202
|
+
errors.concat(validate_class_dependencies(cc, all_identifiers))
|
203
|
+
cc.tests.each do |ct|
|
204
|
+
errors.concat(validate_test_targets(ct, all_identifiers))
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
errors
|
209
|
+
end
|
210
|
+
|
211
|
+
def collect_all_identifiers
|
212
|
+
identifiers = {}
|
213
|
+
|
214
|
+
normative_statements_classes.each do |nsc|
|
215
|
+
identifiers[nsc.identifier.to_s] = nsc
|
216
|
+
nsc.normative_statements.each do |ns|
|
217
|
+
identifiers[ns.identifier.to_s] = ns
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
conformance_classes.each do |cc|
|
222
|
+
identifiers[cc.identifier.to_s] = cc
|
223
|
+
cc.tests.each do |ct|
|
224
|
+
identifiers[ct.identifier.to_s] = ct
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
identifiers
|
229
|
+
end
|
230
|
+
|
231
|
+
def validate_class_dependencies(klass, all_identifiers)
|
232
|
+
errors = []
|
233
|
+
klass.dependencies&.each do |dep|
|
234
|
+
errors << "Invalid dependency #{dep} in #{klass.identifier}" unless all_identifiers.key?(dep.to_s)
|
235
|
+
end
|
236
|
+
errors
|
237
|
+
end
|
238
|
+
|
239
|
+
def validate_statement_dependencies(statement, all_identifiers)
|
240
|
+
errors = []
|
241
|
+
statement.dependencies&.each do |dep|
|
242
|
+
errors << "Invalid dependency #{dep} in #{statement.identifier}" unless all_identifiers.key?(dep.to_s)
|
243
|
+
end
|
244
|
+
errors
|
245
|
+
end
|
246
|
+
|
247
|
+
def validate_test_targets(test, all_identifiers)
|
248
|
+
errors = []
|
249
|
+
test.targets&.each do |target|
|
250
|
+
errors << "Invalid target #{target} in #{test.identifier}" unless all_identifiers.key?(target.to_s)
|
251
|
+
end
|
252
|
+
errors
|
253
|
+
end
|
17
254
|
end
|
18
255
|
end
|
data/lib/modspec/version.rb
CHANGED
data/lib/modspec.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "modspec/version"
|
4
|
-
require "
|
5
|
-
require "shale/adapter/nokogiri"
|
4
|
+
require "lutaml/model"
|
6
5
|
|
7
6
|
module Modspec
|
8
|
-
Shale.xml_adapter = Shale::Adapter::Nokogiri
|
9
|
-
|
10
7
|
class Error < StandardError; end
|
8
|
+
|
11
9
|
# Your code goes here...
|
12
10
|
end
|
13
11
|
|
@@ -17,3 +15,15 @@ require_relative "modspec/normative_statements_class"
|
|
17
15
|
require_relative "modspec/conformance_test"
|
18
16
|
require_relative "modspec/conformance_class"
|
19
17
|
require_relative "modspec/suite"
|
18
|
+
|
19
|
+
require "lutaml/model/xml_adapter/nokogiri_adapter"
|
20
|
+
require "lutaml/model/json_adapter/standard_json_adapter"
|
21
|
+
require "lutaml/model/toml_adapter/toml_rb_adapter"
|
22
|
+
require "lutaml/model/yaml_adapter/standard_yaml_adapter"
|
23
|
+
|
24
|
+
Lutaml::Model::Config.configure do |config|
|
25
|
+
config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
|
26
|
+
config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter
|
27
|
+
config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter
|
28
|
+
config.toml_adapter = Lutaml::Model::TomlAdapter::TomlRbAdapter
|
29
|
+
end
|
data/modspec.gemspec
CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.email = ["open.source@ribose.com"]
|
14
14
|
|
15
15
|
spec.summary = "Library to work with OGC ModSpec."
|
16
|
-
spec.homepage
|
17
|
-
spec.license
|
16
|
+
spec.homepage = "https://github.com/metanorma/modspec-ruby"
|
17
|
+
spec.license = "BSD-2-Clause"
|
18
18
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
19
19
|
|
20
20
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -22,20 +22,14 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
23
23
|
|
24
24
|
# Specify which files should be added to the gem when it is released.
|
25
|
-
spec.files
|
26
|
-
|
25
|
+
spec.files = all_files_in_git
|
26
|
+
.reject { |f| f.match(%r{\A(?:test|features|bin|\.)/}) }
|
27
27
|
|
28
|
-
spec.bindir
|
29
|
-
spec.executables
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
30
|
spec.require_paths = ["lib"]
|
31
31
|
|
32
|
+
spec.add_dependency "lutaml-model", "~> 0.3.10"
|
32
33
|
spec.add_dependency "nokogiri"
|
33
|
-
spec.add_dependency "
|
34
|
-
# spec.add_dependency "thor"
|
35
|
-
|
36
|
-
spec.add_development_dependency "pry", "~> 0.14.0"
|
37
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
38
|
-
spec.add_development_dependency "rspec", "~> 3.10"
|
39
|
-
# spec.add_development_dependency "rubocop"
|
40
|
-
spec.add_development_dependency "equivalent-xml", "~> 0.6"
|
34
|
+
spec.add_dependency "toml-rb"
|
41
35
|
end
|