modspec 0.1.0 → 0.1.2
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 +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 +71 -61
@@ -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
|