modspec 0.1.3 → 0.2.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +4 -0
  3. data/.github/workflows/release.yml +5 -0
  4. data/.rubocop.yml +16 -7
  5. data/.rubocop_todo.yml +67 -80
  6. data/CLAUDE.md +61 -0
  7. data/Gemfile +5 -3
  8. data/lib/modspec/conformance_class.rb +4 -4
  9. data/lib/modspec/conformance_test.rb +21 -6
  10. data/lib/modspec/normative_statement.rb +10 -3
  11. data/lib/modspec/normative_statements_class.rb +7 -5
  12. data/lib/modspec/suite.rb +86 -34
  13. data/lib/modspec/version.rb +1 -1
  14. data/lib/modspec.rb +1 -12
  15. data/modspec.gemspec +3 -2
  16. data/spec/conformance_class.liquid +2 -2
  17. data/spec/fixtures/advanced-json-rc.yaml +5 -7
  18. data/spec/fixtures/advanced-rc.yaml +12 -14
  19. data/spec/fixtures/basic-quaternion-json-rc.yaml +5 -7
  20. data/spec/fixtures/basic-quaternion-json-strict-rc.yaml +4 -5
  21. data/spec/fixtures/basic-quaternion-rc.yaml +7 -8
  22. data/spec/fixtures/basic-ypr-json-rc.yaml +5 -8
  23. data/spec/fixtures/basic-ypr-rc.yaml +7 -7
  24. data/spec/fixtures/chain-json-rc.yaml +5 -7
  25. data/spec/fixtures/chain-rc.yaml +11 -13
  26. data/spec/fixtures/frame-spec-rc.yaml +10 -10
  27. data/spec/fixtures/global-rc.yaml +7 -4
  28. data/spec/fixtures/graph-json-rc.yaml +5 -7
  29. data/spec/fixtures/graph-rc.yaml +11 -13
  30. data/spec/fixtures/series-irregular-json-rc.yaml +5 -7
  31. data/spec/fixtures/series-irregular-rc.yaml +13 -15
  32. data/spec/fixtures/series-regular-json-rc.yaml +5 -7
  33. data/spec/fixtures/series-regular-rc.yaml +15 -17
  34. data/spec/fixtures/stream-json-rc.yaml +9 -11
  35. data/spec/fixtures/stream-rc.yaml +10 -12
  36. data/spec/fixtures/tangent-point-rc.yaml +10 -11
  37. data/spec/fixtures/time-rc.yaml +6 -8
  38. data/spec/modspec/conformance_class_spec.rb +29 -27
  39. data/spec/modspec/conformance_test_spec.rb +6 -5
  40. data/spec/modspec/normative_statement_spec.rb +16 -12
  41. data/spec/modspec/normative_statements_class_spec.rb +4 -4
  42. data/spec/modspec/suite_spec.rb +26 -22
  43. data/spec/modspec_spec.rb +7 -7
  44. data/spec/spec_helper.rb +1 -0
  45. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cd4badc4f810933f2fec27871086eefcc0158e211e13330fb254c6c6b434eb0
4
- data.tar.gz: b43653c35d01208efe9fb99114397488428904549deb1a10db84394b9bdd1b0a
3
+ metadata.gz: 925062f2457c7aa978de3d2eabc679f4a33b9177e8ba2f3a80494c46377ff0b8
4
+ data.tar.gz: '083c357ffd578b297ae28753e48dccacd08a10d202f336299b037d1d6b08fe9a'
5
5
  SHA512:
6
- metadata.gz: 2767799e906ee065a3df1eb05b74f53acb839a0e34e37ed70a1a1418a0f80bdfc0de90e0b3155a958da6f5f35dfcb090c06657e882e17f4f86b0c72b4f42a293
7
- data.tar.gz: '08f9a6974ea4726b0d062338f0e687c8c99a74494ff4a62f56b481c01bd72aa62fda8ef164840d1acef0d5b178e3cc9a4f59d51d137cba9cad10280e559d8b03'
6
+ metadata.gz: ce9246f0f6b4ce811bf498d52a59c25949ad0016b024ab215c332ce7e1e34fa311c2a78267a3a1b07cb1a2f0f468559c1aeaedb638c4552e9500016ad8a3235b
7
+ data.tar.gz: 7132a3d0194c87222d077752784d1d1d7197481d1db1ae356c9f959d8cccd8163f3ef268a2e4e6b75812095768e133ab6da81c629fd3d33b0122f7f2789079fe
@@ -2,6 +2,10 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: rake
4
4
 
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+
5
9
  on:
6
10
  push:
7
11
  branches: [ master, main ]
@@ -2,6 +2,11 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: release
4
4
 
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+ id-token: write
9
+
5
10
  on:
6
11
  workflow_dispatch:
7
12
  inputs:
data/.rubocop.yml CHANGED
@@ -1,10 +1,19 @@
1
- inherit_from: .rubocop_todo.yml
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ inherit_from:
4
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
5
+ - .rubocop_todo.yml
2
6
 
3
- AllCops:
4
- TargetRubyVersion: 3.0
7
+ inherit_mode:
8
+ merge:
9
+ - Exclude
5
10
 
6
- Style/StringLiterals:
7
- EnforcedStyle: double_quotes
11
+ # local repo-specific modifications
12
+ # ...
13
+ plugins:
14
+ - rubocop-rspec
15
+ - rubocop-performance
16
+ - rubocop-rake
8
17
 
9
- Style/StringLiteralsInInterpolation:
10
- EnforcedStyle: double_quotes
18
+ AllCops:
19
+ TargetRubyVersion: 3.0
data/.rubocop_todo.yml CHANGED
@@ -1,118 +1,105 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-09-11 00:07:51 UTC using RuboCop version 1.66.1.
3
+ # on 2026-05-05 14:09:35 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 4
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
12
- # Include: **/*.gemfile, **/Gemfile, **/gems.rb
13
- Bundler/OrderedGems:
14
- Exclude:
15
- - 'Gemfile'
16
-
17
9
  # Offense count: 1
18
- # Configuration parameters: Severity, Include.
19
- # Include: **/*.gemspec
20
10
  Gemspec/RequiredRubyVersion:
21
11
  Exclude:
22
12
  - 'modspec.gemspec'
23
13
 
24
- # Offense count: 10
14
+ # Offense count: 33
25
15
  # This cop supports safe autocorrection (--autocorrect).
26
- # Configuration parameters: EnforcedStyle, IndentationWidth.
27
- # SupportedStyles: aligned, indented, indented_relative_to_receiver
28
- Layout/MultilineMethodCallIndentation:
16
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
+ # URISchemes: http, https
18
+ Layout/LineLength:
29
19
  Exclude:
30
- - 'modspec.gemspec'
20
+ - 'lib/modspec/conformance_class.rb'
21
+ - 'lib/modspec/conformance_test.rb'
22
+ - 'lib/modspec/normative_statement.rb'
23
+ - 'lib/modspec/normative_statements_class.rb'
24
+ - 'lib/modspec/suite.rb'
25
+ - 'spec/modspec/conformance_class_spec.rb'
26
+ - 'spec/modspec/conformance_test_spec.rb'
27
+ - 'spec/modspec/normative_statement_spec.rb'
28
+ - 'spec/modspec/normative_statements_class_spec.rb'
31
29
  - 'spec/modspec/suite_spec.rb'
32
30
 
33
31
  # Offense count: 4
34
- # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
35
- Metrics/AbcSize:
36
- Max: 33
37
-
38
- # Offense count: 6
39
- # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
40
- # AllowedMethods: refine
41
- Metrics/BlockLength:
42
- Max: 138
32
+ # Configuration parameters: AllowComments, AllowEmptyLambdas.
33
+ Lint/EmptyBlock:
34
+ Exclude:
35
+ - 'spec/modspec/conformance_class_spec.rb'
36
+ - 'spec/modspec/suite_spec.rb'
43
37
 
44
38
  # Offense count: 1
45
- # Configuration parameters: CountComments, CountAsOne.
46
- Metrics/ClassLength:
47
- Max: 204
39
+ # This cop supports safe autocorrection (--autocorrect).
40
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
41
+ # NotImplementedExceptions: NotImplementedError
42
+ Lint/UnusedMethodArgument:
43
+ Exclude:
44
+ - 'lib/modspec/normative_statement.rb'
48
45
 
49
- # Offense count: 1
50
- # Configuration parameters: AllowedMethods, AllowedPatterns.
51
- Metrics/CyclomaticComplexity:
52
- Max: 12
46
+ # Offense count: 7
47
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
48
+ Metrics/AbcSize:
49
+ Exclude:
50
+ - 'lib/modspec/suite.rb'
53
51
 
54
52
  # Offense count: 5
53
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
54
+ Metrics/CyclomaticComplexity:
55
+ Exclude:
56
+ - 'lib/modspec/suite.rb'
57
+
58
+ # Offense count: 9
55
59
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
56
60
  Metrics/MethodLength:
57
- Max: 18
61
+ Max: 21
58
62
 
59
- # Offense count: 1
60
- # Configuration parameters: AllowedMethods, AllowedPatterns.
63
+ # Offense count: 3
64
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
61
65
  Metrics/PerceivedComplexity:
62
- Max: 12
63
-
64
- # Offense count: 1
65
- # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros.
66
- # NamePrefix: is_, has_, have_
67
- # ForbiddenPrefixes: is_, has_, have_
68
- # AllowedMethods: is_a?
69
- # MethodDefinitionMacros: define_method, define_singleton_method
70
- Naming/PredicateName:
71
66
  Exclude:
72
- - 'spec/**/*'
73
- - 'lib/modspec/normative_statement.rb'
67
+ - 'lib/modspec/suite.rb'
74
68
 
75
- # Offense count: 6
76
- # Configuration parameters: AllowedConstants.
77
- Style/Documentation:
69
+ # Offense count: 1
70
+ # Configuration parameters: MinSize.
71
+ Performance/CollectionLiteralInLoop:
78
72
  Exclude:
79
- - 'spec/**/*'
80
- - 'test/**/*'
81
- - 'lib/modspec/conformance_class.rb'
82
- - 'lib/modspec/conformance_test.rb'
83
- - 'lib/modspec/normative_statement.rb'
84
- - 'lib/modspec/normative_statements_class.rb'
85
73
  - 'lib/modspec/suite.rb'
86
74
 
87
- # Offense count: 2
88
- # This cop supports safe autocorrection (--autocorrect).
89
- Style/IfUnlessModifier:
90
- Exclude:
91
- - 'lib/modspec/conformance_class.rb'
92
- - 'lib/modspec/normative_statements_class.rb'
75
+ # Offense count: 5
76
+ # Configuration parameters: CountAsOne.
77
+ RSpec/ExampleLength:
78
+ Max: 15
93
79
 
94
- # Offense count: 27
80
+ # Offense count: 1
95
81
  # This cop supports safe autocorrection (--autocorrect).
96
- # Configuration parameters: EnforcedStyleForMultiline.
97
- # SupportedStylesForMultiline: comma, consistent_comma, no_comma
98
- Style/TrailingCommaInArguments:
82
+ RSpec/ExpectActual:
99
83
  Exclude:
100
- - 'spec/modspec/conformance_class_spec.rb'
101
- - 'spec/modspec/conformance_test_spec.rb'
102
84
  - 'spec/modspec/normative_statement_spec.rb'
103
- - 'spec/modspec/normative_statements_class_spec.rb'
104
85
 
105
- # Offense count: 6
106
- # This cop supports safe autocorrection (--autocorrect).
107
- # Configuration parameters: EnforcedStyleForMultiline.
108
- # SupportedStylesForMultiline: comma, consistent_comma, no_comma
109
- Style/TrailingCommaInArrayLiteral:
86
+ # Offense count: 4
87
+ # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
88
+ RSpec/IndexedLet:
110
89
  Exclude:
111
- - 'spec/modspec/conformance_class_spec.rb'
90
+ - 'spec/modspec/normative_statements_class_spec.rb'
91
+ - 'spec/modspec/suite_spec.rb'
112
92
 
113
- # Offense count: 13
114
- # This cop supports safe autocorrection (--autocorrect).
115
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
116
- # URISchemes: http, https
117
- Layout/LineLength:
118
- Max: 247
93
+ # Offense count: 7
94
+ RSpec/MultipleExpectations:
95
+ Max: 9
96
+
97
+ # Offense count: 7
98
+ # Configuration parameters: AllowSubject.
99
+ RSpec/MultipleMemoizedHelpers:
100
+ Max: 12
101
+
102
+ # Offense count: 2
103
+ RSpec/RepeatedExample:
104
+ Exclude:
105
+ - 'spec/modspec_spec.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,61 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `modspec` is a Ruby gem for working with OGC ModSpec — a specification format (OGC 08-131r3) for defining requirements and conformance tests for standards. All models use `lutaml-model` (`Lutaml::Model::Serializable`) for serialization to/from YAML, JSON, and XML.
8
+
9
+ ## Commands
10
+
11
+ - **Run all tests:** `bundle exec rake` or `bundle exec rspec`
12
+ - **Run a single test file:** `bundle exec rspec spec/modspec/suite_spec.rb`
13
+ - **Run a single example:** `bundle exec rspec spec/modspec/suite_spec.rb:42`
14
+ - **Lint:** RuboCop is configured but not in the default rake task. Run with `bundle exec rubocop` if needed.
15
+ - **Console:** `bin/console` (loads IRB with the gem)
16
+
17
+ ## Architecture
18
+
19
+ All model classes inherit from `Lutaml::Model::Serializable` and define attributes via the `attribute` DSL with serialization mappings in `xml do` blocks. YAML/JSON serialization comes from lutaml-model automatically.
20
+
21
+ ### Identifier convention (critical)
22
+
23
+ Identifiers are URIs with a specific structure:
24
+ - Class-level: `/<type>/<class>` (e.g., `/req/example`, `/conf/example`)
25
+ - Instance-level: `/<type>/<class>/<name>` (e.g., `/req/example/foo`)
26
+ - Type prefix: `req` = normative statements, `conf` = conformance tests
27
+
28
+ Validation enforces that children share their parent's identifier prefix.
29
+
30
+ ### Model hierarchy
31
+
32
+ ```
33
+ Suite
34
+ ├── NormativeStatementsClass (collection) → groups normative statements
35
+ │ └── NormativeStatement (collection)
36
+ │ └── NormativeStatementPart (collection) → sub-parts of a statement
37
+ ├── ConformanceClass (collection) → groups conformance tests
38
+ │ └── ConformanceTest (collection)
39
+ ```
40
+
41
+ - `NormativeStatement` has obligation (`requirement`/`recommendation`/`permission`), dependencies, inherit, indirect_dependency, implements, parts
42
+ - `ConformanceTest` has targets (referencing normative statement identifiers), abstract flag, method, purpose
43
+ - `ConformanceTest` uses runtime-only `corresponding_requirements` and `parent_class` attrs set by `Suite#setup_relationships`
44
+
45
+ ### Validation
46
+
47
+ Each model class has a `validate` method that returns an array of error strings. `Suite#validate` orchestrates full validation: cycles (dependency graph via DFS), label uniqueness, dependency resolution, and delegating to child class validators.
48
+
49
+ ### Key Suite operations
50
+
51
+ - `combine(other_suite)` — merges two suites, deduplicating by identifier
52
+ - `from_yaml_files(*files)` — loads and combines multiple YAML files
53
+ - `setup_relationships` — cross-links conformance tests to their target normative statements
54
+
55
+ ### Custom types
56
+
57
+ `Modspec::Identifier` extends `Lutaml::Model::Type::String` — used as the type for all identifier attributes and reference fields (dependencies, targets, inherit, etc.).
58
+
59
+ ## Key dependency
60
+
61
+ `lutaml-model ~> 0.7.7` — provides the `Serializable` base class, attribute DSL, and serialization adapters. Breaking changes in lutaml-model may affect this gem.
data/Gemfile CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in modspec.gemspec
6
5
  gemspec
7
6
 
8
- gem "equivalent-xml"
9
- gem "pry"
7
+ gem "canon"
10
8
  gem "rake"
11
9
  gem "rspec"
10
+ gem "rubocop"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop-rspec"
@@ -18,7 +18,7 @@ module Modspec
18
18
  attribute :reference, :string
19
19
 
20
20
  xml do
21
- root "conformance-class"
21
+ element "conformance-class"
22
22
  map_attribute "identifier", to: :identifier
23
23
  map_element "name", to: :name
24
24
  map_element "dependencies", to: :dependencies
@@ -32,7 +32,7 @@ module Modspec
32
32
  end
33
33
 
34
34
  def validate
35
- errors = super()
35
+ errors = super
36
36
  errors.concat(validate_identifier_prefix)
37
37
  errors.concat(validate_class_children_mapping)
38
38
  errors.concat(tests.flat_map(&:validate))
@@ -42,7 +42,7 @@ module Modspec
42
42
  private
43
43
 
44
44
  def validate_class_children_mapping
45
- if tests.empty?
45
+ if tests.nil? || tests.empty?
46
46
  ["Conformance class #{identifier} has no child conformance tests"]
47
47
  else
48
48
  []
@@ -52,7 +52,7 @@ module Modspec
52
52
  def validate_identifier_prefix
53
53
  errors = []
54
54
  expected_prefix = "#{identifier}/"
55
- tests.each do |test|
55
+ tests&.each do |test|
56
56
  errors << "Conformance test #{test.identifier} does not share the expected prefix #{expected_prefix}" unless test.identifier.to_s.start_with?(expected_prefix)
57
57
  end
58
58
  errors
@@ -13,13 +13,13 @@ module Modspec
13
13
  attribute :description, :string
14
14
  attribute :guidance, :string, collection: true
15
15
  attribute :purpose, :string
16
- attribute :method, :string # Inspection, etc.
16
+ attribute :test_method, :string
17
17
  attribute :type, :string
18
18
  attribute :reference, :string
19
19
  attribute :abstract, :boolean
20
20
 
21
21
  xml do
22
- root "conformance-test"
22
+ element "conformance-test"
23
23
  map_attribute "identifier", to: :identifier
24
24
  map_attribute "abstract", to: :abstract
25
25
  map_element "name", to: :name
@@ -29,15 +29,30 @@ module Modspec
29
29
  map_element "description", to: :description
30
30
  map_element "guidance", to: :guidance
31
31
  map_element "purpose", to: :purpose
32
- map_element "method", to: :method
32
+ map_element "method", to: :test_method
33
33
  map_element "type", to: :type
34
34
  map_element "reference", to: :reference
35
35
  end
36
36
 
37
+ key_value do
38
+ map "identifier", to: :identifier
39
+ map "name", to: :name
40
+ map "dependencies", to: :dependencies
41
+ map "targets", to: :targets
42
+ map "belongs_to", to: :belongs_to
43
+ map "description", to: :description
44
+ map "guidance", to: :guidance
45
+ map "purpose", to: :purpose
46
+ map "method", to: :test_method
47
+ map "type", to: :type
48
+ map "reference", to: :reference
49
+ map "abstract", to: :abstract
50
+ end
51
+
37
52
  attr_accessor :corresponding_requirements, :parent_class
38
53
 
39
54
  def validate
40
- errors = super()
55
+ errors = super
41
56
  errors.concat(validate_requirement_mapping)
42
57
  errors.concat(validate_class_mapping)
43
58
  errors
@@ -46,7 +61,7 @@ module Modspec
46
61
  private
47
62
 
48
63
  def validate_requirement_mapping
49
- if corresponding_requirements.empty?
64
+ if corresponding_requirements.nil? || corresponding_requirements.empty?
50
65
  ["Conformance test #{identifier} has no corresponding requirements"]
51
66
  else
52
67
  []
@@ -54,7 +69,7 @@ module Modspec
54
69
  end
55
70
 
56
71
  def validate_class_mapping
57
- if parent_class.nil? || !parent_class.tests.include?(self)
72
+ if parent_class.nil? || parent_class.tests.nil? || !parent_class.tests.include?(self)
58
73
  ["Conformance test #{identifier} does not belong to its parent class"]
59
74
  else
60
75
  []
@@ -29,7 +29,7 @@ module Modspec
29
29
  default: -> { "requirement" }
30
30
 
31
31
  xml do
32
- root "normative-statement"
32
+ element "normative-statement"
33
33
  map_attribute "identifier", to: :identifier
34
34
  map_element "name", to: :name
35
35
  map_element "subject", to: :subject
@@ -46,7 +46,7 @@ module Modspec
46
46
  map_element "parts", to: :parts
47
47
  end
48
48
 
49
- def validate(suite = nil)
49
+ def validate(suite = nil, register: Lutaml::Model::Config.default_register)
50
50
  errors = super()
51
51
  errors.concat(validate_dependencies(suite)) if suite
52
52
  errors.concat(validate_nested_requirement)
@@ -55,9 +55,16 @@ module Modspec
55
55
 
56
56
  private
57
57
 
58
+ def all_dependencies
59
+ (
60
+ (dependencies || []) +
61
+ (indirect_dependency || []) +
62
+ (implements || [])
63
+ ).flatten.compact
64
+ end
65
+
58
66
  def validate_dependencies(suite)
59
67
  errors = []
60
- all_dependencies = (dependencies + indirect_dependency + implements).flatten.compact.map(&:to_s)
61
68
  all_identifiers = suite.all_identifiers.map(&:to_s)
62
69
  all_dependencies.each do |dep|
63
70
  errors << "Requirement #{identifier} has an invalid dependency: #{dep}" unless all_identifiers.include?(dep)
@@ -12,19 +12,21 @@ module Modspec
12
12
  attribute :subject, :string
13
13
  attribute :guidance, :string, collection: true
14
14
  attribute :dependencies, Identifier, collection: true
15
+ attribute :implements, Identifier, collection: true
15
16
  attribute :normative_statements, NormativeStatement, collection: true
16
17
  attribute :belongs_to, Identifier, collection: true
17
18
  attribute :reference, :string
18
19
  attribute :source, :string
19
20
 
20
21
  xml do
21
- root "normative-statements-class"
22
+ element "normative-statements-class"
22
23
  map_attribute "identifier", to: :identifier
23
24
  map_element "name", to: :name
24
25
  map_element "description", to: :description
25
26
  map_element "subject", to: :subject
26
27
  map_element "guidance", to: :guidance
27
28
  map_element "dependencies", to: :dependencies
29
+ map_element "implements", to: :implements
28
30
  map_element "normative-statements", to: :normative_statements
29
31
  map_element "belongs_to", to: :belongs_to
30
32
  map_element "reference", to: :reference
@@ -42,16 +44,16 @@ module Modspec
42
44
  private
43
45
 
44
46
  def validate_identifier_prefix
45
- errors = []
47
+ return [] if normative_statements.nil? || normative_statements.empty?
48
+
46
49
  expected_prefix = "#{identifier}/"
47
- normative_statements.each do |statement|
50
+ normative_statements.each_with_object([]) do |statement, errors|
48
51
  errors << "Normative statement #{statement.identifier} does not share the expected prefix #{expected_prefix}" unless statement.identifier.to_s.start_with?(expected_prefix)
49
52
  end
50
- errors
51
53
  end
52
54
 
53
55
  def validate_class_children_mapping
54
- if normative_statements.empty?
56
+ if normative_statements.nil? || normative_statements.empty?
55
57
  ["Requirement class #{identifier} has no child requirements"]
56
58
  else
57
59
  []