suma 0.1.24 → 0.1.25
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.yml +1 -1
- data/.rubocop_todo.yml +43 -60
- data/Gemfile +1 -0
- data/README.adoc +119 -0
- data/lib/suma/cli/compare.rb +210 -0
- data/lib/suma/cli/export.rb +2 -11
- data/lib/suma/cli.rb +19 -0
- data/lib/suma/eengine/errors.rb +27 -0
- data/lib/suma/eengine/wrapper.rb +150 -0
- data/lib/suma/eengine_converter.rb +32 -0
- data/lib/suma/export_standalone_schema.rb +14 -0
- data/lib/suma/schema_exporter.rb +2 -1
- data/lib/suma/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fda8e972931c092b7c01d4eaafda7ada9d0f29739d584123b527bf2b5a75075
|
4
|
+
data.tar.gz: c9efe1f70b118180a770f843a6858b3d73e435349a6148e857516fe603dcb688
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce79c333f25940d8a0a201a3ff65d40c68d816581c9cfadecf3900684ff2ca043597203e6d5a2fa728dfcdd0bfe5415ce80923ed385273dc0df0518f3f183631
|
7
|
+
data.tar.gz: aa335cf0c8669cd27d5d3b7c715c23d16278337b42db554d9971e0fd1b9a3e9d89e056756e1cc5c08d555635059f83a3688691f05763dcc8a1f267425a8f3ddb
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2025-10-
|
3
|
+
# on 2025-10-15 10:00:06 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
|
@@ -18,55 +18,12 @@ Gemspec/RequiredRubyVersion:
|
|
18
18
|
Exclude:
|
19
19
|
- 'suma.gemspec'
|
20
20
|
|
21
|
-
# Offense count:
|
22
|
-
# This cop supports safe autocorrection (--autocorrect).
|
23
|
-
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
24
|
-
# SupportedStyles: with_first_argument, with_fixed_indentation
|
25
|
-
Layout/ArgumentAlignment:
|
26
|
-
Exclude:
|
27
|
-
- 'spec/suma/cli/export_spec.rb'
|
28
|
-
|
29
|
-
# Offense count: 1
|
30
|
-
# This cop supports safe autocorrection (--autocorrect).
|
31
|
-
# Configuration parameters: EnforcedStyleAlignWith.
|
32
|
-
# SupportedStylesAlignWith: either, start_of_block, start_of_line
|
33
|
-
Layout/BlockAlignment:
|
34
|
-
Exclude:
|
35
|
-
- 'spec/suma/cli/export_spec.rb'
|
36
|
-
|
37
|
-
# Offense count: 1
|
38
|
-
# This cop supports safe autocorrection (--autocorrect).
|
39
|
-
Layout/BlockEndNewline:
|
40
|
-
Exclude:
|
41
|
-
- 'spec/suma/cli/export_spec.rb'
|
42
|
-
|
43
|
-
# Offense count: 2
|
44
|
-
# This cop supports safe autocorrection (--autocorrect).
|
45
|
-
# Configuration parameters: Width, AllowedPatterns.
|
46
|
-
Layout/IndentationWidth:
|
47
|
-
Exclude:
|
48
|
-
- 'spec/suma/cli/export_spec.rb'
|
49
|
-
|
50
|
-
# Offense count: 74
|
21
|
+
# Offense count: 84
|
51
22
|
# This cop supports safe autocorrection (--autocorrect).
|
52
23
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
53
24
|
# URISchemes: http, https
|
54
25
|
Layout/LineLength:
|
55
|
-
|
56
|
-
- 'lib/suma/cli/build.rb'
|
57
|
-
- 'lib/suma/cli/validate.rb'
|
58
|
-
- 'lib/suma/collection_manifest.rb'
|
59
|
-
- 'lib/suma/jsdai/figure.rb'
|
60
|
-
- 'lib/suma/jsdai/figure_image.rb'
|
61
|
-
- 'lib/suma/processor.rb'
|
62
|
-
- 'lib/suma/schema_attachment.rb'
|
63
|
-
- 'lib/suma/schema_document.rb'
|
64
|
-
- 'lib/suma/thor_ext.rb'
|
65
|
-
- 'spec/suma/cli/export_spec.rb'
|
66
|
-
- 'spec/suma/cli/extract_terms_spec.rb'
|
67
|
-
- 'spec/suma/cli/validate_ascii_spec.rb'
|
68
|
-
- 'spec/suma/jsdai/figure_spec.rb'
|
69
|
-
- 'suma.gemspec'
|
26
|
+
Enabled: false
|
70
27
|
|
71
28
|
# Offense count: 2
|
72
29
|
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
@@ -84,30 +41,48 @@ Lint/IneffectiveAccessModifier:
|
|
84
41
|
Exclude:
|
85
42
|
- 'lib/suma/cli/export.rb'
|
86
43
|
|
87
|
-
# Offense count:
|
44
|
+
# Offense count: 11
|
88
45
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
89
46
|
Metrics/AbcSize:
|
90
47
|
Exclude:
|
48
|
+
- 'lib/suma/cli/compare.rb'
|
91
49
|
- 'lib/suma/jsdai/figure.rb'
|
92
50
|
- 'lib/suma/jsdai/figure_image.rb'
|
93
51
|
- 'lib/suma/schema_attachment.rb'
|
94
52
|
- 'lib/suma/schema_document.rb'
|
95
53
|
- 'lib/suma/thor_ext.rb'
|
96
54
|
|
97
|
-
# Offense count:
|
55
|
+
# Offense count: 6
|
98
56
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
99
57
|
Metrics/CyclomaticComplexity:
|
100
58
|
Exclude:
|
59
|
+
- 'lib/suma/cli/compare.rb'
|
60
|
+
- 'lib/suma/eengine/wrapper.rb'
|
101
61
|
- 'lib/suma/jsdai/figure.rb'
|
102
62
|
- 'lib/suma/jsdai/figure_image.rb'
|
103
63
|
- 'lib/suma/thor_ext.rb'
|
104
64
|
|
105
|
-
# Offense count:
|
65
|
+
# Offense count: 5
|
66
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
67
|
+
Metrics/MethodLength:
|
68
|
+
Max: 60
|
69
|
+
|
70
|
+
# Offense count: 4
|
106
71
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
107
72
|
Metrics/PerceivedComplexity:
|
108
73
|
Exclude:
|
74
|
+
- 'lib/suma/cli/compare.rb'
|
75
|
+
- 'lib/suma/eengine/wrapper.rb'
|
109
76
|
- 'lib/suma/jsdai/figure_image.rb'
|
110
77
|
|
78
|
+
# Offense count: 1
|
79
|
+
# Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
|
80
|
+
# AllowedMethods: call
|
81
|
+
# WaywardPredicates: nonzero?
|
82
|
+
Naming/PredicateMethod:
|
83
|
+
Exclude:
|
84
|
+
- 'lib/suma/eengine/wrapper.rb'
|
85
|
+
|
111
86
|
# Offense count: 6
|
112
87
|
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
113
88
|
# SupportedStyles: snake_case, normalcase, non_integer
|
@@ -131,18 +106,32 @@ RSpec/ContextWording:
|
|
131
106
|
Exclude:
|
132
107
|
- 'spec/suma/cli/export_spec.rb'
|
133
108
|
|
134
|
-
# Offense count:
|
109
|
+
# Offense count: 51
|
135
110
|
# Configuration parameters: CountAsOne.
|
136
111
|
RSpec/ExampleLength:
|
137
112
|
Max: 44
|
138
113
|
|
139
|
-
# Offense count:
|
114
|
+
# Offense count: 4
|
140
115
|
# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
|
141
116
|
RSpec/IndexedLet:
|
142
117
|
Exclude:
|
118
|
+
- 'spec/suma/cli/compare_spec.rb'
|
143
119
|
- 'spec/suma/cli/export_spec.rb'
|
144
120
|
|
145
|
-
# Offense count:
|
121
|
+
# Offense count: 22
|
122
|
+
# Configuration parameters: AssignmentOnly.
|
123
|
+
RSpec/InstanceVariable:
|
124
|
+
Exclude:
|
125
|
+
- 'spec/suma/cli/compare_spec.rb'
|
126
|
+
|
127
|
+
# Offense count: 1
|
128
|
+
# Configuration parameters: EnforcedStyle.
|
129
|
+
# SupportedStyles: have_received, receive
|
130
|
+
RSpec/MessageSpies:
|
131
|
+
Exclude:
|
132
|
+
- 'spec/suma/cli/compare_spec.rb'
|
133
|
+
|
134
|
+
# Offense count: 46
|
146
135
|
RSpec/MultipleExpectations:
|
147
136
|
Max: 12
|
148
137
|
|
@@ -154,12 +143,6 @@ RSpec/SpecFilePathFormat:
|
|
154
143
|
- 'spec/suma/cli/expressir_spec.rb'
|
155
144
|
|
156
145
|
# Offense count: 1
|
157
|
-
|
158
|
-
# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
|
159
|
-
# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
|
160
|
-
# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
|
161
|
-
# FunctionalMethods: let, let!, subject, watch
|
162
|
-
# AllowedMethods: lambda, proc, it
|
163
|
-
Style/BlockDelimiters:
|
146
|
+
RSpec/StubbedMock:
|
164
147
|
Exclude:
|
165
|
-
- 'spec/suma/cli/
|
148
|
+
- 'spec/suma/cli/compare_spec.rb'
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
@@ -698,6 +698,125 @@ express-files/
|
|
698
698
|
----
|
699
699
|
|
700
700
|
|
701
|
+
=== Compare schemas command
|
702
|
+
|
703
|
+
==== General
|
704
|
+
|
705
|
+
The `suma compare` command compares two EXPRESS schemas using eengine and
|
706
|
+
generates an EXPRESS Changes YAML file tracking the differences.
|
707
|
+
|
708
|
+
This command is essential for managing schema evolution across versions,
|
709
|
+
particularly when working with multiple git branches of the same repository.
|
710
|
+
|
711
|
+
[source,sh]
|
712
|
+
----
|
713
|
+
$ suma compare TRIAL_SCHEMA REFERENCE_SCHEMA --version VERSION [options]
|
714
|
+
----
|
715
|
+
|
716
|
+
Parameters:
|
717
|
+
|
718
|
+
`TRIAL_SCHEMA`:: Path to the new/trial EXPRESS schema file
|
719
|
+
`REFERENCE_SCHEMA`:: Path to the old/reference EXPRESS schema file
|
720
|
+
|
721
|
+
Options:
|
722
|
+
|
723
|
+
`--version=VERSION`, `-v VERSION`:: (required) Version number for this change edition
|
724
|
+
`--output=PATH`, `-o PATH`:: Output Change YAML file path (default: `{schema}.changes.yaml` in trial schema directory)
|
725
|
+
`--mode=MODE`:: Schema comparison mode: `resource` or `module` (default: `resource`)
|
726
|
+
`--trial-stepmod=PATH`:: Override auto-detected trial repository root
|
727
|
+
`--reference-stepmod=PATH`:: Override auto-detected reference repository root
|
728
|
+
`--verbose`:: Enable verbose output
|
729
|
+
|
730
|
+
==== Typical workflow
|
731
|
+
|
732
|
+
The compare command is designed for a two-repository workflow where you have
|
733
|
+
different branches checked out:
|
734
|
+
|
735
|
+
[source,sh]
|
736
|
+
----
|
737
|
+
# 1. Check out old version at /path/to/repo-old
|
738
|
+
# 2. Check out new version at /path/to/repo-new
|
739
|
+
# 3. Compare schemas:
|
740
|
+
$ suma compare \
|
741
|
+
/path/to/repo-new/schemas/resources/action_schema/action_schema.exp \
|
742
|
+
/path/to/repo-old/schemas/resources/action_schema/action_schema.exp \
|
743
|
+
--version 2
|
744
|
+
----
|
745
|
+
|
746
|
+
==== Behavior
|
747
|
+
|
748
|
+
The command:
|
749
|
+
|
750
|
+
* Auto-detects git repository roots from schema file paths
|
751
|
+
* Uses detected roots as stepmod paths for eengine
|
752
|
+
* Generates or updates `.changes.yaml` file in the trial schema directory
|
753
|
+
* **Replaces** existing change edition if the version already exists
|
754
|
+
* **Adds** new change edition if the version is different
|
755
|
+
|
756
|
+
==== Requirements
|
757
|
+
|
758
|
+
This command requires `eengine` to be installed:
|
759
|
+
|
760
|
+
* macOS: https://github.com/expresslang/homebrew-eengine
|
761
|
+
* Linux: https://github.com/expresslang/eengine-releases
|
762
|
+
|
763
|
+
[example]
|
764
|
+
.To compare schemas with auto-detection
|
765
|
+
[source,sh]
|
766
|
+
----
|
767
|
+
$ suma compare \
|
768
|
+
~/iso-10303-new/schemas/resources/support_resource_schema/support_resource_schema.exp \
|
769
|
+
~/iso-10303-old/schemas/resources/support_resource_schema/support_resource_schema.exp \
|
770
|
+
--version 2
|
771
|
+
# => generates or updates support_resource_schema.changes.yaml
|
772
|
+
----
|
773
|
+
|
774
|
+
[example]
|
775
|
+
.To compare with verbose output
|
776
|
+
[source,sh]
|
777
|
+
----
|
778
|
+
$ suma compare schema_new.exp schema_old.exp --version 2 --verbose
|
779
|
+
# Shows: eengine version, detected repo roots, XML generation, etc.
|
780
|
+
----
|
781
|
+
|
782
|
+
[example]
|
783
|
+
.To specify custom output location
|
784
|
+
[source,sh]
|
785
|
+
----
|
786
|
+
$ suma compare schema_new.exp schema_old.exp \
|
787
|
+
--version 2 \
|
788
|
+
--output /path/to/custom.changes.yaml
|
789
|
+
----
|
790
|
+
|
791
|
+
[example]
|
792
|
+
.To override auto-detected repository roots
|
793
|
+
[source,sh]
|
794
|
+
----
|
795
|
+
$ suma compare schema_new.exp schema_old.exp \
|
796
|
+
--version 2 \
|
797
|
+
--trial-stepmod ~/repo-new \
|
798
|
+
--reference-stepmod ~/repo-old
|
799
|
+
----
|
800
|
+
|
801
|
+
==== Generated Change YAML format
|
802
|
+
|
803
|
+
The command generates EXPRESS Changes YAML files in the ISO 10303 format:
|
804
|
+
|
805
|
+
[source,yaml]
|
806
|
+
----
|
807
|
+
---
|
808
|
+
schema: support_resource_schema
|
809
|
+
editions:
|
810
|
+
- version: '2'
|
811
|
+
description: 'TYPE text: Underlying Type changed'
|
812
|
+
modifications:
|
813
|
+
- type: TYPE
|
814
|
+
name: text
|
815
|
+
----
|
816
|
+
|
817
|
+
For complete format documentation, see the
|
818
|
+
https://www.expresslang.org/docs/express-changes[EXPRESS Changes specification].
|
819
|
+
|
701
820
|
=== Expressir command
|
702
821
|
|
703
822
|
==== General
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require_relative "../eengine/wrapper"
|
5
|
+
require_relative "../eengine_converter"
|
6
|
+
|
7
|
+
module Suma
|
8
|
+
module Cli
|
9
|
+
# Command to compare EXPRESS schemas using eengine
|
10
|
+
class Compare < Thor
|
11
|
+
desc "compare TRIAL_SCHEMA REFERENCE_SCHEMA",
|
12
|
+
"Compare EXPRESS schemas using eengine and generate Change YAML"
|
13
|
+
long_desc <<~DESC
|
14
|
+
Compare two EXPRESS schemas from different git branches/checkouts.
|
15
|
+
|
16
|
+
Typical workflow:
|
17
|
+
1. Check out old version of repo at /path/to/repo-old
|
18
|
+
2. Check out new version of repo at /path/to/repo-new
|
19
|
+
3. Run comparison:
|
20
|
+
suma compare \\
|
21
|
+
/path/to/repo-new/schemas/.../schema.exp \\
|
22
|
+
/path/to/repo-old/schemas/.../schema.exp \\
|
23
|
+
--version 2
|
24
|
+
|
25
|
+
The command will:
|
26
|
+
- Auto-detect repository roots from schema paths
|
27
|
+
- Use those as stepmod paths for eengine
|
28
|
+
- Generate/update the .changes.yaml file in the new repo
|
29
|
+
DESC
|
30
|
+
|
31
|
+
option :output, type: :string, aliases: "-o",
|
32
|
+
desc: "Output Change YAML file path " \
|
33
|
+
"(default: {schema}.changes.yaml in trial schema directory)"
|
34
|
+
option :version, type: :string, aliases: "-v", required: true,
|
35
|
+
desc: "Version number for this change edition"
|
36
|
+
option :mode, type: :string, default: "resource",
|
37
|
+
enum: ["resource", "module"],
|
38
|
+
desc: "Schema comparison mode"
|
39
|
+
option :trial_stepmod, type: :string,
|
40
|
+
desc: "Override auto-detected trial repo root"
|
41
|
+
option :reference_stepmod, type: :string,
|
42
|
+
desc: "Override auto-detected reference repo root"
|
43
|
+
option :verbose, type: :boolean, default: false,
|
44
|
+
desc: "Enable verbose output"
|
45
|
+
|
46
|
+
def compare(trial_schema, reference_schema)
|
47
|
+
# Validate schema files exist
|
48
|
+
unless File.exist?(trial_schema)
|
49
|
+
say "Error: Trial schema not found: #{trial_schema}", :red
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
unless File.exist?(reference_schema)
|
54
|
+
say "Error: Reference schema not found: #{reference_schema}", :red
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
# Check eengine availability
|
59
|
+
unless Eengine::Wrapper.available?
|
60
|
+
say "Error: eengine not found in PATH", :red
|
61
|
+
say "Install eengine following instructions at:"
|
62
|
+
say " macOS: https://github.com/expresslang/homebrew-eengine"
|
63
|
+
say " Linux: https://github.com/expresslang/eengine-releases"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
# Auto-detect repo roots
|
68
|
+
trial_stepmod = options[:trial_stepmod] ||
|
69
|
+
detect_repo_root(trial_schema)
|
70
|
+
reference_stepmod = options[:reference_stepmod] ||
|
71
|
+
detect_repo_root(reference_schema)
|
72
|
+
|
73
|
+
if options[:verbose]
|
74
|
+
say "Using eengine version: #{Eengine::Wrapper.version}", :green
|
75
|
+
say "Trial repo root: #{trial_stepmod}", :cyan
|
76
|
+
say "Reference repo root: #{reference_stepmod}", :cyan
|
77
|
+
end
|
78
|
+
|
79
|
+
# Create a temporary directory for eengine output
|
80
|
+
require "tmpdir"
|
81
|
+
out_dir = nil
|
82
|
+
out_dir = Dir.mktmpdir("eengine-compare-")
|
83
|
+
|
84
|
+
# Run comparison
|
85
|
+
result = Eengine::Wrapper.compare(
|
86
|
+
trial_schema,
|
87
|
+
reference_schema,
|
88
|
+
mode: options[:mode],
|
89
|
+
trial_stepmod: trial_stepmod,
|
90
|
+
reference_stepmod: reference_stepmod,
|
91
|
+
out_dir: out_dir,
|
92
|
+
)
|
93
|
+
|
94
|
+
unless result[:has_changes]
|
95
|
+
say "No changes detected between schemas", :yellow
|
96
|
+
# Clean up temp directory
|
97
|
+
FileUtils.rm_rf(out_dir) if out_dir && File.directory?(out_dir)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
unless result[:xml_path]
|
102
|
+
say "Error: XML output not found", :red
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
if options[:verbose]
|
107
|
+
say "Comparison XML generated: #{result[:xml_path]}", :green
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convert to Change YAML
|
111
|
+
convert_to_change_yaml(result[:xml_path], trial_schema, out_dir)
|
112
|
+
rescue Eengine::EengineError => e
|
113
|
+
# Clean up temp directory
|
114
|
+
FileUtils.rm_rf(out_dir) if out_dir && File.directory?(out_dir)
|
115
|
+
say "Error: #{e.message}", :red
|
116
|
+
say e.stderr if e.respond_to?(:stderr) && options[:verbose]
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def detect_repo_root(schema_path)
|
123
|
+
# Walk up from schema path to find .git directory
|
124
|
+
current = File.expand_path(File.dirname(schema_path))
|
125
|
+
|
126
|
+
loop do
|
127
|
+
if File.directory?(File.join(current, ".git"))
|
128
|
+
return current
|
129
|
+
end
|
130
|
+
|
131
|
+
parent = File.dirname(current)
|
132
|
+
break if parent == current # reached root
|
133
|
+
|
134
|
+
current = parent
|
135
|
+
end
|
136
|
+
|
137
|
+
# If no .git found, use the directory containing the schema
|
138
|
+
# (for non-git workflows)
|
139
|
+
File.dirname(schema_path)
|
140
|
+
end
|
141
|
+
|
142
|
+
def convert_to_change_yaml(xml_path, trial_schema, out_dir)
|
143
|
+
schema_name = extract_schema_name(trial_schema)
|
144
|
+
output_path = determine_output_path(trial_schema)
|
145
|
+
|
146
|
+
# Load existing ChangeSchema if it exists
|
147
|
+
existing_schema = if File.exist?(output_path)
|
148
|
+
if options[:verbose]
|
149
|
+
say "Loading existing change schema: " \
|
150
|
+
"#{output_path}", :cyan
|
151
|
+
end
|
152
|
+
require "expressir/changes"
|
153
|
+
Expressir::Changes::SchemaChange.from_file(output_path)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Convert using Suma's converter
|
157
|
+
converter = EengineConverter.new(xml_path, schema_name)
|
158
|
+
change_schema = converter.convert(
|
159
|
+
version: options[:version],
|
160
|
+
existing_change_schema: existing_schema,
|
161
|
+
)
|
162
|
+
|
163
|
+
# Save using Expressir model
|
164
|
+
change_schema.to_file(output_path)
|
165
|
+
|
166
|
+
# Determine what action was taken
|
167
|
+
if existing_schema
|
168
|
+
existing_edition = existing_schema.editions.find do |ed|
|
169
|
+
ed.version == options[:version]
|
170
|
+
end
|
171
|
+
|
172
|
+
say "Change YAML file updated: #{output_path}", :green
|
173
|
+
if existing_edition
|
174
|
+
say " Replaced existing version #{options[:version]}", :green
|
175
|
+
else
|
176
|
+
say " Added version #{options[:version]} to change editions",
|
177
|
+
:green
|
178
|
+
end
|
179
|
+
else
|
180
|
+
say "Change YAML file created: #{output_path}", :green
|
181
|
+
end
|
182
|
+
|
183
|
+
if options[:verbose]
|
184
|
+
say "\nGenerated change schema content:", :cyan
|
185
|
+
say File.read(output_path)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Clean up temp directory and XML file
|
189
|
+
FileUtils.rm_rf(out_dir) if out_dir && File.directory?(out_dir)
|
190
|
+
end
|
191
|
+
|
192
|
+
def extract_schema_name(path)
|
193
|
+
# Remove version suffix if present (e.g., schema_1.exp -> schema)
|
194
|
+
basename = File.basename(path, ".exp")
|
195
|
+
basename.sub(/_\d+$/, "")
|
196
|
+
end
|
197
|
+
|
198
|
+
def determine_output_path(trial_schema)
|
199
|
+
if options[:output]
|
200
|
+
options[:output]
|
201
|
+
else
|
202
|
+
# Place .changes.yaml next to the trial schema in the NEW repo
|
203
|
+
base = extract_schema_name(trial_schema)
|
204
|
+
dir = File.dirname(trial_schema)
|
205
|
+
File.join(dir, "#{base}.changes.yaml")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/lib/suma/cli/export.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "thor"
|
4
4
|
require_relative "../thor_ext"
|
5
|
+
require_relative "../export_standalone_schema"
|
5
6
|
|
6
7
|
module Suma
|
7
8
|
module Cli
|
@@ -86,22 +87,12 @@ module Suma
|
|
86
87
|
def create_schema_from_exp_file(exp_file)
|
87
88
|
# Create a schema object from a standalone EXPRESS file
|
88
89
|
# The id will be determined during parsing
|
89
|
-
|
90
|
+
ExportStandaloneSchema.new(
|
90
91
|
id: nil,
|
91
92
|
path: File.expand_path(exp_file),
|
92
93
|
)
|
93
94
|
end
|
94
95
|
|
95
|
-
# Simple schema class for standalone EXPRESS files
|
96
|
-
class StandaloneSchema
|
97
|
-
attr_accessor :id, :path
|
98
|
-
|
99
|
-
def initialize(id:, path:)
|
100
|
-
@id = id
|
101
|
-
@path = path
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
96
|
def self.exit_on_failure?
|
106
97
|
true
|
107
98
|
end
|
data/lib/suma/cli.rb
CHANGED
@@ -74,6 +74,25 @@ module Suma
|
|
74
74
|
Cli::Export.start
|
75
75
|
end
|
76
76
|
|
77
|
+
desc "compare TRIAL_SCHEMA REFERENCE_SCHEMA",
|
78
|
+
"Compare EXPRESS schemas using eengine and generate Change YAML"
|
79
|
+
option :output, type: :string, aliases: "-o",
|
80
|
+
desc: "Output Change YAML file path"
|
81
|
+
option :version, type: :string, aliases: "-v", required: true,
|
82
|
+
desc: "Version number for this change edition"
|
83
|
+
option :mode, type: :string, default: "resource",
|
84
|
+
desc: "Schema comparison mode (resource/module)"
|
85
|
+
option :trial_stepmod, type: :string,
|
86
|
+
desc: "Override auto-detected trial repo root"
|
87
|
+
option :reference_stepmod, type: :string,
|
88
|
+
desc: "Override auto-detected reference repo root"
|
89
|
+
option :verbose, type: :boolean, default: false,
|
90
|
+
desc: "Enable verbose output"
|
91
|
+
def compare(_trial_schema, _reference_schema)
|
92
|
+
require_relative "cli/compare"
|
93
|
+
Cli::Compare.start
|
94
|
+
end
|
95
|
+
|
77
96
|
desc "validate SUBCOMMAND ...ARGS", "Validate express documents"
|
78
97
|
subcommand "validate", Cli::Validate
|
79
98
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Suma
|
4
|
+
module Eengine
|
5
|
+
# Base error class for eengine-related errors
|
6
|
+
class EengineError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when eengine binary is not found in PATH
|
9
|
+
class EengineNotFoundError < EengineError
|
10
|
+
def initialize
|
11
|
+
super("eengine not found in PATH. Install eengine:\n " \
|
12
|
+
"macOS: https://github.com/expresslang/homebrew-eengine\n " \
|
13
|
+
"Linux: https://github.com/expresslang/eengine-releases")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when eengine comparison fails
|
18
|
+
class ComparisonError < EengineError
|
19
|
+
attr_reader :stderr
|
20
|
+
|
21
|
+
def initialize(message, stderr = nil)
|
22
|
+
super(message)
|
23
|
+
@stderr = stderr
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require_relative "errors"
|
5
|
+
|
6
|
+
module Suma
|
7
|
+
module Eengine
|
8
|
+
# Wrapper for eengine binary to compare EXPRESS schemas
|
9
|
+
class Wrapper
|
10
|
+
class << self
|
11
|
+
# Compare two EXPRESS schemas using eengine
|
12
|
+
#
|
13
|
+
# @param trial_schema [String] Path to the new/trial schema
|
14
|
+
# @param reference_schema [String] Path to the old/reference schema
|
15
|
+
# @param options [Hash] Comparison options
|
16
|
+
# @option options [String] :mode Comparison mode (resource/module)
|
17
|
+
# @option options [String] :trial_stepmod Path to trial repo root
|
18
|
+
# @option options [String] :reference_stepmod Path to reference repo root
|
19
|
+
# @return [Hash] Result with :success, :xml_path, :has_changes, :output
|
20
|
+
def compare(trial_schema, reference_schema, options = {})
|
21
|
+
ensure_eengine_available!
|
22
|
+
|
23
|
+
cmd = build_command(trial_schema, reference_schema, options)
|
24
|
+
output, error, status = Open3.capture3(*cmd)
|
25
|
+
|
26
|
+
unless status.success?
|
27
|
+
error_message = error.empty? ? "Unknown eengine error" : error.strip
|
28
|
+
raise ComparisonError.new(error_message, error)
|
29
|
+
end
|
30
|
+
|
31
|
+
parse_output(output, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check if eengine is available on the system
|
35
|
+
#
|
36
|
+
# @return [Boolean] true if eengine binary is found
|
37
|
+
def available?
|
38
|
+
return false if ENV["EENGINE_DISABLED"] == "true"
|
39
|
+
|
40
|
+
eengine_path && eengine_executable?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the eengine version
|
44
|
+
#
|
45
|
+
# @return [String, nil] Version string or nil if unavailable
|
46
|
+
def version
|
47
|
+
return nil unless available?
|
48
|
+
|
49
|
+
cmd = [eengine_path, "--version"]
|
50
|
+
output, _, status = Open3.capture3(*cmd)
|
51
|
+
status.success? ? parse_version(output) : nil
|
52
|
+
rescue StandardError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def eengine_path
|
59
|
+
@eengine_path ||= find_eengine_binary
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_eengine_binary
|
63
|
+
# Search for eengine or eengine-* in PATH
|
64
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |dir|
|
65
|
+
# First try plain eengine
|
66
|
+
plain_path = File.join(dir, "eengine")
|
67
|
+
return plain_path if File.exist?(plain_path) && File.executable?(plain_path)
|
68
|
+
|
69
|
+
# Then try eengine-* pattern
|
70
|
+
Dir.glob(File.join(dir, "eengine-*")).each do |path|
|
71
|
+
return path if File.executable?(path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def eengine_executable?
|
78
|
+
eengine_path && File.executable?(eengine_path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def ensure_eengine_available!
|
82
|
+
raise EengineNotFoundError unless available?
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_command(trial, reference, options)
|
86
|
+
cmd = [
|
87
|
+
eengine_path,
|
88
|
+
"--compare",
|
89
|
+
"-trial_schema", trial,
|
90
|
+
"-trial_stepmod", options[:trial_stepmod] || ".",
|
91
|
+
"-reference_schema", reference,
|
92
|
+
"-reference_stepmod", options[:reference_stepmod] || ".",
|
93
|
+
"-mode", options[:mode] || "resource",
|
94
|
+
"--xml-output"
|
95
|
+
]
|
96
|
+
|
97
|
+
# Add output directory if specified
|
98
|
+
if options[:out_dir]
|
99
|
+
cmd += ["-out-dir", options[:out_dir]]
|
100
|
+
end
|
101
|
+
|
102
|
+
cmd
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_output(output, _options)
|
106
|
+
# Extract XML file path from output
|
107
|
+
# eengine prints: "Writing \"path/to/file.xml\""
|
108
|
+
xml_match = output.match(/Writing "(.+\.xml)"/)
|
109
|
+
xml_path = xml_match ? xml_match[1] : nil
|
110
|
+
|
111
|
+
# Expand to absolute path if found
|
112
|
+
xml_path = File.expand_path(xml_path) if xml_path
|
113
|
+
|
114
|
+
# Determine if changes were detected
|
115
|
+
has_changes = detect_changes(output)
|
116
|
+
|
117
|
+
{
|
118
|
+
success: true,
|
119
|
+
xml_path: xml_path,
|
120
|
+
has_changes: has_changes,
|
121
|
+
output: output,
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def detect_changes(output)
|
126
|
+
# Check for various indicators of changes in the output
|
127
|
+
return true if output.include?("Comparing TYPE")
|
128
|
+
return true if output.include?("Comparing ENTITY")
|
129
|
+
return true if output.include?("Comparing FUNCTION")
|
130
|
+
return true if output.include?("Comparing RULE")
|
131
|
+
return true if output.include?("Comparing PROCEDURE")
|
132
|
+
|
133
|
+
# Check for modification indicators
|
134
|
+
return true if output.include?("changed")
|
135
|
+
return true if output.include?("modified")
|
136
|
+
return true if output.include?("added")
|
137
|
+
return true if output.include?("removed")
|
138
|
+
|
139
|
+
false
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse_version(output)
|
143
|
+
# Extract version from output like "Express Engine 5.2.7"
|
144
|
+
version_match = output.match(/Express Engine ([\d.]+)/)
|
145
|
+
version_match ? version_match[1] : nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "expressir/commands/changes_import_eengine"
|
4
|
+
|
5
|
+
module Suma
|
6
|
+
# Converts eengine comparison XML to Expressir::Changes::SchemaChange
|
7
|
+
# This is a thin wrapper around Expressir's ChangesImportEengine command
|
8
|
+
class EengineConverter
|
9
|
+
def initialize(xml_path, schema_name)
|
10
|
+
@xml_path = xml_path
|
11
|
+
@schema_name = schema_name
|
12
|
+
@xml_content = File.read(xml_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Convert the eengine XML to a ChangeSchema
|
16
|
+
#
|
17
|
+
# @param version [String] Version number for this change edition
|
18
|
+
# @param existing_change_schema [Expressir::Changes::SchemaChange, nil]
|
19
|
+
# Existing schema to append to, or nil to create new
|
20
|
+
# @return [Expressir::Changes::SchemaChange] The updated change schema
|
21
|
+
def convert(version:, existing_change_schema: nil)
|
22
|
+
# Use Expressir's built-in conversion which properly handles
|
23
|
+
# HTML elements in descriptions
|
24
|
+
Expressir::Commands::ChangesImportEengine.from_xml(
|
25
|
+
@xml_content,
|
26
|
+
@schema_name,
|
27
|
+
version,
|
28
|
+
existing_schema: existing_change_schema,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Suma
|
4
|
+
# Simple schema class for standalone EXPRESS files
|
5
|
+
# Used when exporting individual .exp files that are not part of a manifest
|
6
|
+
class ExportStandaloneSchema
|
7
|
+
attr_accessor :id, :path
|
8
|
+
|
9
|
+
def initialize(id:, path:)
|
10
|
+
@id = id
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/suma/schema_exporter.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "express_schema"
|
4
4
|
require_relative "utils"
|
5
|
+
require_relative "export_standalone_schema"
|
5
6
|
require "fileutils"
|
6
7
|
|
7
8
|
module Suma
|
@@ -44,7 +45,7 @@ module Suma
|
|
44
45
|
def export_single_schema(schema)
|
45
46
|
# Check if this is a standalone EXPRESS file
|
46
47
|
# (not from a manifest structure)
|
47
|
-
is_standalone_file = schema.is_a?(
|
48
|
+
is_standalone_file = schema.is_a?(ExportStandaloneSchema)
|
48
49
|
schema_output_path = determine_output_path(schema, is_standalone_file)
|
49
50
|
|
50
51
|
express_schema = ExpressSchema.new(
|
data/lib/suma/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: suma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.25
|
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-
|
11
|
+
date: 2025-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: expressir
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/suma.rb
|
163
163
|
- lib/suma/cli.rb
|
164
164
|
- lib/suma/cli/build.rb
|
165
|
+
- lib/suma/cli/compare.rb
|
165
166
|
- lib/suma/cli/convert_jsdai.rb
|
166
167
|
- lib/suma/cli/export.rb
|
167
168
|
- lib/suma/cli/extract_terms.rb
|
@@ -172,6 +173,10 @@ files:
|
|
172
173
|
- lib/suma/cli/validate_links.rb
|
173
174
|
- lib/suma/collection_config.rb
|
174
175
|
- lib/suma/collection_manifest.rb
|
176
|
+
- lib/suma/eengine/errors.rb
|
177
|
+
- lib/suma/eengine/wrapper.rb
|
178
|
+
- lib/suma/eengine_converter.rb
|
179
|
+
- lib/suma/export_standalone_schema.rb
|
175
180
|
- lib/suma/express_schema.rb
|
176
181
|
- lib/suma/jsdai.rb
|
177
182
|
- lib/suma/jsdai/figure.rb
|