expressir 2.1.26 → 2.1.28

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94ad4b65c4f08a6ff1aafab5424989afee0419e94ac57fbd4715f2daca1da426
4
- data.tar.gz: 21f24a38ca761ced3b7dec0070f08591190de7769e4e43150a4f87db59189ef4
3
+ metadata.gz: 2818d10a5113fdafcb93fef22c7520b2747b0aa6767003239e99266fbe28aacb
4
+ data.tar.gz: 04d9cc82b902107a07f93114ee0a59f551a0969e158e0fa50a125d49d92dd097
5
5
  SHA512:
6
- metadata.gz: 1bc6510434694446b93e6df1e66682e0e737463ad130af77266fa207b0b7214a513817456ee7e42073a419d75ecdbb6adf9f7d34b0077f070269c5eff1d972d4
7
- data.tar.gz: 4d44e23f91d30849da8d550b1cbadbc7628260f444eae1004500b2b5a9489ba3c62333ec7cb61c8d9a5860d1f5b1bef93464ad6c26910902ff0f44cb922150c7
6
+ metadata.gz: b435f41cfa13599b1547769c1052c2e9ac800f86c3282ad6ef0a5608ba77b0ee86bf31fceed7c21595019aa79e87c8327f001dea6d03ba14d1dea7a024adc5a8
7
+ data.tar.gz: 6c93af83e066f0288d41ec7552b4d512a0aa29626dfb8101f7268e92741989704e4131fbf76a976088b05a22187a952459461764bc18cda1f624380a46c92faf
data/.rubocop_todo.yml CHANGED
@@ -1,51 +1,38 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-07-12 00:59:01 UTC using RuboCop version 1.77.0.
3
+ # on 2025-10-13 04:55:00 UTC using RuboCop version 1.81.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: 549
9
+ # Offense count: 565
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
12
12
  # URISchemes: http, https
13
13
  Layout/LineLength:
14
14
  Enabled: false
15
15
 
16
- # Offense count: 1
16
+ # Offense count: 2
17
17
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
18
18
  Lint/DuplicateBranch:
19
19
  Exclude:
20
+ - 'lib/expressir/commands/changes_import_eengine.rb'
20
21
  - 'lib/expressir/coverage.rb'
21
22
 
22
23
  # Offense count: 2
23
24
  # This cop supports safe autocorrection (--autocorrect).
24
- # Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
25
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
25
26
  # NotImplementedExceptions: NotImplementedError
26
27
  Lint/UnusedMethodArgument:
27
28
  Exclude:
28
29
  - 'lib/expressir/express/cache.rb'
29
30
  - 'lib/expressir/express/parser.rb'
30
31
 
31
- # Offense count: 79
32
+ # Offense count: 83
32
33
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
33
34
  Metrics/AbcSize:
34
- Exclude:
35
- - 'lib/expressir/benchmark.rb'
36
- - 'lib/expressir/commands/benchmark.rb'
37
- - 'lib/expressir/commands/benchmark_cache.rb'
38
- - 'lib/expressir/commands/coverage.rb'
39
- - 'lib/expressir/commands/validate.rb'
40
- - 'lib/expressir/config.rb'
41
- - 'lib/expressir/coverage.rb'
42
- - 'lib/expressir/express/formatter.rb'
43
- - 'lib/expressir/express/hyperlink_formatter.rb'
44
- - 'lib/expressir/express/parser.rb'
45
- - 'lib/expressir/express/resolve_references_model_visitor.rb'
46
- - 'lib/expressir/express/visitor.rb'
47
- - 'lib/expressir/model/declarations/schema.rb'
48
- - 'lib/expressir/model/model_element.rb'
35
+ Enabled: false
49
36
 
50
37
  # Offense count: 1
51
38
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
@@ -53,11 +40,14 @@ Metrics/AbcSize:
53
40
  Metrics/BlockLength:
54
41
  Max: 46
55
42
 
56
- # Offense count: 56
43
+ # Offense count: 59
57
44
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
58
45
  Metrics/CyclomaticComplexity:
59
46
  Exclude:
60
47
  - 'lib/expressir/benchmark.rb'
48
+ - 'lib/expressir/changes/schema_change.rb'
49
+ - 'lib/expressir/commands/changes_import_eengine.rb'
50
+ - 'lib/expressir/commands/changes_validate.rb'
61
51
  - 'lib/expressir/commands/coverage.rb'
62
52
  - 'lib/expressir/coverage.rb'
63
53
  - 'lib/expressir/express/formatter.rb'
@@ -68,16 +58,19 @@ Metrics/CyclomaticComplexity:
68
58
  - 'lib/expressir/model/model_element.rb'
69
59
  - 'spec/support/model_element_helper.rb'
70
60
 
71
- # Offense count: 105
61
+ # Offense count: 110
72
62
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
73
63
  Metrics/MethodLength:
74
64
  Max: 106
75
65
 
76
- # Offense count: 44
66
+ # Offense count: 47
77
67
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
78
68
  Metrics/PerceivedComplexity:
79
69
  Exclude:
80
70
  - 'lib/expressir/benchmark.rb'
71
+ - 'lib/expressir/changes/schema_change.rb'
72
+ - 'lib/expressir/commands/changes_import_eengine.rb'
73
+ - 'lib/expressir/commands/changes_validate.rb'
81
74
  - 'lib/expressir/commands/coverage.rb'
82
75
  - 'lib/expressir/coverage.rb'
83
76
  - 'lib/expressir/express/formatter.rb'
@@ -104,7 +97,7 @@ Performance/MapMethodChain:
104
97
  - 'spec/expressir/commands/coverage_ignore_files_spec.rb'
105
98
  - 'spec/expressir/coverage_spec.rb'
106
99
 
107
- # Offense count: 102
100
+ # Offense count: 126
108
101
  # Configuration parameters: CountAsOne.
109
102
  RSpec/ExampleLength:
110
103
  Max: 123
@@ -120,12 +113,7 @@ RSpec/IndexedLet:
120
113
  - 'spec/expressir/model/data_types/set_spec.rb'
121
114
  - 'spec/expressir/model/data_types/string_spec.rb'
122
115
 
123
- # Offense count: 1
124
- RSpec/IteratedExpectation:
125
- Exclude:
126
- - 'spec/expressir/schema_manifest_spec.rb'
127
-
128
- # Offense count: 235
116
+ # Offense count: 257
129
117
  RSpec/MultipleExpectations:
130
118
  Max: 114
131
119
 
@@ -146,7 +134,23 @@ RSpec/RepeatedExample:
146
134
  Exclude:
147
135
  - 'spec/expressir/model/data_types/logical_spec.rb'
148
136
 
137
+ # Offense count: 1
138
+ # This cop supports safe autocorrection (--autocorrect).
139
+ # Configuration parameters: EnforcedStyle, AllowComments.
140
+ # SupportedStyles: empty, nil, both
141
+ Style/EmptyElse:
142
+ Exclude:
143
+ - 'lib/expressir/commands/changes_validate.rb'
144
+
149
145
  # Offense count: 1
150
146
  Style/MissingRespondToMissing:
151
147
  Exclude:
152
148
  - 'lib/expressir/express/visitor.rb'
149
+
150
+ # Offense count: 1
151
+ # This cop supports unsafe autocorrection (--autocorrect-all).
152
+ # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments.
153
+ # AllowedMethods: define_method
154
+ Style/SymbolProc:
155
+ Exclude:
156
+ - 'spec/expressir/commands/changes_import_eengine_spec.rb'
data/README.adoc CHANGED
@@ -76,6 +76,7 @@ $ expressir
76
76
  Commands:
77
77
  expressir benchmark FILE_OR_YAML # Benchmark schema loading performance for a file or list of files from YAML
78
78
  expressir benchmark-cache FILE_OR_YAML # Benchmark schema loading with caching
79
+ expressir changes SUBCOMMAND # Commands for EXPRESS Changes files
79
80
  expressir clean PATH # Strip remarks and prettify EXPRESS schema at PATH
80
81
  expressir format PATH # pretty print EXPRESS schema located at PATH
81
82
  expressir help [COMMAND] # Describe available commands or one specific command
@@ -667,6 +668,107 @@ The default text output displays:
667
668
  This helps identify areas of your EXPRESS schemas that need documentation
668
669
  improvement.
669
670
 
671
+ === EXPRESS Changes files
672
+
673
+ Expressir provides commands for working with EXPRESS Changes files that track
674
+ schema modifications across versions.
675
+
676
+ ==== Validating change files
677
+
678
+ The `changes validate` command validates EXPRESS Changes YAML files and
679
+ optionally normalizes them through round-trip serialization.
680
+
681
+ [source, sh]
682
+ ----
683
+ # Validate a changes file
684
+ expressir changes validate schema.changes.yaml
685
+
686
+ # Validate with verbose output
687
+ expressir changes validate schema.changes.yaml --verbose
688
+
689
+ # Validate and normalize (outputs to stdout)
690
+ expressir changes validate schema.changes.yaml --normalize
691
+
692
+ # Validate and normalize in-place
693
+ expressir changes validate schema.changes.yaml --normalize --in-place
694
+
695
+ # Validate and save normalized output to a new file
696
+ expressir changes validate schema.changes.yaml --normalize --output normalized.yaml
697
+ ----
698
+
699
+ [options="header"]
700
+ |===
701
+ | Option | Description
702
+ | `--normalize` | Normalize file through round-trip serialization
703
+ | `--in-place` | Update file in place (requires `--normalize`)
704
+ | `--output PATH` | Output file path for normalized output
705
+ | `--verbose` | Show verbose output with validation details
706
+ |===
707
+
708
+ The validate command performs the following checks:
709
+
710
+ . Verifies the YAML file can be parsed
711
+ . Validates against the SchemaChange model structure
712
+ . Ensures all required fields are present
713
+ . Checks that change items have valid types
714
+
715
+ When using `--normalize`, the command:
716
+
717
+ . Loads the file and validates it
718
+ . Serializes it back to YAML with consistent formatting
719
+ . Either outputs to stdout, saves in-place, or writes to a new file
720
+
721
+ This is useful for:
722
+
723
+ * **Standardizing formatting**: Ensures consistent YAML structure
724
+ * **Catching errors early**: Validates before committing changes
725
+ * **Cleaning up files**: Removes inconsistencies in formatting
726
+
727
+ ==== Importing from eengine XML
728
+
729
+ The `changes import-eengine` command converts eengine comparison XML files to
730
+ EXPRESS Changes YAML format.
731
+
732
+ The eengine compare XML format is created through
733
+ `exp-engine-engine/kernel/compare.lisp` in the EXPRESS Engine code. Expressir is
734
+ compatible with v5.2.7 of eengine output.
735
+
736
+ [source, sh]
737
+ ----
738
+ # Import and output to stdout
739
+ expressir changes import-eengine comparison.xml schema_name "2"
740
+
741
+ # Import and save to file
742
+ expressir changes import-eengine comparison.xml schema_name "2" -o output.yaml
743
+
744
+ # Import with verbose output
745
+ expressir changes import-eengine comparison.xml schema_name "2" -o output.yaml --verbose
746
+
747
+ # Append to existing changes file
748
+ expressir changes import-eengine comparison.xml schema_name "3" -o existing.yaml
749
+ ----
750
+
751
+ [options="header"]
752
+ |===
753
+ | Option | Description
754
+ | `-o, --output PATH` | Output YAML file path (stdout if not specified)
755
+ | `--verbose` | Show verbose output
756
+ |===
757
+
758
+ The import command:
759
+
760
+ . Parses the eengine XML comparison file
761
+ . Extracts additions, modifications, and deletions
762
+ . Creates or updates an EXPRESS Changes YAML file
763
+ . Supports appending new versions to existing files
764
+
765
+ When the output file already exists:
766
+
767
+ * **Same version**: Replaces the existing edition with that version
768
+ * **New version**: Adds a new edition to the file
769
+
770
+ This allows you to build up a complete change history incrementally.
771
+
670
772
 
671
773
  == Usage: Ruby
672
774
 
@@ -1063,6 +1165,194 @@ expressir coverage schemas.yml --format json --exclude=TYPE:SELECT
1063
1165
 
1064
1166
 
1065
1167
 
1168
+ == Working with EXPRESS Changes
1169
+
1170
+ === General
1171
+
1172
+ Expressir provides the `Changes` module for managing and tracking changes to
1173
+ EXPRESS schemas across versions. This module implements the EXPRESS Changes YAML
1174
+ format defined by ELF (Express Language Foundation).
1175
+
1176
+ The Changes module enables:
1177
+
1178
+ * Loading and saving schema change records from/to YAML files
1179
+ * Programmatic creation and manipulation of change records
1180
+ * Smart edition handling (replace same version, add new version)
1181
+ * Support for all change types: additions, modifications, deletions
1182
+ * Support for mapping changes in ARM/MIM schemas
1183
+
1184
+ === Reading change files
1185
+
1186
+ Load an existing schema change file:
1187
+
1188
+ [source,ruby]
1189
+ ----
1190
+ require "expressir/changes"
1191
+
1192
+ # Load from file
1193
+ change_schema = Expressir::Changes::SchemaChange.from_file("schema.changes.yaml")
1194
+
1195
+ # Access schema name
1196
+ puts "Schema: #{change_schema.schema}"
1197
+
1198
+ # Iterate through change editions
1199
+ change_schema.editions.each do |edition|
1200
+ puts "Version #{edition.version}: #{edition.description}"
1201
+
1202
+ # Access changes by type
1203
+ puts " Additions: #{edition.additions.size}" if edition.additions
1204
+ puts " Modifications: #{edition.modifications.size}" if edition.modifications
1205
+ puts " Deletions: #{edition.deletions.size}" if edition.deletions
1206
+ end
1207
+ ----
1208
+
1209
+ === Creating change records
1210
+
1211
+ Create a new change schema programmatically:
1212
+
1213
+ [source,ruby]
1214
+ ----
1215
+ # Create a new empty change schema
1216
+ change_schema = Expressir::Changes::SchemaChange.new(schema: "my_schema")
1217
+
1218
+ # Create change items
1219
+ new_entity = Expressir::Changes::ItemChange.new(
1220
+ type: "ENTITY",
1221
+ name: "new_entity_name"
1222
+ )
1223
+
1224
+ modified_function = Expressir::Changes::ItemChange.new(
1225
+ type: "FUNCTION",
1226
+ name: "modified_function",
1227
+ description: "Updated parameters"
1228
+ )
1229
+
1230
+ # Add a change edition
1231
+ changes = {
1232
+ additions: [new_entity],
1233
+ modifications: [modified_function],
1234
+ deletions: []
1235
+ }
1236
+
1237
+ change_schema.add_or_update_edition(
1238
+ "2",
1239
+ "Added new entity and modified function",
1240
+ changes
1241
+ )
1242
+
1243
+ # Save to file
1244
+ change_schema.to_file("my_schema.changes.yaml")
1245
+ ----
1246
+
1247
+ === Updating existing change files
1248
+
1249
+ The `add_or_update_edition` method provides smart handling:
1250
+
1251
+ * **Same version**: Replaces the existing edition
1252
+ * **Different version**: Adds a new edition
1253
+
1254
+ [source,ruby]
1255
+ ----
1256
+ # Load existing change file
1257
+ change_schema = Expressir::Changes::SchemaChange.from_file("schema.changes.yaml")
1258
+
1259
+ # Add a new version
1260
+ changes = {
1261
+ modifications: [
1262
+ Expressir::Changes::ItemChange.new(type: "TYPE", name: "updated_type")
1263
+ ]
1264
+ }
1265
+ change_schema.add_or_update_edition("3", "Modified type definition", changes)
1266
+
1267
+ # Or replace existing version
1268
+ change_schema.add_or_update_edition("2", "Revised description", changes)
1269
+
1270
+ # Save changes
1271
+ change_schema.to_file("schema.changes.yaml")
1272
+ ----
1273
+
1274
+ === Change item fields
1275
+
1276
+ Change items support the following fields:
1277
+
1278
+ `type`:: (Required) The EXPRESS construct type (ENTITY, TYPE, FUNCTION, etc.)
1279
+ `name`:: (Required) The name of the construct
1280
+ `description`:: (Optional) Additional details about the change
1281
+ `interfaced_items`:: (Optional) For REFERENCE_FROM items
1282
+
1283
+ [source,ruby]
1284
+ ----
1285
+ item = Expressir::Changes::ItemChange.new(
1286
+ type: "REFERENCE_FROM",
1287
+ name: "measure_schema",
1288
+ interfaced_items: "length_measure"
1289
+ )
1290
+ ----
1291
+
1292
+ === Change edition fields
1293
+
1294
+ Change editions support categorizing changes into:
1295
+
1296
+ `additions`:: New elements added to the schema
1297
+ `modifications`:: Existing elements that were modified
1298
+ `deletions`:: Elements removed from the schema
1299
+ `mapping`:: Mapping-related changes (for ARM/MIM modules)
1300
+ `changes`:: General changes (alternative to mapping)
1301
+
1302
+ [source,ruby]
1303
+ ----
1304
+ edition = Expressir::Changes::EditionChange.new(
1305
+ version: "2",
1306
+ description: "Added support for new functionality",
1307
+ additions: [item1, item2],
1308
+ modifications: [item3],
1309
+ deletions: [item4],
1310
+ mapping: [mapping_change]
1311
+ )
1312
+ ----
1313
+
1314
+ === Mapping changes
1315
+
1316
+ For ARM/MIM schema mappings, use `MappingChange`:
1317
+
1318
+ [source,ruby]
1319
+ ----
1320
+ mapping_change = Expressir::Changes::MappingChange.new(
1321
+ change: "Entity_name ENTITY mapping updated"
1322
+ )
1323
+ ----
1324
+
1325
+ === Example change file format
1326
+
1327
+ [source,yaml]
1328
+ ----
1329
+ ---
1330
+ schema: support_resource_schema
1331
+ editions:
1332
+ - version: '2'
1333
+ description: |-
1334
+ The definitions of the following EXPRESS entity data types were modified:
1335
+
1336
+ * action;
1337
+ * action_directive;
1338
+ * action_method.
1339
+ additions:
1340
+ - type: FUNCTION
1341
+ name: type_check_function
1342
+ modifications:
1343
+ - type: FUNCTION
1344
+ name: bag_to_set
1345
+ - version: '4'
1346
+ description: |-
1347
+ Added support for external element references.
1348
+ additions:
1349
+ - type: ENTITY
1350
+ name: component_path_shape_aspect
1351
+ modifications:
1352
+ - type: FUNCTION
1353
+ name: type_check_function
1354
+ ----
1355
+
1066
1356
  == Contributing
1067
1357
 
1068
1358
  First, thank you for contributing! We love pull requests from everyone. By
data/expressir.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "csv"
37
37
  spec.add_dependency "liquid"
38
38
  spec.add_dependency "lutaml-model"
39
+ spec.add_dependency "moxml"
39
40
  spec.add_dependency "parslet", "~> 2.0"
40
41
  spec.add_dependency "ruby-progressbar", "~> 1.11"
41
42
  spec.add_dependency "table_tennis"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "item_change"
5
+ require_relative "mapping_change"
6
+
7
+ module Expressir
8
+ module Changes
9
+ # Represents a version edition of schema changes
10
+ class EditionChange < Lutaml::Model::Serializable
11
+ attribute :version, :string
12
+ attribute :description, :string
13
+ attribute :additions, ItemChange, collection: true
14
+ attribute :modifications, ItemChange, collection: true
15
+ attribute :deletions, ItemChange, collection: true
16
+ attribute :mapping, MappingChange, collection: true
17
+ attribute :changes, MappingChange, collection: true
18
+
19
+ yaml do
20
+ map "version", to: :version
21
+ map "description", to: :description
22
+ map "additions", to: :additions
23
+ map "modifications", to: :modifications
24
+ map "deletions", to: :deletions
25
+ map "mapping", to: :mapping
26
+ map "changes", to: :changes
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Expressir
6
+ module Changes
7
+ # Represents a single change to an EXPRESS construct
8
+ class ItemChange < Lutaml::Model::Serializable
9
+ attribute :type, :string
10
+ attribute :name, :string
11
+ attribute :description, :string
12
+ attribute :interfaced_items, :string
13
+
14
+ yaml do
15
+ map "type", to: :type
16
+ map "name", to: :name
17
+ map "description", to: :description
18
+ map "interfaced_items", to: :interfaced_items
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Expressir
6
+ module Changes
7
+ # Represents a mapping change entry
8
+ class MappingChange < Lutaml::Model::Serializable
9
+ attribute :change, :string
10
+ attribute :description, :string
11
+
12
+ yaml do
13
+ map "change", to: :change
14
+ map "description", to: :description
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "edition_change"
5
+
6
+ module Expressir
7
+ module Changes
8
+ # Represents changes to an EXPRESS schema across multiple versions
9
+ class SchemaChange < Lutaml::Model::Serializable
10
+ attribute :schema, :string
11
+ attribute :editions, EditionChange, collection: true
12
+
13
+ yaml do
14
+ map "schema", to: :schema
15
+ map "editions", to: :editions
16
+ end
17
+
18
+ class << self
19
+ # Load a SchemaChange from a YAML file
20
+ #
21
+ # @param path [String] Path to the YAML file
22
+ # @return [SchemaChange] The loaded schema changes
23
+ def from_file(path)
24
+ content = File.read(path)
25
+ # Handle empty or minimal YAML files
26
+ return new if content.strip == "---" || content.strip.empty?
27
+
28
+ from_yaml(content)
29
+ end
30
+ end
31
+
32
+ # Add or update a change edition in this schema
33
+ #
34
+ # @param version [String] Version number
35
+ # @param description [String] Description of changes
36
+ # @param changes [Hash] Hash with :additions, :modifications, :deletions
37
+ # @return [EditionChange] The added or updated edition
38
+ def add_or_update_edition(version, description, changes)
39
+ version_str = version.to_s
40
+
41
+ # Initialize editions array if nil
42
+ self.editions ||= []
43
+
44
+ # Find existing edition with this version
45
+ existing_index = editions.find_index do |ed|
46
+ ed.version == version_str
47
+ end
48
+
49
+ # Create new edition
50
+ edition = EditionChange.new(
51
+ version: version_str,
52
+ description: description,
53
+ additions: changes[:additions] || [],
54
+ modifications: changes[:modifications] || [],
55
+ deletions: changes[:deletions] || [],
56
+ )
57
+
58
+ if existing_index
59
+ # Replace existing edition with same version
60
+ editions[existing_index] = edition
61
+ else
62
+ # Add new edition
63
+ editions << edition
64
+ end
65
+
66
+ edition
67
+ end
68
+
69
+ # Save this SchemaChange to a YAML file
70
+ #
71
+ # @param path [String] Path where to save the file
72
+ # @return [Integer] Number of bytes written
73
+ def to_file(path)
74
+ File.write(path, to_yaml)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ # Module for EXPRESS schema change tracking and management
5
+ module Changes
6
+ end
7
+ end
8
+
9
+ require_relative "changes/schema_change"
10
+ require_relative "changes/mapping_change"
data/lib/expressir/cli.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "commands/benchmark"
8
8
  require_relative "commands/benchmark_cache"
9
9
  require_relative "commands/validate"
10
10
  require_relative "commands/coverage"
11
+ require_relative "commands/changes"
11
12
  require_relative "commands/version"
12
13
 
13
14
  module Expressir
@@ -73,6 +74,9 @@ module Expressir
73
74
  Commands::Coverage.new(options).run(paths)
74
75
  end
75
76
 
77
+ desc "changes SUBCOMMAND", "Commands for EXPRESS Changes files"
78
+ subcommand "changes", Commands::Changes
79
+
76
80
  desc "version", "Expressir Version"
77
81
  def version
78
82
  Commands::Version.new(options).run
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Expressir
6
+ module Commands
7
+ # Thor subcommand for EXPRESS Changes file operations
8
+ class Changes < Thor
9
+ desc "validate PATH", "Validate EXPRESS Changes YAML file"
10
+ method_option :normalize, type: :boolean,
11
+ desc: "Normalize file through round-trip serialization"
12
+ method_option :in_place, type: :boolean,
13
+ desc: "Update file in place (requires --normalize)"
14
+ method_option :output, type: :string,
15
+ desc: "Output file path (for normalized output)"
16
+ method_option :verbose, type: :boolean,
17
+ desc: "Show verbose output"
18
+ def validate(path)
19
+ require_relative "changes_validate"
20
+ ChangesValidate.new(options).run(path)
21
+ end
22
+
23
+ desc "import-eengine INPUT_XML SCHEMA_NAME VERSION",
24
+ "Import eengine comparison XML to EXPRESS Changes YAML"
25
+ method_option :output, type: :string, aliases: "-o",
26
+ desc: "Output YAML file path (stdout if not specified)"
27
+ method_option :verbose, type: :boolean,
28
+ desc: "Show verbose output"
29
+ def import_eengine(input_xml, schema_name, version)
30
+ require_relative "changes_import_eengine"
31
+ ChangesImportEengine.call(input_xml, options[:output], schema_name,
32
+ version, **options)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "moxml"
5
+
6
+ module Expressir
7
+ module Commands
8
+ # Command to import eengine comparison XML to EXPRESS Changes YAML
9
+ class ChangesImportEengine < Base
10
+ def self.call(input_file, output_file, schema_name, version, **options)
11
+ new.call(input_file, output_file, schema_name, version, **options)
12
+ end
13
+
14
+ def call(input_file, output_file, schema_name, version, **options)
15
+ require "expressir/changes"
16
+
17
+ # Parse the eengine XML using Moxml
18
+ xml_content = File.read(input_file)
19
+ xml_doc = Moxml.new.parse(xml_content)
20
+
21
+ # Detect XML mode from root element
22
+ xml_mode = detect_xml_mode(xml_doc)
23
+
24
+ # Extract changes from XML
25
+ changes = extract_changes(xml_doc, xml_mode)
26
+ description = generate_description(xml_doc, xml_mode)
27
+
28
+ # Load or create change schema
29
+ change_schema = if output_file && File.exist?(output_file) && File.size(output_file).positive?
30
+ Expressir::Changes::SchemaChange.from_file(output_file)
31
+ else
32
+ Expressir::Changes::SchemaChange.new(schema: schema_name)
33
+ end
34
+
35
+ # Add or update edition
36
+ change_schema.add_or_update_edition(version, description, changes)
37
+
38
+ # Save to file
39
+ if output_file
40
+ change_schema.to_file(output_file)
41
+ puts "Change YAML file written to: #{output_file}" if options[:verbose]
42
+ else
43
+ puts change_schema.to_yaml
44
+ end
45
+
46
+ change_schema
47
+ end
48
+
49
+ private
50
+
51
+ # Detect XML mode from root element (arm, mim, or schema)
52
+ def detect_xml_mode(xml_doc)
53
+ root = xml_doc.root
54
+ return nil unless root
55
+
56
+ case root.name
57
+ when "arm.changes"
58
+ "arm"
59
+ when "mim.changes"
60
+ "mim"
61
+ when "schema.changes"
62
+ "schema"
63
+ else
64
+ # Default to schema mode if unrecognized
65
+ "schema"
66
+ end
67
+ end
68
+
69
+ def extract_changes(xml_doc, xml_mode)
70
+ {
71
+ additions: extract_added_objects(xml_doc, xml_mode),
72
+ modifications: extract_modified_objects(xml_doc, xml_mode),
73
+ deletions: extract_deleted_objects(xml_doc, xml_mode),
74
+ }
75
+ end
76
+
77
+ def extract_modified_objects(xml_doc, xml_mode)
78
+ xpath = "//#{xml_mode}.modifications/modified.object"
79
+ xml_doc.xpath(xpath).map do |node|
80
+ extract_item_change(node)
81
+ end
82
+ end
83
+
84
+ def extract_added_objects(xml_doc, xml_mode)
85
+ xpath = "//#{xml_mode}.additions/modified.object"
86
+ xml_doc.xpath(xpath).map do |node|
87
+ extract_item_change(node)
88
+ end
89
+ end
90
+
91
+ def extract_deleted_objects(xml_doc, xml_mode)
92
+ xpath = "//#{xml_mode}.deletions/modified.object"
93
+ xml_doc.xpath(xpath).map do |node|
94
+ extract_item_change(node)
95
+ end
96
+ end
97
+
98
+ def extract_item_change(node)
99
+ item_change = Expressir::Changes::ItemChange.new(
100
+ type: node["type"],
101
+ name: node["name"],
102
+ )
103
+
104
+ # Extract interfaced.items attribute if present (for interface changes)
105
+ if node["interfaced.items"]
106
+ item_change.interfaced_items = node["interfaced.items"]
107
+ end
108
+
109
+ item_change
110
+ end
111
+
112
+ def generate_description(xml_doc, xml_mode)
113
+ parts = []
114
+
115
+ # Get descriptions from modifications
116
+ xml_doc.xpath("//#{xml_mode}.modifications/modified.object/description").each do |desc|
117
+ text = desc.text.strip
118
+ parts << text unless text.empty?
119
+ end
120
+
121
+ # Get descriptions from additions
122
+ xml_doc.xpath("//#{xml_mode}.additions/modified.object/description").each do |desc|
123
+ text = desc.text.strip
124
+ parts << text unless text.empty?
125
+ end
126
+
127
+ # Get descriptions from deletions
128
+ xml_doc.xpath("//#{xml_mode}.deletions/modified.object/description").each do |desc|
129
+ text = desc.text.strip
130
+ parts << text unless text.empty?
131
+ end
132
+
133
+ parts.join("\n\n")
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Expressir
6
+ module Commands
7
+ # Command to validate and normalize EXPRESS Changes YAML files
8
+ class ChangesValidate < Base
9
+ def run(path)
10
+ require "expressir/changes"
11
+
12
+ # Check if file exists
13
+ unless File.exist?(path)
14
+ exit_with_error("File not found: #{path}")
15
+ end
16
+
17
+ # Validate --in-place requires --normalize
18
+ if options[:in_place] && !options[:normalize]
19
+ exit_with_error("--in-place requires --normalize flag")
20
+ end
21
+
22
+ # Validate --in-place and --output are mutually exclusive
23
+ if options[:in_place] && options[:output]
24
+ exit_with_error("Cannot use both --in-place and --output")
25
+ end
26
+
27
+ begin
28
+ # Load and validate the file
29
+ say "Validating #{path}..." if options[:verbose]
30
+ schema_change = Expressir::Changes::SchemaChange.from_file(path)
31
+
32
+ say "✓ File is valid" if options[:verbose]
33
+ say " Schema: #{schema_change.schema}" if options[:verbose]
34
+ say " Editions: #{schema_change.editions.length}" if options[:verbose]
35
+
36
+ # Normalize if requested
37
+ if options[:normalize]
38
+ say "Normalizing through round-trip serialization..." if options[:verbose]
39
+
40
+ output_path = if options[:in_place]
41
+ path
42
+ elsif options[:output]
43
+ options[:output]
44
+ else
45
+ # Output to stdout
46
+ nil
47
+ end
48
+
49
+ if output_path
50
+ schema_change.to_file(output_path)
51
+ say "✓ Normalized file written to: #{output_path}"
52
+ else
53
+ # Output to stdout
54
+ puts schema_change.to_yaml
55
+ end
56
+ else
57
+ say "✓ File is valid"
58
+ end
59
+ rescue Psych::SyntaxError => e
60
+ exit_with_error("Invalid YAML syntax: #{e.message}")
61
+ rescue StandardError => e
62
+ exit_with_error("Validation failed: #{e.message}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,3 +1,3 @@
1
1
  module Expressir
2
- VERSION = "2.1.26".freeze
2
+ VERSION = "2.1.28".freeze
3
3
  end
data/lib/expressir.rb CHANGED
@@ -170,11 +170,20 @@ module Expressir
170
170
  autoload :Benchmark, "expressir/commands/benchmark"
171
171
  autoload :BenchmarkCache, "expressir/commands/benchmark_cache"
172
172
  autoload :Clean, "expressir/commands/clean"
173
+ autoload :ImportComparison, "expressir/commands/import_comparison"
173
174
  autoload :Coverage, "expressir/commands/coverage"
174
175
  autoload :Format, "expressir/commands/format"
175
176
  autoload :Validate, "expressir/commands/validate"
176
177
  autoload :Version, "expressir/commands/version"
177
178
  end
179
+
180
+ # Autoload for Changes module classes
181
+ module Changes
182
+ autoload :SchemaChange, "expressir/changes/schema_change"
183
+ autoload :EditionChange, "expressir/changes/edition_change"
184
+ autoload :ItemChange, "expressir/changes/item_change"
185
+ autoload :MappingChange, "expressir/changes/mapping_change"
186
+ end
178
187
  end
179
188
 
180
189
  require_relative "expressir/model"
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.26
4
+ version: 2.1.28
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-10-04 00:00:00.000000000 Z
11
+ date: 2025-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: moxml
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: parslet
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -169,10 +183,18 @@ files:
169
183
  - expressir.gemspec
170
184
  - lib/expressir.rb
171
185
  - lib/expressir/benchmark.rb
186
+ - lib/expressir/changes.rb
187
+ - lib/expressir/changes/edition_change.rb
188
+ - lib/expressir/changes/item_change.rb
189
+ - lib/expressir/changes/mapping_change.rb
190
+ - lib/expressir/changes/schema_change.rb
172
191
  - lib/expressir/cli.rb
173
192
  - lib/expressir/commands/base.rb
174
193
  - lib/expressir/commands/benchmark.rb
175
194
  - lib/expressir/commands/benchmark_cache.rb
195
+ - lib/expressir/commands/changes.rb
196
+ - lib/expressir/commands/changes_import_eengine.rb
197
+ - lib/expressir/commands/changes_validate.rb
176
198
  - lib/expressir/commands/clean.rb
177
199
  - lib/expressir/commands/coverage.rb
178
200
  - lib/expressir/commands/format.rb