expressir 2.1.18 → 2.1.20
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 +30 -9
- data/README.adoc +354 -3
- data/expressir.gemspec +2 -1
- data/lib/expressir/cli.rb +29 -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 +342 -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 +474 -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/model.rb +94 -71
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +122 -6
- metadata +33 -8
@@ -0,0 +1,474 @@
|
|
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
|
+
"PARAMETER" => "Expressir::Model::Declarations::Parameter",
|
16
|
+
"VARIABLE" => "Expressir::Model::Declarations::Variable",
|
17
|
+
"ATTRIBUTE" => "Expressir::Model::Declarations::Attribute",
|
18
|
+
"DERIVED_ATTRIBUTE" => "Expressir::Model::Declarations::DerivedAttribute",
|
19
|
+
"INVERSE_ATTRIBUTE" => "Expressir::Model::Declarations::InverseAttribute",
|
20
|
+
"UNIQUE_RULE" => "Expressir::Model::Declarations::UniqueRule",
|
21
|
+
"WHERE_RULE" => "Expressir::Model::Declarations::WhereRule",
|
22
|
+
"ENUMERATION_ITEM" => "Expressir::Model::DataTypes::EnumerationItem",
|
23
|
+
"INTERFACE" => "Expressir::Model::Declarations::Interface",
|
24
|
+
"INTERFACE_ITEM" => "Expressir::Model::Declarations::InterfaceItem",
|
25
|
+
"INTERFACED_ITEM" => "Expressir::Model::Declarations::InterfacedItem",
|
26
|
+
"SCHEMA_VERSION" => "Expressir::Model::Declarations::SchemaVersion",
|
27
|
+
"SCHEMA_VERSION_ITEM" => "Expressir::Model::Declarations::SchemaVersionItem",
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# Mapping of class names to EXPRESS entity type names (for proper formatting)
|
31
|
+
CLASS_TO_EXPRESS_TYPE_MAP = {
|
32
|
+
"Type" => "TYPE",
|
33
|
+
"Entity" => "ENTITY",
|
34
|
+
"Constant" => "CONSTANT",
|
35
|
+
"Function" => "FUNCTION",
|
36
|
+
"Rule" => "RULE",
|
37
|
+
"Procedure" => "PROCEDURE",
|
38
|
+
"SubtypeConstraint" => "SUBTYPE_CONSTRAINT",
|
39
|
+
"Parameter" => "PARAMETER",
|
40
|
+
"Variable" => "VARIABLE",
|
41
|
+
"Attribute" => "ATTRIBUTE",
|
42
|
+
"DerivedAttribute" => "DERIVED_ATTRIBUTE",
|
43
|
+
"InverseAttribute" => "INVERSE_ATTRIBUTE",
|
44
|
+
"UniqueRule" => "UNIQUE_RULE",
|
45
|
+
"WhereRule" => "WHERE_RULE",
|
46
|
+
"EnumerationItem" => "ENUMERATION_ITEM",
|
47
|
+
"Interface" => "INTERFACE",
|
48
|
+
"InterfaceItem" => "INTERFACE_ITEM",
|
49
|
+
"InterfacedItem" => "INTERFACED_ITEM",
|
50
|
+
"SchemaVersion" => "SCHEMA_VERSION",
|
51
|
+
"SchemaVersionItem" => "SCHEMA_VERSION_ITEM",
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
# Available TYPE subtypes based on data types
|
55
|
+
TYPE_SUBTYPES = %w[
|
56
|
+
AGGREGATE ARRAY BAG BINARY BOOLEAN ENUMERATION GENERIC GENERIC_ENTITY
|
57
|
+
INTEGER LIST LOGICAL NUMBER REAL SELECT SET STRING
|
58
|
+
].freeze
|
59
|
+
# Represents a documentation coverage report for EXPRESS schemas
|
60
|
+
class Report
|
61
|
+
attr_reader :repository, :schema_reports, :total_entities, :documented_entities,
|
62
|
+
:undocumented_entities
|
63
|
+
|
64
|
+
# Initialize a coverage report
|
65
|
+
# @param repository [Expressir::Model::Repository] The repository to analyze
|
66
|
+
# @param skip_types [Array<String>] Array of entity type names to skip from coverage
|
67
|
+
def initialize(repository, skip_types = [])
|
68
|
+
@repository = repository
|
69
|
+
@skip_types = skip_types
|
70
|
+
@schema_reports = []
|
71
|
+
@total_entities = []
|
72
|
+
@documented_entities = []
|
73
|
+
@undocumented_entities = []
|
74
|
+
|
75
|
+
process_repository
|
76
|
+
end
|
77
|
+
|
78
|
+
# Create a report from a repository
|
79
|
+
# @param repository [Expressir::Model::Repository] The repository to analyze
|
80
|
+
# @param skip_types [Array<String>] Array of entity type names to skip from coverage
|
81
|
+
# @return [Report] The coverage report
|
82
|
+
def self.from_repository(repository, skip_types = [])
|
83
|
+
new(repository, skip_types)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create a report from a schema file
|
87
|
+
# @param path [String] Path to the schema file
|
88
|
+
# @param skip_types [Array<String>] Array of entity type names to skip from coverage
|
89
|
+
# @return [Report] The coverage report
|
90
|
+
def self.from_file(path, skip_types = [])
|
91
|
+
repository = Expressir::Express::Parser.from_file(path)
|
92
|
+
new(repository, skip_types)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Calculate the overall coverage percentage
|
96
|
+
# @return [Float] The coverage percentage
|
97
|
+
def coverage_percentage
|
98
|
+
return 100.0 if @total_entities.empty?
|
99
|
+
|
100
|
+
(@documented_entities.size.to_f / @total_entities.size) * 100
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get file-level reports
|
104
|
+
# @return [Array<Hash>] Array of file report hashes
|
105
|
+
def file_reports
|
106
|
+
@schema_reports.map do |report|
|
107
|
+
absolute_path = report[:schema].file
|
108
|
+
relative_path = begin
|
109
|
+
Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
|
110
|
+
rescue ArgumentError
|
111
|
+
# If paths are on different drives or otherwise incompatible,
|
112
|
+
# fall back to the absolute path
|
113
|
+
absolute_path
|
114
|
+
end
|
115
|
+
|
116
|
+
{
|
117
|
+
"file" => relative_path,
|
118
|
+
"file_basename" => File.basename(absolute_path),
|
119
|
+
"directory" => File.dirname(absolute_path),
|
120
|
+
"total" => report[:total].size,
|
121
|
+
"documented" => report[:documented].size,
|
122
|
+
"undocumented" => report[:undocumented],
|
123
|
+
"coverage" => report[:coverage],
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get directory-level reports
|
129
|
+
# @return [Array<Hash>] Array of directory report hashes
|
130
|
+
def directory_reports
|
131
|
+
# Group by directory (absolute path)
|
132
|
+
by_directory = file_reports.group_by { |r| r["directory"] }
|
133
|
+
|
134
|
+
# Aggregate stats for each directory
|
135
|
+
by_directory.map do |directory, reports|
|
136
|
+
# Convert directory to relative path
|
137
|
+
relative_directory = begin
|
138
|
+
Pathname.new(directory).relative_path_from(Pathname.pwd).to_s
|
139
|
+
rescue ArgumentError
|
140
|
+
# If paths are on different drives or otherwise incompatible,
|
141
|
+
# fall back to the absolute path
|
142
|
+
directory
|
143
|
+
end
|
144
|
+
|
145
|
+
total = reports.sum { |r| r["total"] }
|
146
|
+
documented = reports.sum { |r| r["documented"] }
|
147
|
+
coverage = total.positive? ? (documented.to_f / total) * 100 : 100.0
|
148
|
+
|
149
|
+
{
|
150
|
+
"directory" => relative_directory,
|
151
|
+
"total" => total,
|
152
|
+
"documented" => documented,
|
153
|
+
"undocumented" => total - documented,
|
154
|
+
"coverage" => coverage,
|
155
|
+
"files" => reports.size,
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Convert report to a hash representation
|
161
|
+
# @return [Hash] The report as a hash
|
162
|
+
def to_h
|
163
|
+
{
|
164
|
+
"overall" => {
|
165
|
+
"total" => @total_entities.size,
|
166
|
+
"documented" => @documented_entities.size,
|
167
|
+
"undocumented" => @undocumented_entities.size,
|
168
|
+
"coverage" => coverage_percentage,
|
169
|
+
},
|
170
|
+
"directories" => directory_reports,
|
171
|
+
"files" => file_reports,
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
# Process the repository and collect coverage information
|
178
|
+
def process_repository
|
179
|
+
return unless @repository
|
180
|
+
|
181
|
+
@repository.schemas.each do |schema|
|
182
|
+
schema_report = process_schema(schema)
|
183
|
+
@schema_reports << schema_report
|
184
|
+
|
185
|
+
@total_entities.concat(schema_report[:total])
|
186
|
+
@documented_entities.concat(schema_report[:documented])
|
187
|
+
@undocumented_entities.concat(schema_report[:undocumented].map do |entity|
|
188
|
+
{ schema: schema.id, entity: entity }
|
189
|
+
end)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Process a schema and collect coverage information
|
194
|
+
# @param schema [Expressir::Model::Declarations::Schema] The schema to analyze
|
195
|
+
# @return [Hash] A hash with coverage information
|
196
|
+
def process_schema(schema)
|
197
|
+
entities = Expressir::Coverage.find_entities(schema, @skip_types)
|
198
|
+
documented = entities.select { |e| Expressir::Coverage.entity_documented?(e) }
|
199
|
+
undocumented = entities - documented
|
200
|
+
|
201
|
+
coverage = entities.empty? ? 100.0 : (documented.size.to_f / entities.size) * 100
|
202
|
+
|
203
|
+
{
|
204
|
+
schema: schema,
|
205
|
+
total: entities,
|
206
|
+
documented: documented,
|
207
|
+
undocumented: undocumented.map { |e| format_entity(e) },
|
208
|
+
coverage: coverage,
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
# Format an entity for display
|
213
|
+
# @param entity [Expressir::Model::ModelElement] The entity to format
|
214
|
+
# @return [Hash] A hash with type and name of the entity
|
215
|
+
def format_entity(entity)
|
216
|
+
# Get class name (e.g., "Type" from "Expressir::Model::Declarations::Type")
|
217
|
+
type_name = entity.class.name.split("::").last
|
218
|
+
|
219
|
+
# Use proper mapping to EXPRESS convention
|
220
|
+
express_type = CLASS_TO_EXPRESS_TYPE_MAP[type_name] || type_name.upcase
|
221
|
+
|
222
|
+
# Return structured format
|
223
|
+
{
|
224
|
+
"type" => express_type,
|
225
|
+
"name" => entity.id.to_s,
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Check if an entity has documentation (remarks)
|
231
|
+
# @param entity [Expressir::Model::ModelElement] The entity to check
|
232
|
+
# @return [Boolean] True if the entity has documentation
|
233
|
+
def self.entity_documented?(entity)
|
234
|
+
# Check for direct remarks
|
235
|
+
if entity.respond_to?(:remarks) && entity.remarks && !entity.remarks.empty?
|
236
|
+
return true
|
237
|
+
end
|
238
|
+
|
239
|
+
# Check for remark_items
|
240
|
+
if entity.respond_to?(:remark_items) && entity.remark_items && !entity.remark_items.empty?
|
241
|
+
return true
|
242
|
+
end
|
243
|
+
|
244
|
+
# For schema entities, check if there's a remark_item with their ID
|
245
|
+
if entity.parent.respond_to?(:remark_items) && entity.parent.remark_items
|
246
|
+
entity_id = entity.id.to_s.downcase
|
247
|
+
entity.parent.remark_items.any? do |item|
|
248
|
+
item.id.to_s.downcase == entity_id || item.id.to_s.downcase.include?("#{entity_id}.")
|
249
|
+
end
|
250
|
+
else
|
251
|
+
false
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Find all entities in a schema or repository
|
256
|
+
# @param schema_or_repo [Expressir::Model::Declarations::Schema, Expressir::Model::Repository] The schema or repository to analyze
|
257
|
+
# @param skip_types [Array<String>] Array of entity type names to skip from coverage
|
258
|
+
# @return [Array<Expressir::Model::ModelElement>] Array of entities
|
259
|
+
def self.find_entities(schema_or_repo, skip_types = [])
|
260
|
+
entities = []
|
261
|
+
|
262
|
+
# Handle both repository and schema inputs
|
263
|
+
if schema_or_repo.is_a?(Expressir::Model::Repository)
|
264
|
+
# If it's a repository, process all schemas
|
265
|
+
schema_or_repo.schemas.each do |schema|
|
266
|
+
entities.concat(find_entities_in_schema(schema))
|
267
|
+
end
|
268
|
+
else
|
269
|
+
# If it's a schema, process it directly
|
270
|
+
entities.concat(find_entities_in_schema(schema_or_repo))
|
271
|
+
end
|
272
|
+
|
273
|
+
# Filter out any nil elements and ensure all have IDs
|
274
|
+
entities = entities.compact.select { |e| e.respond_to?(:id) && e.id }
|
275
|
+
|
276
|
+
# Filter out skipped entity types
|
277
|
+
apply_exclusions(entities, skip_types)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Find all entities in a single schema
|
281
|
+
# @param schema [Expressir::Model::Declarations::Schema] The schema to analyze
|
282
|
+
# @return [Array<Expressir::Model::ModelElement>] Array of entities
|
283
|
+
def self.find_entities_in_schema(schema)
|
284
|
+
entities = []
|
285
|
+
|
286
|
+
# Add all schema-level entities
|
287
|
+
entities.concat(schema.constants) if schema.constants
|
288
|
+
entities.concat(schema.types) if schema.types
|
289
|
+
entities.concat(schema.entities) if schema.entities
|
290
|
+
entities.concat(schema.functions) if schema.functions
|
291
|
+
entities.concat(schema.rules) if schema.rules
|
292
|
+
entities.concat(schema.procedures) if schema.procedures
|
293
|
+
entities.concat(schema.subtype_constraints) if schema.subtype_constraints
|
294
|
+
entities.concat(schema.interfaces) if schema.interfaces
|
295
|
+
|
296
|
+
# Add nested entities recursively
|
297
|
+
entities.concat(find_nested_entities(schema))
|
298
|
+
|
299
|
+
entities
|
300
|
+
end
|
301
|
+
|
302
|
+
# Find all nested entities within a container (schema, entity, function, etc.)
|
303
|
+
# @param container [Expressir::Model::ModelElement] The container to search
|
304
|
+
# @return [Array<Expressir::Model::ModelElement>] Array of nested entities
|
305
|
+
def self.find_nested_entities(container)
|
306
|
+
entities = []
|
307
|
+
|
308
|
+
# Handle different container types
|
309
|
+
case container
|
310
|
+
when Expressir::Model::Declarations::Schema
|
311
|
+
# Schema-level nested entities
|
312
|
+
container.types&.each do |type|
|
313
|
+
entities.concat(find_nested_entities(type))
|
314
|
+
end
|
315
|
+
container.entities&.each do |entity|
|
316
|
+
entities.concat(find_nested_entities(entity))
|
317
|
+
end
|
318
|
+
container.functions&.each do |function|
|
319
|
+
entities.concat(find_nested_entities(function))
|
320
|
+
end
|
321
|
+
container.rules&.each do |rule|
|
322
|
+
entities.concat(find_nested_entities(rule))
|
323
|
+
end
|
324
|
+
container.procedures&.each do |procedure|
|
325
|
+
entities.concat(find_nested_entities(procedure))
|
326
|
+
end
|
327
|
+
container.interfaces&.each do |interface|
|
328
|
+
entities.concat(find_nested_entities(interface))
|
329
|
+
end
|
330
|
+
|
331
|
+
when Expressir::Model::Declarations::Type
|
332
|
+
# Type nested entities
|
333
|
+
if container.respond_to?(:enumeration_items) && container.enumeration_items
|
334
|
+
entities.concat(container.enumeration_items)
|
335
|
+
end
|
336
|
+
|
337
|
+
when Expressir::Model::Declarations::Entity
|
338
|
+
# Entity nested entities
|
339
|
+
entities.concat(container.attributes) if container.attributes
|
340
|
+
entities.concat(container.unique_rules) if container.unique_rules
|
341
|
+
entities.concat(container.where_rules) if container.where_rules
|
342
|
+
|
343
|
+
when Expressir::Model::Declarations::Function
|
344
|
+
# Function nested entities
|
345
|
+
entities.concat(container.parameters) if container.parameters
|
346
|
+
entities.concat(container.variables) if container.variables
|
347
|
+
entities.concat(container.constants) if container.constants
|
348
|
+
entities.concat(container.types) if container.types
|
349
|
+
entities.concat(container.entities) if container.entities
|
350
|
+
entities.concat(container.functions) if container.functions
|
351
|
+
entities.concat(container.procedures) if container.procedures
|
352
|
+
entities.concat(container.subtype_constraints) if container.subtype_constraints
|
353
|
+
|
354
|
+
# Recursively find nested entities in nested containers
|
355
|
+
container.types&.each { |type| entities.concat(find_nested_entities(type)) }
|
356
|
+
container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
|
357
|
+
container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
|
358
|
+
container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
|
359
|
+
|
360
|
+
when Expressir::Model::Declarations::Rule
|
361
|
+
# Rule nested entities
|
362
|
+
entities.concat(container.variables) if container.variables
|
363
|
+
entities.concat(container.constants) if container.constants
|
364
|
+
entities.concat(container.types) if container.types
|
365
|
+
entities.concat(container.entities) if container.entities
|
366
|
+
entities.concat(container.functions) if container.functions
|
367
|
+
entities.concat(container.procedures) if container.procedures
|
368
|
+
entities.concat(container.subtype_constraints) if container.subtype_constraints
|
369
|
+
|
370
|
+
# Recursively find nested entities in nested containers
|
371
|
+
container.types&.each { |type| entities.concat(find_nested_entities(type)) }
|
372
|
+
container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
|
373
|
+
container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
|
374
|
+
container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
|
375
|
+
|
376
|
+
when Expressir::Model::Declarations::Procedure
|
377
|
+
# Procedure nested entities
|
378
|
+
entities.concat(container.parameters) if container.parameters
|
379
|
+
entities.concat(container.variables) if container.variables
|
380
|
+
entities.concat(container.constants) if container.constants
|
381
|
+
entities.concat(container.types) if container.types
|
382
|
+
entities.concat(container.entities) if container.entities
|
383
|
+
entities.concat(container.functions) if container.functions
|
384
|
+
entities.concat(container.procedures) if container.procedures
|
385
|
+
entities.concat(container.subtype_constraints) if container.subtype_constraints
|
386
|
+
|
387
|
+
# Recursively find nested entities in nested containers
|
388
|
+
container.types&.each { |type| entities.concat(find_nested_entities(type)) }
|
389
|
+
container.entities&.each { |entity| entities.concat(find_nested_entities(entity)) }
|
390
|
+
container.functions&.each { |function| entities.concat(find_nested_entities(function)) }
|
391
|
+
container.procedures&.each { |procedure| entities.concat(find_nested_entities(procedure)) }
|
392
|
+
|
393
|
+
when Expressir::Model::Declarations::Interface
|
394
|
+
# Interface nested entities
|
395
|
+
entities.concat(container.items) if container.items
|
396
|
+
end
|
397
|
+
|
398
|
+
entities
|
399
|
+
end
|
400
|
+
|
401
|
+
# Apply exclusions to filter out entities based on skip_types (supports both simple and TYPE:SUBTYPE syntax)
|
402
|
+
# @param entities [Array<Expressir::Model::ModelElement>] The entities to filter
|
403
|
+
# @param exclusions [Array<String>] Array of entity type names to exclude from coverage
|
404
|
+
# @return [Array<Expressir::Model::ModelElement>] Filtered entities
|
405
|
+
def self.apply_exclusions(entities, exclusions)
|
406
|
+
filter_skipped_entities(entities, exclusions)
|
407
|
+
end
|
408
|
+
|
409
|
+
# Filter out entities based on skip_types (supports both simple and TYPE:SUBTYPE syntax)
|
410
|
+
# @param entities [Array<Expressir::Model::ModelElement>] The entities to filter
|
411
|
+
# @param skip_types [Array<String>] Array of entity type names to skip from coverage
|
412
|
+
# @return [Array<Expressir::Model::ModelElement>] Filtered entities
|
413
|
+
def self.filter_skipped_entities(entities, skip_types)
|
414
|
+
return entities if skip_types.empty?
|
415
|
+
|
416
|
+
# Parse skip_types into simple types and TYPE subtypes
|
417
|
+
simple_skips = []
|
418
|
+
type_subtype_skips = []
|
419
|
+
|
420
|
+
skip_types.each do |skip_type|
|
421
|
+
if skip_type.include?(":")
|
422
|
+
# Handle TYPE:SUBTYPE format
|
423
|
+
main_type, subtype = skip_type.split(":", 2)
|
424
|
+
if main_type == "TYPE" && TYPE_SUBTYPES.include?(subtype)
|
425
|
+
type_subtype_skips << subtype
|
426
|
+
end
|
427
|
+
else
|
428
|
+
# Handle simple type format
|
429
|
+
simple_skips << skip_type
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Filter entities
|
434
|
+
entities.reject do |entity|
|
435
|
+
entity_class = entity.class.name
|
436
|
+
|
437
|
+
# Check simple type exclusions
|
438
|
+
# Convert entity class to EXPRESS type name for comparison
|
439
|
+
class_name = entity_class.split("::").last
|
440
|
+
express_type = CLASS_TO_EXPRESS_TYPE_MAP[class_name] || class_name.upcase
|
441
|
+
|
442
|
+
if simple_skips.include?(express_type)
|
443
|
+
true
|
444
|
+
# Check TYPE subtype exclusions
|
445
|
+
elsif entity_class == "Expressir::Model::Declarations::Type" && type_subtype_skips.any?
|
446
|
+
entity_subtype = get_type_subtype(entity)
|
447
|
+
type_subtype_skips.include?(entity_subtype)
|
448
|
+
else
|
449
|
+
false
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# Get the subtype of a TYPE entity based on its underlying_type
|
455
|
+
# @param type_entity [Expressir::Model::Declarations::Type] The TYPE entity
|
456
|
+
# @return [String] The subtype name (e.g., "SELECT", "ENUMERATION")
|
457
|
+
def self.get_type_subtype(type_entity)
|
458
|
+
return nil unless type_entity.respond_to?(:underlying_type) && type_entity.underlying_type
|
459
|
+
|
460
|
+
# Get the class name of the underlying type
|
461
|
+
underlying_class = type_entity.underlying_type.class.name
|
462
|
+
|
463
|
+
# Extract the data type name (e.g., "Select" from "Expressir::Model::DataTypes::Select")
|
464
|
+
if underlying_class.start_with?("Expressir::Model::DataTypes::")
|
465
|
+
data_type_name = underlying_class.split("::").last
|
466
|
+
# Convert to uppercase (e.g., "Select" -> "SELECT")
|
467
|
+
data_type_name.upcase
|
468
|
+
else
|
469
|
+
# For other types, try to extract the last part of the class name
|
470
|
+
underlying_class.split("::").last&.upcase
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
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" =>
|