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.
@@ -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
- 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" =>