expressir 2.1.18 → 2.1.19

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.
@@ -0,0 +1,249 @@
1
+ require "pathname"
2
+
3
+ module Expressir
4
+ # Coverage module for calculating documentation coverage of EXPRESS entities
5
+ module Coverage
6
+ # Mapping of EXPRESS entity type names to their corresponding class names
7
+ ENTITY_TYPE_MAP = {
8
+ "TYPE" => "Expressir::Model::Declarations::Type",
9
+ "ENTITY" => "Expressir::Model::Declarations::Entity",
10
+ "CONSTANT" => "Expressir::Model::Declarations::Constant",
11
+ "FUNCTION" => "Expressir::Model::Declarations::Function",
12
+ "RULE" => "Expressir::Model::Declarations::Rule",
13
+ "PROCEDURE" => "Expressir::Model::Declarations::Procedure",
14
+ "SUBTYPE_CONSTRAINT" => "Expressir::Model::Declarations::SubtypeConstraint",
15
+ }.freeze
16
+ # Represents a documentation coverage report for EXPRESS schemas
17
+ class Report
18
+ attr_reader :repository, :schema_reports, :total_entities, :documented_entities,
19
+ :undocumented_entities
20
+
21
+ # Initialize a coverage report
22
+ # @param repository [Expressir::Model::Repository] The repository to analyze
23
+ # @param skip_types [Array<String>] Array of entity type names to skip from coverage
24
+ def initialize(repository, skip_types = [])
25
+ @repository = repository
26
+ @skip_types = skip_types
27
+ @schema_reports = []
28
+ @total_entities = []
29
+ @documented_entities = []
30
+ @undocumented_entities = []
31
+
32
+ process_repository
33
+ end
34
+
35
+ # Create a report from a repository
36
+ # @param repository [Expressir::Model::Repository] The repository to analyze
37
+ # @param skip_types [Array<String>] Array of entity type names to skip from coverage
38
+ # @return [Report] The coverage report
39
+ def self.from_repository(repository, skip_types = [])
40
+ new(repository, skip_types)
41
+ end
42
+
43
+ # Create a report from a schema file
44
+ # @param path [String] Path to the schema file
45
+ # @param skip_types [Array<String>] Array of entity type names to skip from coverage
46
+ # @return [Report] The coverage report
47
+ def self.from_file(path, skip_types = [])
48
+ repository = Expressir::Express::Parser.from_file(path)
49
+ new(repository, skip_types)
50
+ end
51
+
52
+ # Calculate the overall coverage percentage
53
+ # @return [Float] The coverage percentage
54
+ def coverage_percentage
55
+ return 100.0 if @total_entities.empty?
56
+
57
+ (@documented_entities.size.to_f / @total_entities.size) * 100
58
+ end
59
+
60
+ # Get file-level reports
61
+ # @return [Array<Hash>] Array of file report hashes
62
+ def file_reports
63
+ @schema_reports.map do |report|
64
+ absolute_path = report[:schema].file
65
+ relative_path = begin
66
+ Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
67
+ rescue ArgumentError
68
+ # If paths are on different drives or otherwise incompatible,
69
+ # fall back to the absolute path
70
+ absolute_path
71
+ end
72
+
73
+ {
74
+ "file" => relative_path,
75
+ "file_basename" => File.basename(absolute_path),
76
+ "directory" => File.dirname(absolute_path),
77
+ "total" => report[:total].size,
78
+ "documented" => report[:documented].size,
79
+ "undocumented" => report[:undocumented],
80
+ "coverage" => report[:coverage],
81
+ }
82
+ end
83
+ end
84
+
85
+ # Get directory-level reports
86
+ # @return [Array<Hash>] Array of directory report hashes
87
+ def directory_reports
88
+ # Group by directory (absolute path)
89
+ by_directory = file_reports.group_by { |r| r["directory"] }
90
+
91
+ # Aggregate stats for each directory
92
+ by_directory.map do |directory, reports|
93
+ # Convert directory to relative path
94
+ relative_directory = begin
95
+ Pathname.new(directory).relative_path_from(Pathname.pwd).to_s
96
+ rescue ArgumentError
97
+ # If paths are on different drives or otherwise incompatible,
98
+ # fall back to the absolute path
99
+ directory
100
+ end
101
+
102
+ total = reports.sum { |r| r["total"] }
103
+ documented = reports.sum { |r| r["documented"] }
104
+ coverage = total.positive? ? (documented.to_f / total) * 100 : 100.0
105
+
106
+ {
107
+ "directory" => relative_directory,
108
+ "total" => total,
109
+ "documented" => documented,
110
+ "undocumented" => total - documented,
111
+ "coverage" => coverage,
112
+ "files" => reports.size,
113
+ }
114
+ end
115
+ end
116
+
117
+ # Convert report to a hash representation
118
+ # @return [Hash] The report as a hash
119
+ def to_h
120
+ {
121
+ "overall" => {
122
+ "total" => @total_entities.size,
123
+ "documented" => @documented_entities.size,
124
+ "undocumented" => @undocumented_entities.size,
125
+ "coverage" => coverage_percentage,
126
+ },
127
+ "directories" => directory_reports,
128
+ "files" => file_reports,
129
+ }
130
+ end
131
+
132
+ private
133
+
134
+ # Process the repository and collect coverage information
135
+ def process_repository
136
+ return unless @repository
137
+
138
+ @repository.schemas.each do |schema|
139
+ schema_report = process_schema(schema)
140
+ @schema_reports << schema_report
141
+
142
+ @total_entities.concat(schema_report[:total])
143
+ @documented_entities.concat(schema_report[:documented])
144
+ @undocumented_entities.concat(schema_report[:undocumented].map do |entity|
145
+ { schema: schema.id, entity: entity }
146
+ end)
147
+ end
148
+ end
149
+
150
+ # Process a schema and collect coverage information
151
+ # @param schema [Expressir::Model::Declarations::Schema] The schema to analyze
152
+ # @return [Hash] A hash with coverage information
153
+ def process_schema(schema)
154
+ entities = Expressir::Coverage.find_entities(schema, @skip_types)
155
+ documented = entities.select { |e| Expressir::Coverage.entity_documented?(e) }
156
+ undocumented = entities - documented
157
+
158
+ coverage = entities.empty? ? 100.0 : (documented.size.to_f / entities.size) * 100
159
+
160
+ {
161
+ schema: schema,
162
+ total: entities,
163
+ documented: documented,
164
+ undocumented: undocumented.map { |e| format_entity(e) },
165
+ coverage: coverage,
166
+ }
167
+ end
168
+
169
+ # Format an entity for display
170
+ # @param entity [Expressir::Model::ModelElement] The entity to format
171
+ # @return [Hash] A hash with type and name of the entity
172
+ def format_entity(entity)
173
+ # Get class name (e.g., "Type" from "Expressir::Model::Declarations::Type")
174
+ type_name = entity.class.name.split("::").last
175
+
176
+ # Convert to EXPRESS convention (e.g., "TYPE")
177
+ express_type = type_name.upcase
178
+
179
+ # Return structured format
180
+ {
181
+ "type" => express_type,
182
+ "name" => entity.id.to_s,
183
+ }
184
+ end
185
+ end
186
+
187
+ # Check if an entity has documentation (remarks)
188
+ # @param entity [Expressir::Model::ModelElement] The entity to check
189
+ # @return [Boolean] True if the entity has documentation
190
+ def self.entity_documented?(entity)
191
+ # Check for direct remarks
192
+ if entity.respond_to?(:remarks) && entity.remarks && !entity.remarks.empty?
193
+ return true
194
+ end
195
+
196
+ # Check for remark_items
197
+ if entity.respond_to?(:remark_items) && entity.remark_items && !entity.remark_items.empty?
198
+ return true
199
+ end
200
+
201
+ # For schema entities, check if there's a remark_item with their ID
202
+ if entity.parent.respond_to?(:remark_items) && entity.parent.remark_items
203
+ entity_id = entity.id.to_s.downcase
204
+ entity.parent.remark_items.any? do |item|
205
+ item.id.to_s.downcase == entity_id || item.id.to_s.downcase.include?("#{entity_id}.")
206
+ end
207
+ else
208
+ false
209
+ end
210
+ end
211
+
212
+ # Find all entities in a schema
213
+ # @param schema [Expressir::Model::Declarations::Schema] The schema to analyze
214
+ # @param skip_types [Array<String>] Array of entity type names to skip from coverage
215
+ # @return [Array<Expressir::Model::ModelElement>] Array of entities
216
+ def self.find_entities(schema, skip_types = [])
217
+ entities = []
218
+
219
+ # Add all schema-level entities
220
+ entities.concat(schema.constants) if schema.constants
221
+ entities.concat(schema.types) if schema.types
222
+ entities.concat(schema.entities) if schema.entities
223
+ entities.concat(schema.functions) if schema.functions
224
+ entities.concat(schema.rules) if schema.rules
225
+ entities.concat(schema.procedures) if schema.procedures
226
+ entities.concat(schema.subtype_constraints) if schema.subtype_constraints
227
+
228
+ # Add enumeration items from types (only if TYPE is not being skipped)
229
+ unless skip_types.include?("TYPE")
230
+ schema.types&.each do |type|
231
+ if type.respond_to?(:enumeration_items) && type.enumeration_items
232
+ entities.concat(type.enumeration_items)
233
+ end
234
+ end
235
+ end
236
+
237
+ # Filter out any nil elements and ensure all have IDs
238
+ entities = entities.compact.select { |e| e.respond_to?(:id) && e.id }
239
+
240
+ # Filter out skipped entity types
241
+ if skip_types.any?
242
+ skip_classes = skip_types.map { |type| ENTITY_TYPE_MAP[type] }.compact
243
+ entities = entities.reject { |entity| skip_classes.include?(entity.class.name) }
244
+ end
245
+
246
+ entities
247
+ end
248
+ end
249
+ end
@@ -219,13 +219,15 @@ module Expressir
219
219
  " ",
220
220
  ].join
221
221
  end,
222
- *if node.supertype_attribute && node.id
222
+ *if node.supertype_attribute && node.id != node.supertype_attribute.attribute.id
223
223
  [
224
224
  "RENAMED",
225
225
  " ",
226
+ node.id,
227
+ " ",
226
228
  ].join
227
229
  end,
228
- *if node.id
230
+ *if node.id && !node.supertype_attribute
229
231
  [
230
232
  node.id,
231
233
  " ",
@@ -581,7 +581,13 @@ module Expressir
581
581
  ctx__redeclared_attribute__qualified_attribute = ctx__redeclared_attribute&.qualified_attribute
582
582
  ctx__redeclared_attribute__attribute_id = ctx__redeclared_attribute&.attribute_id
583
583
 
584
- id = visit_if(ctx__attribute_id || ctx__redeclared_attribute__attribute_id)
584
+ attribute_qualifier = ctx__redeclared_attribute__qualified_attribute&.attribute_qualifier
585
+ attribute_id = attribute_qualifier&.attribute_ref&.attribute_id
586
+ ctx__redeclared_attribute__qualified_attribute__attribute_qualifier_attribute_id = attribute_id
587
+
588
+ id = visit_if(ctx__attribute_id ||
589
+ ctx__redeclared_attribute__attribute_id ||
590
+ ctx__redeclared_attribute__qualified_attribute__attribute_qualifier_attribute_id)
585
591
  supertype_attribute = visit_if(ctx__redeclared_attribute__qualified_attribute)
586
592
 
587
593
  Model::Declarations::Attribute.new(
@@ -806,9 +812,8 @@ module Expressir
806
812
  type = visit_if(ctx__parameter_type)
807
813
  expression = visit_if(ctx__expression)
808
814
 
809
- Model::Declarations::Attribute.new(
810
- id: attribute.id, # reuse
811
- kind: Model::Declarations::Attribute::DERIVED,
815
+ Model::Declarations::DerivedAttribute.new(
816
+ id: attribute.id,
812
817
  supertype_attribute: attribute.supertype_attribute, # reuse
813
818
  type: type,
814
819
  expression: expression,
@@ -1428,9 +1433,8 @@ module Expressir
1428
1433
  visit(ctx__attribute_ref)
1429
1434
  end
1430
1435
 
1431
- Model::Declarations::Attribute.new(
1436
+ Model::Declarations::InverseAttribute.new(
1432
1437
  id: attribute.id, # reuse
1433
- kind: Model::Declarations::Attribute::INVERSE,
1434
1438
  supertype_attribute: attribute.supertype_attribute, # reuse
1435
1439
  type: type,
1436
1440
  expression: expression,
@@ -0,0 +1,25 @@
1
+ module Expressir
2
+ module Model
3
+ module Declarations
4
+ # Specified in ISO 10303-11:2004
5
+ # - section 9.2.1 Attributes
6
+ class DerivedAttribute < Attribute
7
+ include Identifier
8
+
9
+ DERIVED = "DERIVED".freeze
10
+
11
+ attribute :kind, :string, default: -> { DERIVED }
12
+ attribute :_class, :string, default: -> { send(:name) }
13
+
14
+ key_value do
15
+ map "_class", to: :_class, render_default: true
16
+ map "kind", to: :kind, render_default: true
17
+ map "supertype_attribute", to: :supertype_attribute
18
+ map "optional", to: :optional
19
+ map "type", to: :type
20
+ map "expression", to: :expression
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Expressir
2
+ module Model
3
+ module Declarations
4
+ # Specified in ISO 10303-11:2004
5
+ # - section 9.2.1 Attributes
6
+ class InverseAttribute < Attribute
7
+ include Identifier
8
+
9
+ INVERSE = "INVERSE".freeze
10
+
11
+ attribute :kind, :string, default: -> { INVERSE }
12
+ attribute :_class, :string, default: -> { send(:name) }
13
+
14
+ key_value do
15
+ map "_class", to: :_class, render_default: true
16
+ map "kind", to: :kind, render_default: true
17
+ map "supertype_attribute", to: :supertype_attribute
18
+ map "optional", to: :optional
19
+ map "type", to: :type
20
+ map "expression", to: :expression
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -56,6 +56,8 @@ module Expressir
56
56
  "Expressir::Model::DataTypes::Set",
57
57
  "Expressir::Model::DataTypes::String" =>
58
58
  "Expressir::Model::DataTypes::String",
59
+ "Expressir::Model::Declarations::DerivedAttribute" =>
60
+ "Expressir::Model::Declarations::DerivedAttribute",
59
61
  "Expressir::Model::Declarations::Attribute" =>
60
62
  "Expressir::Model::Declarations::Attribute",
61
63
  "Expressir::Model::Declarations::Constant" =>
@@ -1,3 +1,3 @@
1
1
  module Expressir
2
- VERSION = "2.1.18".freeze
2
+ VERSION = "2.1.19".freeze
3
3
  end
data/lib/expressir.rb CHANGED
@@ -3,6 +3,7 @@ require_relative "expressir/version"
3
3
  require_relative "expressir/cli"
4
4
  require_relative "expressir/config"
5
5
  require_relative "expressir/benchmark"
6
+ require_relative "expressir/coverage"
6
7
  require "lutaml/model"
7
8
  require "liquid" # To enable Lutaml::Model::Liquefiable
8
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.18
4
+ version: 2.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-09 00:00:00.000000000 Z
11
+ date: 2025-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ruby-progressbar
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.11'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.11'
111
+ - !ruby/object:Gem::Dependency
112
+ name: terminal-table
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: thor
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -156,7 +184,16 @@ files:
156
184
  - lib/expressir.rb
157
185
  - lib/expressir/benchmark.rb
158
186
  - lib/expressir/cli.rb
187
+ - lib/expressir/commands/base.rb
188
+ - lib/expressir/commands/benchmark.rb
189
+ - lib/expressir/commands/benchmark_cache.rb
190
+ - lib/expressir/commands/clean.rb
191
+ - lib/expressir/commands/coverage.rb
192
+ - lib/expressir/commands/format.rb
193
+ - lib/expressir/commands/validate.rb
194
+ - lib/expressir/commands/version.rb
159
195
  - lib/expressir/config.rb
196
+ - lib/expressir/coverage.rb
160
197
  - lib/expressir/express/cache.rb
161
198
  - lib/expressir/express/error.rb
162
199
  - lib/expressir/express/formatter.rb
@@ -188,11 +225,13 @@ files:
188
225
  - lib/expressir/model/data_types/string.rb
189
226
  - lib/expressir/model/declarations/attribute.rb
190
227
  - lib/expressir/model/declarations/constant.rb
228
+ - lib/expressir/model/declarations/derived_attribute.rb
191
229
  - lib/expressir/model/declarations/entity.rb
192
230
  - lib/expressir/model/declarations/function.rb
193
231
  - lib/expressir/model/declarations/interface.rb
194
232
  - lib/expressir/model/declarations/interface_item.rb
195
233
  - lib/expressir/model/declarations/interfaced_item.rb
234
+ - lib/expressir/model/declarations/inverse_attribute.rb
196
235
  - lib/expressir/model/declarations/parameter.rb
197
236
  - lib/expressir/model/declarations/procedure.rb
198
237
  - lib/expressir/model/declarations/remark_item.rb