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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +17 -9
- data/README.adoc +225 -3
- data/expressir.gemspec +2 -0
- data/lib/expressir/cli.rb +28 -193
- data/lib/expressir/commands/base.rb +32 -0
- data/lib/expressir/commands/benchmark.rb +68 -0
- data/lib/expressir/commands/benchmark_cache.rb +88 -0
- data/lib/expressir/commands/clean.rb +20 -0
- data/lib/expressir/commands/coverage.rb +307 -0
- data/lib/expressir/commands/format.rb +13 -0
- data/lib/expressir/commands/validate.rb +53 -0
- data/lib/expressir/commands/version.rb +9 -0
- data/lib/expressir/coverage.rb +249 -0
- data/lib/expressir/express/formatter.rb +4 -2
- data/lib/expressir/express/visitor.rb +10 -6
- data/lib/expressir/model/declarations/derived_attribute.rb +25 -0
- data/lib/expressir/model/declarations/inverse_attribute.rb +25 -0
- data/lib/expressir/model/model_element.rb +2 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +1 -0
- metadata +41 -2
@@ -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
|
-
|
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::
|
810
|
-
id: attribute.id,
|
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::
|
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" =>
|
data/lib/expressir/version.rb
CHANGED
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.
|
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-
|
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
|