suma 0.2.5 → 0.3.0
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/.github/workflows/rake.yml +3 -0
- data/.github/workflows/release.yml +5 -1
- data/.gitignore +10 -1
- data/.rubocop_todo.yml +237 -28
- data/CLAUDE.md +102 -0
- data/Gemfile +3 -1
- data/README.adoc +188 -1
- data/exe/suma +1 -1
- data/lib/suma/cli/build.rb +2 -8
- data/lib/suma/cli/check_svg_quality.rb +172 -0
- data/lib/suma/cli/compare.rb +6 -158
- data/lib/suma/cli/convert_jsdai.rb +0 -2
- data/lib/suma/cli/core.rb +119 -0
- data/lib/suma/cli/export.rb +1 -10
- data/lib/suma/cli/extract_terms.rb +10 -654
- data/lib/suma/cli/generate_register.rb +34 -0
- data/lib/suma/cli/generate_schemas.rb +8 -124
- data/lib/suma/cli/reformat.rb +0 -1
- data/lib/suma/cli/validate.rb +0 -2
- data/lib/suma/cli/validate_links.rb +14 -291
- data/lib/suma/cli.rb +12 -102
- data/lib/suma/collection_config.rb +0 -2
- data/lib/suma/collection_manifest.rb +7 -111
- data/lib/suma/eengine/wrapper.rb +0 -1
- data/lib/suma/eengine.rb +8 -0
- data/lib/suma/express_schema.rb +43 -31
- data/lib/suma/jsdai/figure.rb +0 -3
- data/lib/suma/jsdai/figure_xml.rb +12 -9
- data/lib/suma/jsdai.rb +5 -8
- data/lib/suma/link_validator.rb +211 -0
- data/lib/suma/manifest_traverser.rb +92 -0
- data/lib/suma/processor.rb +76 -105
- data/lib/suma/register_manifest_generator.rb +163 -0
- data/lib/suma/schema_category.rb +83 -0
- data/lib/suma/schema_collection.rb +28 -63
- data/lib/suma/schema_comparer.rb +117 -0
- data/lib/suma/schema_compiler.rb +86 -0
- data/lib/suma/schema_discovery.rb +75 -0
- data/lib/suma/schema_exporter.rb +7 -35
- data/lib/suma/schema_index.rb +53 -0
- data/lib/suma/schema_manifest_generator.rb +113 -0
- data/lib/suma/schema_naming.rb +111 -0
- data/lib/suma/schema_template/document.rb +141 -0
- data/lib/suma/schema_template/plain.rb +46 -0
- data/lib/suma/schema_template.rb +19 -0
- data/lib/suma/svg_quality/batch_report.rb +78 -0
- data/lib/suma/svg_quality/formatters/json_formatter.rb +30 -0
- data/lib/suma/svg_quality/formatters/terminal_formatter.rb +168 -0
- data/lib/suma/svg_quality/formatters/yaml_formatter.rb +32 -0
- data/lib/suma/svg_quality/formatters.rb +12 -0
- data/lib/suma/svg_quality/report.rb +52 -0
- data/lib/suma/svg_quality.rb +30 -0
- data/lib/suma/term_extractor.rb +466 -0
- data/lib/suma/urn.rb +61 -0
- data/lib/suma/utils.rb +10 -2
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +34 -5
- data/suma.gemspec +3 -2
- metadata +53 -9
- data/lib/suma/export_standalone_schema.rb +0 -14
- data/lib/suma/schema_attachment.rb +0 -130
- data/lib/suma/schema_document.rb +0 -132
data/README.adoc
CHANGED
|
@@ -39,7 +39,8 @@ $ suma
|
|
|
39
39
|
Commands:
|
|
40
40
|
suma build METANORMA_SITE_MANIFEST # Build collection specified in site manifest (`metanorma*.yml`)
|
|
41
41
|
suma convert-jsdai XML_FILE IMAGE_FILE OUTPUT_DIR # Convert JSDAI XML and image files to SVG and EXP files
|
|
42
|
-
suma extract-terms SCHEMA_MANIFEST_FILE GLOSSARIST_OUTPUT_PATH # Extract
|
|
42
|
+
suma extract-terms SCHEMA_MANIFEST_FILE GLOSSARIST_OUTPUT_PATH # Extract EXPRESS entity concepts into Glossarist v3 format
|
|
43
|
+
suma generate-register SCHEMA_MANIFEST_FILE OUTPUT_PATH # Generate hierarchical register.yaml from schema manifest
|
|
43
44
|
suma generate-schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE # Generate EXPRESS schema manifest file from Metanorma site manifest
|
|
44
45
|
suma help [COMMAND] # Describe available commands or one specific command
|
|
45
46
|
suma reformat EXPRESS_FILE_PATH # Reformat EXPRESS files
|
|
@@ -301,6 +302,61 @@ $ bundle exec suma extract-terms schemas-activity-modules.yml terms_output
|
|
|
301
302
|
====
|
|
302
303
|
|
|
303
304
|
|
|
305
|
+
=== Generate register command
|
|
306
|
+
|
|
307
|
+
The `suma generate-register` command reads an EXPRESS schema manifest and
|
|
308
|
+
emits a Glossarist v3 `register.yaml` with hierarchical sections. The
|
|
309
|
+
output is used by the concept browser to render a navigable sidebar of
|
|
310
|
+
sections and to resolve concept cross-references.
|
|
311
|
+
|
|
312
|
+
[source,sh]
|
|
313
|
+
----
|
|
314
|
+
$ suma generate-register SCHEMA_MANIFEST_FILE OUTPUT_PATH -u URN --id ID --ref REF
|
|
315
|
+
----
|
|
316
|
+
|
|
317
|
+
Parameters:
|
|
318
|
+
|
|
319
|
+
`SCHEMA_MANIFEST_FILE`:: Path to the schema manifest (e.g., "schemas-smrl-part-2.yml")
|
|
320
|
+
|
|
321
|
+
`OUTPUT_PATH`:: Directory where `register.yaml` will be written
|
|
322
|
+
|
|
323
|
+
Options:
|
|
324
|
+
|
|
325
|
+
`--urn`, `-u`:: Base URN prefix for the dataset (e.g. `urn:iso:std:iso:10303:-2:ed-2:en:tech:*`).
|
|
326
|
+
The wildcard is stripped for the base URN and kept in `urnAliases`.
|
|
327
|
+
*Required*.
|
|
328
|
+
|
|
329
|
+
The URN is fully configurable — every component can be changed to match the
|
|
330
|
+
dataset being exported. The general structure is:
|
|
331
|
+
|
|
332
|
+
----
|
|
333
|
+
urn:iso:std:iso:<part>:ed-<edition>:<lang>:tech:*
|
|
334
|
+
----
|
|
335
|
+
|
|
336
|
+
For example, to export edition 3 in French:
|
|
337
|
+
|
|
338
|
+
----
|
|
339
|
+
--urn urn:iso:std:iso:10303:-2:ed-3:fr:tech:*
|
|
340
|
+
----
|
|
341
|
+
|
|
342
|
+
The current ISO 10303-2 SMRL data is edition 2; use `ed-2` unless you are
|
|
343
|
+
re-publishing an earlier edition.
|
|
344
|
+
|
|
345
|
+
`--id`:: Dataset identifier (e.g. `iso10303-2-express`). *Required*.
|
|
346
|
+
|
|
347
|
+
`--ref`:: Human-readable dataset reference label (e.g. `ISO 10303-2 EXPRESS Concepts`). *Required*.
|
|
348
|
+
|
|
349
|
+
`--language_code`, `-l`:: Language code for section names (default: "eng")
|
|
350
|
+
|
|
351
|
+
The generated register contains two top-level groups:
|
|
352
|
+
|
|
353
|
+
* **Resources** — integrated resource schemas (path contains `schemas/resources/`)
|
|
354
|
+
* **Application Modules** — ARM and MIM schemas (path contains `schemas/modules/`)
|
|
355
|
+
|
|
356
|
+
Each schema gets a human-readable name via the `SchemaNaming` module
|
|
357
|
+
(e.g. `topology_schema` → "Resource: Topology", `Activity_method_assignment_mim` → "Module: Activity Method Assignment (MIM)"). Acronyms (AIC, AEC, BREP, 2D, 3D) are preserved; function words (as, of, the) are lowercased per ISO title-case convention.
|
|
358
|
+
|
|
359
|
+
|
|
304
360
|
=== Convert JSDAI image outputs to SVG and Annotated EXPRESS
|
|
305
361
|
|
|
306
362
|
The `suma convert-jsdai` command converts JSDAI EXPRESS-G diagram outputs
|
|
@@ -864,6 +920,137 @@ For complete documentation of all Expressir commands and options, see the
|
|
|
864
920
|
https://github.com/lutaml/expressir[Expressir documentation].
|
|
865
921
|
|
|
866
922
|
|
|
923
|
+
=== Check SVG quality command
|
|
924
|
+
|
|
925
|
+
==== General
|
|
926
|
+
|
|
927
|
+
The `check_svg_quality` command validates SVG files for conformance and quality
|
|
928
|
+
using the https://github.com/claricle/svg_conform[svg_conform] library. It supports
|
|
929
|
+
both batch directory scanning and single file analysis.
|
|
930
|
+
|
|
931
|
+
By default, it uses the `metanorma` profile which allows raster images
|
|
932
|
+
(PNG, JPEG) and other features specific to Metanorma documentation.
|
|
933
|
+
|
|
934
|
+
[source,sh]
|
|
935
|
+
----
|
|
936
|
+
$ suma check_svg_quality [PATH] [options]
|
|
937
|
+
----
|
|
938
|
+
|
|
939
|
+
Parameters:
|
|
940
|
+
|
|
941
|
+
`PATH`:: Path to directory containing SVG files or a single SVG file.
|
|
942
|
+
Defaults to `schemas` directory.
|
|
943
|
+
|
|
944
|
+
==== Batch directory scanning
|
|
945
|
+
|
|
946
|
+
When given a directory, it scans all SVG files and produces a quality report
|
|
947
|
+
sorted by error count (most problematic files first).
|
|
948
|
+
|
|
949
|
+
[example]
|
|
950
|
+
.Check all SVG files in schemas directory
|
|
951
|
+
[source,sh]
|
|
952
|
+
----
|
|
953
|
+
$ suma check_svg_quality ../iso-10303/schemas
|
|
954
|
+
----
|
|
955
|
+
|
|
956
|
+
.Output
|
|
957
|
+
----
|
|
958
|
+
🔍 Scanning 3878 SVG files...
|
|
959
|
+
|
|
960
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
961
|
+
║ 🔍 SVG Quality Report Sorted by error count (most first) ║
|
|
962
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
963
|
+
|
|
964
|
+
📊 OVERVIEW
|
|
965
|
+
|
|
966
|
+
● Total Files : 3878
|
|
967
|
+
● Valid : 3845 ✅
|
|
968
|
+
● Invalid : 33 ❌
|
|
969
|
+
...
|
|
970
|
+
|
|
971
|
+
💥 CRITICAL QUALITY (2 files)
|
|
972
|
+
|
|
973
|
+
✗ 0/100 813 errors schemas/resources/action_schema/action_schemaexpg2.svg
|
|
974
|
+
✗ 25/100 156 errors schemas/modules/activity/mimexpg1.svg
|
|
975
|
+
----
|
|
976
|
+
|
|
977
|
+
==== Single file analysis
|
|
978
|
+
|
|
979
|
+
When given a path to a single SVG file, it shows detailed error breakdown
|
|
980
|
+
grouped by requirement type.
|
|
981
|
+
|
|
982
|
+
[example]
|
|
983
|
+
.Analyze a single SVG file
|
|
984
|
+
[source,sh]
|
|
985
|
+
----
|
|
986
|
+
$ suma check_svg_quality schemas/resources/action_schema/action_schemaexpg2.svg
|
|
987
|
+
----
|
|
988
|
+
|
|
989
|
+
.Output
|
|
990
|
+
----
|
|
991
|
+
📄 SVG Quality Report: schemas/resources/action_schema/action_schemaexpg2.svg
|
|
992
|
+
|
|
993
|
+
Valid: NO ❌
|
|
994
|
+
Errors: 813
|
|
995
|
+
|
|
996
|
+
📋 Error Details
|
|
997
|
+
|
|
998
|
+
style (56 occurrences)
|
|
999
|
+
- Style property 'enable-background' removed
|
|
1000
|
+
- Style property 'overflow' removed
|
|
1001
|
+
...
|
|
1002
|
+
|
|
1003
|
+
allowed_elements (109 occurrences)
|
|
1004
|
+
- The element 'image' is not allowed as a child of 'svg'
|
|
1005
|
+
...
|
|
1006
|
+
|
|
1007
|
+
color_restrictions (108 occurrences)
|
|
1008
|
+
- Color 'rgb(33, 128, 255)' in style property 'fill' is not allowed
|
|
1009
|
+
...
|
|
1010
|
+
----
|
|
1011
|
+
|
|
1012
|
+
Options:
|
|
1013
|
+
|
|
1014
|
+
`--profile=PROFILE`:: Validation profile to use (default: `metanorma`).
|
|
1015
|
+
Other options: `svg_1_2_rfc`, `svg_1_2_rfc_with_rdf`, `base`, `lucid_fix`
|
|
1016
|
+
|
|
1017
|
+
`--sort=MODE`:: Sort order: `errors` (most errors first, default) or
|
|
1018
|
+
`quality` (lowest quality scores first)
|
|
1019
|
+
|
|
1020
|
+
`--format=FORMAT`:: Output format: `terminal` (default), `json`, or `yaml`
|
|
1021
|
+
|
|
1022
|
+
`--output=PATH`, `-o PATH`:: Write output to file instead of stdout
|
|
1023
|
+
|
|
1024
|
+
`--min_errors=N`:: Filter to show only files with at least N errors
|
|
1025
|
+
|
|
1026
|
+
`--limit=N`:: Limit output to top N files
|
|
1027
|
+
|
|
1028
|
+
`--progress`:: Show progress during batch scanning
|
|
1029
|
+
|
|
1030
|
+
`--summary-only`:: Show only summary, not individual file details
|
|
1031
|
+
|
|
1032
|
+
[example]
|
|
1033
|
+
.Check with custom profile
|
|
1034
|
+
[source,sh]
|
|
1035
|
+
----
|
|
1036
|
+
$ suma check_svg_quality schemas/ --profile svg_1_2_rfc
|
|
1037
|
+
----
|
|
1038
|
+
|
|
1039
|
+
[example]
|
|
1040
|
+
.Output as JSON
|
|
1041
|
+
[source,sh]
|
|
1042
|
+
----
|
|
1043
|
+
$ suma check_svg_quality schemas/ --format json --output quality_report.json
|
|
1044
|
+
----
|
|
1045
|
+
|
|
1046
|
+
[example]
|
|
1047
|
+
.Show only files with 100+ errors
|
|
1048
|
+
[source,sh]
|
|
1049
|
+
----
|
|
1050
|
+
$ suma check_svg_quality schemas/ --min_errors 100
|
|
1051
|
+
----
|
|
1052
|
+
|
|
1053
|
+
|
|
867
1054
|
== Usage: Ruby
|
|
868
1055
|
|
|
869
1056
|
=== General
|
data/exe/suma
CHANGED
data/lib/suma/cli/build.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require_relative "../thor_ext"
|
|
5
4
|
|
|
6
5
|
module Suma
|
|
7
6
|
module Cli
|
|
@@ -15,10 +14,6 @@ module Suma
|
|
|
15
14
|
desc: "Generate file that contains all schemas in the collection."
|
|
16
15
|
|
|
17
16
|
def build(metanorma_site_manifest)
|
|
18
|
-
# Lazy-load dependencies only when this command is actually used
|
|
19
|
-
require_relative "../processor"
|
|
20
|
-
require_relative "../utils"
|
|
21
|
-
|
|
22
17
|
unless File.exist?(metanorma_site_manifest)
|
|
23
18
|
raise Errno::ENOENT, "Specified Metanorma site manifest file " \
|
|
24
19
|
"`#{metanorma_site_manifest}` not found."
|
|
@@ -31,16 +26,15 @@ module Suma
|
|
|
31
26
|
private
|
|
32
27
|
|
|
33
28
|
def run(manifest, options)
|
|
34
|
-
# Set schemas_all_path to match metanorma_yaml_path
|
|
35
29
|
schemas_all_path = options[:schemas_all_path] ||
|
|
36
30
|
manifest.gsub("metanorma", "schemas")
|
|
37
31
|
|
|
38
|
-
Processor.
|
|
32
|
+
Processor.new(
|
|
39
33
|
metanorma_yaml_path: manifest,
|
|
40
34
|
schemas_all_path: schemas_all_path,
|
|
41
35
|
compile: options[:compile],
|
|
42
36
|
output_directory: "_site",
|
|
43
|
-
)
|
|
37
|
+
).run
|
|
44
38
|
end
|
|
45
39
|
|
|
46
40
|
def log_error(error)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Suma
|
|
6
|
+
module Cli
|
|
7
|
+
# Check SVG quality using svg_conform Validator API - thin CLI wrapper
|
|
8
|
+
class CheckSvgQuality
|
|
9
|
+
DATA_PATH = "schemas"
|
|
10
|
+
DEFAULT_PATTERN = "**/*.svg"
|
|
11
|
+
DEFAULT_PROFILE = :metanorma
|
|
12
|
+
|
|
13
|
+
def initialize(pattern: DEFAULT_PATTERN, profile: DEFAULT_PROFILE,
|
|
14
|
+
format: "terminal", output: nil, min_errors: nil,
|
|
15
|
+
summary_only: false, progress: false, limit: nil,
|
|
16
|
+
sort: "errors")
|
|
17
|
+
@options = {
|
|
18
|
+
pattern: pattern,
|
|
19
|
+
profile: profile,
|
|
20
|
+
format: format,
|
|
21
|
+
output: output,
|
|
22
|
+
min_errors: min_errors,
|
|
23
|
+
summary_only: summary_only,
|
|
24
|
+
progress: progress,
|
|
25
|
+
limit: limit,
|
|
26
|
+
sort: sort.to_sym,
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run(path = DATA_PATH)
|
|
31
|
+
require "svg_conform"
|
|
32
|
+
|
|
33
|
+
path_obj = Pathname.new(path).expand_path
|
|
34
|
+
|
|
35
|
+
# Enable progress by default when outputting to terminal
|
|
36
|
+
show_progress = options[:progress] || ($stdout.tty? && !options[:output])
|
|
37
|
+
if show_progress
|
|
38
|
+
$stdout.sync = true
|
|
39
|
+
$stderr.sync = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if path_obj.file?
|
|
43
|
+
# Single file mode - show detailed errors
|
|
44
|
+
analyze_single_file(path_obj)
|
|
45
|
+
else
|
|
46
|
+
# Directory mode - show batch report
|
|
47
|
+
svg_files = find_svg_files(path_obj)
|
|
48
|
+
|
|
49
|
+
if svg_files.empty?
|
|
50
|
+
puts "No SVG files found in #{path}"
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "🔍 Scanning #{svg_files.size} SVG files..."
|
|
55
|
+
puts
|
|
56
|
+
|
|
57
|
+
reports = analyze_files_one_by_one(svg_files, show_progress)
|
|
58
|
+
batch_report = SvgQuality::BatchReport.new(reports)
|
|
59
|
+
sorted_report = sort_report(batch_report)
|
|
60
|
+
output_report(sorted_report)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def analyze_single_file(path)
|
|
65
|
+
validator = SvgConform::Validator.new
|
|
66
|
+
result = validator.validate_file(path.to_s, profile: options[:profile])
|
|
67
|
+
|
|
68
|
+
puts "📄 SVG Quality Report: #{path}"
|
|
69
|
+
puts ""
|
|
70
|
+
puts " Valid: #{result.valid? ? 'YES ✅' : 'NO ❌'}"
|
|
71
|
+
puts " Errors: #{result.error_count}"
|
|
72
|
+
puts ""
|
|
73
|
+
|
|
74
|
+
if result.errors.any?
|
|
75
|
+
puts " 📋 Error Details"
|
|
76
|
+
puts ""
|
|
77
|
+
|
|
78
|
+
# Group errors by requirement_id
|
|
79
|
+
by_req = result.errors.group_by(&:requirement_id)
|
|
80
|
+
|
|
81
|
+
by_req.each do |req_id, errors|
|
|
82
|
+
puts " #{req_id} (#{errors.size} occurrences)"
|
|
83
|
+
errors.first(5).each do |e|
|
|
84
|
+
puts " - #{e.message}"
|
|
85
|
+
end
|
|
86
|
+
if errors.size > 5
|
|
87
|
+
puts " ... and #{errors.size - 5} more"
|
|
88
|
+
end
|
|
89
|
+
puts ""
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
attr_reader :options
|
|
97
|
+
|
|
98
|
+
def find_svg_files(path)
|
|
99
|
+
if path.directory?
|
|
100
|
+
Pathname.glob(path.join(options[:pattern])).select(&:file?)
|
|
101
|
+
elsif path.file? && path.extname == ".svg"
|
|
102
|
+
[path]
|
|
103
|
+
else
|
|
104
|
+
[]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def analyze_files_one_by_one(files, show_progress = false)
|
|
109
|
+
validator = SvgConform::Validator.new
|
|
110
|
+
reports = []
|
|
111
|
+
|
|
112
|
+
files.each_with_index do |file, index|
|
|
113
|
+
result = validator.validate_file(file.to_s,
|
|
114
|
+
profile: options[:profile])
|
|
115
|
+
report = SvgQuality::Report.new(file.to_s, result)
|
|
116
|
+
reports << report
|
|
117
|
+
|
|
118
|
+
if show_progress
|
|
119
|
+
tier = report.quality_tier
|
|
120
|
+
status = report.valid? ? "✅" : "❌"
|
|
121
|
+
msg = " [#{index + 1}/#{files.size}] #{tier[:emoji]} #{report.error_count} errors #{status} #{shorten_path(file)}\n"
|
|
122
|
+
$stderr.print msg
|
|
123
|
+
$stderr.flush
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
reports
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def sort_report(batch_report)
|
|
131
|
+
case options[:sort]
|
|
132
|
+
when :quality
|
|
133
|
+
batch_report.sort_by_quality
|
|
134
|
+
else
|
|
135
|
+
batch_report.sort_by_errors
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def output_report(batch_report)
|
|
140
|
+
filtered = batch_report.filter_by_min_errors(options[:min_errors])
|
|
141
|
+
limited = filtered.limit(options[:limit])
|
|
142
|
+
|
|
143
|
+
formatter = case options[:format].to_sym
|
|
144
|
+
when :json
|
|
145
|
+
SvgQuality::Formatters::JsonFormatter.new(limited,
|
|
146
|
+
output: options[:output])
|
|
147
|
+
when :yaml
|
|
148
|
+
SvgQuality::Formatters::YamlFormatter.new(limited,
|
|
149
|
+
output: options[:output])
|
|
150
|
+
else
|
|
151
|
+
SvgQuality::Formatters::TerminalFormatter.new(limited,
|
|
152
|
+
output: options[:output], sort: options[:sort])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
puts formatter.format
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def shorten_path(path)
|
|
159
|
+
p = Pathname.new(path)
|
|
160
|
+
if p.absolute?
|
|
161
|
+
begin
|
|
162
|
+
p.relative_path_from(Pathname.pwd)
|
|
163
|
+
rescue StandardError
|
|
164
|
+
p
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
p
|
|
168
|
+
end.to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/suma/cli/compare.rb
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require_relative "../eengine/wrapper"
|
|
5
|
-
require_relative "../eengine_converter"
|
|
6
4
|
|
|
7
5
|
module Suma
|
|
8
6
|
module Cli
|
|
9
|
-
# Command to compare EXPRESS schemas using eengine
|
|
10
7
|
class Compare < Thor
|
|
11
8
|
desc "compare TRIAL_SCHEMA REFERENCE_SCHEMA",
|
|
12
9
|
"Compare EXPRESS schemas using eengine and generate Change YAML"
|
|
@@ -44,166 +41,17 @@ module Suma
|
|
|
44
41
|
desc: "Enable verbose output"
|
|
45
42
|
|
|
46
43
|
def compare(trial_schema, reference_schema)
|
|
47
|
-
|
|
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
|
|
44
|
+
comparer = SchemaComparer.new(trial_schema, reference_schema, options)
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
trial_stepmod = options[:trial_stepmod] ||
|
|
69
|
-
detect_repo_root(trial_schema)
|
|
70
|
-
reference_stepmod = options[:reference_stepmod] ||
|
|
71
|
-
detect_repo_root(reference_schema)
|
|
46
|
+
result = comparer.compare
|
|
72
47
|
|
|
73
|
-
if
|
|
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]
|
|
48
|
+
if result.nil?
|
|
95
49
|
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_version = existing_schema.versions.find do |ed|
|
|
169
|
-
ed.version == options[:version]
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
say "Change YAML file updated: #{output_path}", :green
|
|
173
|
-
if existing_version
|
|
174
|
-
say " Replaced existing version #{options[:version]}", :green
|
|
175
|
-
else
|
|
176
|
-
say " Added version #{options[:version]} to change versions",
|
|
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
50
|
else
|
|
202
|
-
|
|
203
|
-
base = extract_schema_name(trial_schema)
|
|
204
|
-
dir = File.dirname(trial_schema)
|
|
205
|
-
File.join(dir, "#{base}.changes.yaml")
|
|
51
|
+
say "Change YAML file: #{result}", :green
|
|
206
52
|
end
|
|
53
|
+
rescue Suma::Error => e
|
|
54
|
+
raise Thor::Error, e.message
|
|
207
55
|
end
|
|
208
56
|
end
|
|
209
57
|
end
|