suma 0.2.4 → 0.2.6

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +3 -0
  3. data/.github/workflows/release.yml +5 -1
  4. data/.rubocop_todo.yml +78 -26
  5. data/CLAUDE.md +76 -0
  6. data/Gemfile +3 -1
  7. data/README.adoc +131 -0
  8. data/lib/suma/cli/build.rb +2 -3
  9. data/lib/suma/cli/check_svg_quality.rb +178 -0
  10. data/lib/suma/cli/compare.rb +7 -158
  11. data/lib/suma/cli/export.rb +1 -7
  12. data/lib/suma/cli/extract_terms.rb +7 -648
  13. data/lib/suma/cli/generate_schemas.rb +9 -123
  14. data/lib/suma/cli/validate_links.rb +15 -290
  15. data/lib/suma/cli.rb +39 -0
  16. data/lib/suma/collection_manifest.rb +3 -4
  17. data/lib/suma/express_schema.rb +43 -30
  18. data/lib/suma/jsdai/figure_xml.rb +12 -9
  19. data/lib/suma/jsdai.rb +0 -6
  20. data/lib/suma/link_validator.rb +203 -0
  21. data/lib/suma/processor.rb +75 -101
  22. data/lib/suma/schema_attachment.rb +2 -29
  23. data/lib/suma/schema_collection.rb +1 -32
  24. data/lib/suma/schema_comparer.rb +116 -0
  25. data/lib/suma/schema_document.rb +0 -14
  26. data/lib/suma/schema_exporter.rb +16 -28
  27. data/lib/suma/schema_index.rb +53 -0
  28. data/lib/suma/schema_manifest_generator.rb +105 -0
  29. data/lib/suma/svg_quality/batch_report.rb +80 -0
  30. data/lib/suma/svg_quality/formatters/json_formatter.rb +30 -0
  31. data/lib/suma/svg_quality/formatters/terminal_formatter.rb +168 -0
  32. data/lib/suma/svg_quality/formatters/yaml_formatter.rb +32 -0
  33. data/lib/suma/svg_quality/report.rb +52 -0
  34. data/lib/suma/svg_quality.rb +28 -0
  35. data/lib/suma/term_extractor.rb +393 -0
  36. data/lib/suma/utils.rb +10 -2
  37. data/lib/suma/version.rb +1 -1
  38. data/lib/suma.rb +3 -2
  39. data/suma.gemspec +3 -2
  40. metadata +33 -7
  41. data/lib/suma/export_standalone_schema.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e48ff476d65e79fed1e67ed14aced4a49332640c2cd5765545237eae6b59d57
4
- data.tar.gz: 89ce2271fd741533638b798cdeca99c2b4dbc30ba827c2f8c3bf342ab71b3aa5
3
+ metadata.gz: 6e6db67576d42aaaed54f90f72960ad405774d4084be5f7c0e8f1b277ebbbd50
4
+ data.tar.gz: ed5cdf3967496436950306f0edcf553751bb3da06c4e4582a014b8e7610e6d35
5
5
  SHA512:
6
- metadata.gz: 8f139509f972a2700345efe36f8d76709f1338e27447771ec3de966bd7a21c5c6a5c0c3e765162efeba53a102e29517b038d446c7b98c058fd9a7f793b7cdbef
7
- data.tar.gz: 65256ed59b74ff76fd7710ce308049c3972e0bdc4a4698de02e090a85a66ddfaf5e4c5e90864ecef9e134de922c0b96534d05488759cd4cce8ec08bfe54deb4e
6
+ metadata.gz: 0e4e8fe19fb5b9ec7c8023258e55a8ed100c98bb4ee271898562eeed13d5e1646d09c673e0053f2271827c8413408d5cb9295e94cc39d298be7161d33a0805c2
7
+ data.tar.gz: 8110684a885977266204dace36b4b5bb8a88113c1b410aa78c19d8ad4c58c2a49858f14f9495e433414d43b0bd13716a49e47eb8ef7724ee34306826a215ff15
@@ -2,6 +2,9 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: rake
4
4
 
5
+ permissions:
6
+ contents: write
7
+
5
8
  on:
6
9
  push:
7
10
  branches: [ master, main ]
@@ -2,6 +2,11 @@
2
2
  # See https://github.com/metanorma/cimas
3
3
  name: release
4
4
 
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+ id-token: write
9
+
5
10
  on:
6
11
  workflow_dispatch:
7
12
  inputs:
@@ -22,4 +27,3 @@ jobs:
22
27
  secrets:
23
28
  rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
24
29
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
25
-
data/.rubocop_todo.yml CHANGED
@@ -1,35 +1,34 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-12-18 05:29:37 UTC using RuboCop version 1.81.1.
3
+ # on 2026-05-11 08:57:26 UTC using RuboCop version 1.86.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
9
  # Offense count: 3
10
- # Configuration parameters: Severity.
11
10
  Gemspec/DuplicatedAssignment:
12
11
  Exclude:
13
12
  - 'suma.gemspec'
14
13
 
15
14
  # Offense count: 1
16
- # Configuration parameters: Severity.
17
15
  Gemspec/RequiredRubyVersion:
18
16
  Exclude:
19
17
  - 'suma.gemspec'
20
18
 
21
- # Offense count: 82
19
+ # Offense count: 120
22
20
  # This cop supports safe autocorrection (--autocorrect).
23
- # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
21
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
24
22
  # URISchemes: http, https
25
23
  Layout/LineLength:
26
24
  Enabled: false
27
25
 
28
- # Offense count: 2
26
+ # Offense count: 4
29
27
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
30
28
  Lint/DuplicateBranch:
31
29
  Exclude:
32
30
  - 'lib/suma/jsdai/figure.rb'
31
+ - 'lib/suma/term_extractor.rb'
33
32
 
34
33
  # Offense count: 1
35
34
  Lint/DuplicateMethods:
@@ -41,48 +40,93 @@ Lint/IneffectiveAccessModifier:
41
40
  Exclude:
42
41
  - 'lib/suma/cli/export.rb'
43
42
 
44
- # Offense count: 11
43
+ # Offense count: 1
44
+ # This cop supports safe autocorrection (--autocorrect).
45
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
46
+ # NotImplementedExceptions: NotImplementedError
47
+ Lint/UnusedMethodArgument:
48
+ Exclude:
49
+ - 'lib/suma/svg_quality.rb'
50
+
51
+ # Offense count: 29
45
52
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
46
53
  Metrics/AbcSize:
47
54
  Exclude:
48
- - 'lib/suma/cli/compare.rb'
55
+ - 'lib/suma/cli.rb'
56
+ - 'lib/suma/cli/check_svg_quality.rb'
49
57
  - 'lib/suma/jsdai/figure.rb'
50
58
  - 'lib/suma/jsdai/figure_image.rb'
59
+ - 'lib/suma/link_validator.rb'
60
+ - 'lib/suma/processor.rb'
51
61
  - 'lib/suma/schema_attachment.rb'
62
+ - 'lib/suma/schema_comparer.rb'
52
63
  - 'lib/suma/schema_document.rb'
64
+ - 'lib/suma/schema_manifest_generator.rb'
65
+ - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
66
+ - 'lib/suma/term_extractor.rb'
53
67
  - 'lib/suma/thor_ext.rb'
54
68
 
55
- # Offense count: 6
69
+ # Offense count: 1
70
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
71
+ # AllowedMethods: refine
72
+ Metrics/BlockLength:
73
+ Max: 54
74
+
75
+ # Offense count: 14
56
76
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
57
77
  Metrics/CyclomaticComplexity:
58
78
  Exclude:
59
- - 'lib/suma/cli/compare.rb'
79
+ - 'lib/suma/cli/check_svg_quality.rb'
60
80
  - 'lib/suma/eengine/wrapper.rb'
81
+ - 'lib/suma/express_schema.rb'
61
82
  - 'lib/suma/jsdai/figure.rb'
62
83
  - 'lib/suma/jsdai/figure_image.rb'
84
+ - 'lib/suma/link_validator.rb'
85
+ - 'lib/suma/schema_comparer.rb'
86
+ - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
87
+ - 'lib/suma/term_extractor.rb'
63
88
  - 'lib/suma/thor_ext.rb'
64
89
 
65
- # Offense count: 5
90
+ # Offense count: 25
66
91
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
67
92
  Metrics/MethodLength:
68
- Max: 60
93
+ Max: 59
69
94
 
70
- # Offense count: 4
95
+ # Offense count: 2
96
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
97
+ Metrics/ParameterLists:
98
+ Max: 9
99
+
100
+ # Offense count: 8
71
101
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
72
102
  Metrics/PerceivedComplexity:
73
103
  Exclude:
74
- - 'lib/suma/cli/compare.rb'
75
104
  - 'lib/suma/eengine/wrapper.rb'
76
105
  - 'lib/suma/jsdai/figure_image.rb'
106
+ - 'lib/suma/link_validator.rb'
107
+ - 'lib/suma/schema_comparer.rb'
108
+ - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
109
+ - 'lib/suma/term_extractor.rb'
77
110
 
78
111
  # Offense count: 1
79
112
  # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
80
113
  # AllowedMethods: call
81
- # WaywardPredicates: nonzero?
114
+ # WaywardPredicates: infinite?, nonzero?
82
115
  Naming/PredicateMethod:
83
116
  Exclude:
84
117
  - 'lib/suma/eengine/wrapper.rb'
85
118
 
119
+ # Offense count: 1
120
+ # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
121
+ # NamePrefix: is_, has_, have_, does_
122
+ # ForbiddenPrefixes: is_, has_, have_, does_
123
+ # AllowedMethods: is_a?
124
+ # MethodDefinitionMacros: define_method, define_singleton_method
125
+ Naming/PredicatePrefix:
126
+ Exclude:
127
+ - 'spec/**/*'
128
+ - 'lib/suma/term_extractor.rb'
129
+
86
130
  # Offense count: 2
87
131
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
88
132
  # SupportedStyles: snake_case, normalcase, non_integer
@@ -95,9 +139,9 @@ Naming/VariableNumber:
95
139
  # Configuration parameters: MinSize.
96
140
  Performance/CollectionLiteralInLoop:
97
141
  Exclude:
98
- - 'spec/suma/cli/compare_spec.rb'
99
142
  - 'spec/suma/cli/export_spec.rb'
100
143
  - 'spec/suma/cli_spec.rb'
144
+ - 'spec/suma/schema_comparer_integration_spec.rb'
101
145
 
102
146
  # Offense count: 2
103
147
  # Configuration parameters: Prefixes, AllowedPatterns.
@@ -106,40 +150,40 @@ RSpec/ContextWording:
106
150
  Exclude:
107
151
  - 'spec/suma/cli/export_spec.rb'
108
152
 
109
- # Offense count: 64
153
+ # Offense count: 71
110
154
  # Configuration parameters: CountAsOne.
111
155
  RSpec/ExampleLength:
112
- Max: 61
156
+ Max: 60
113
157
 
114
158
  # Offense count: 4
115
159
  # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
116
160
  RSpec/IndexedLet:
117
161
  Exclude:
118
- - 'spec/suma/cli/compare_spec.rb'
119
162
  - 'spec/suma/cli/export_spec.rb'
163
+ - 'spec/suma/schema_comparer_integration_spec.rb'
120
164
 
121
165
  # Offense count: 22
122
166
  # Configuration parameters: AssignmentOnly.
123
167
  RSpec/InstanceVariable:
124
168
  Exclude:
125
- - 'spec/suma/cli/compare_spec.rb'
169
+ - 'spec/suma/schema_comparer_integration_spec.rb'
126
170
 
127
171
  # Offense count: 1
128
172
  # This cop supports safe autocorrection (--autocorrect).
129
173
  RSpec/IteratedExpectation:
130
174
  Exclude:
131
- - 'spec/suma/cli/compare_spec.rb'
175
+ - 'spec/suma/schema_comparer_integration_spec.rb'
132
176
 
133
177
  # Offense count: 1
134
178
  # Configuration parameters: EnforcedStyle.
135
179
  # SupportedStyles: have_received, receive
136
180
  RSpec/MessageSpies:
137
181
  Exclude:
138
- - 'spec/suma/cli/compare_spec.rb'
182
+ - 'spec/suma/schema_comparer_integration_spec.rb'
139
183
 
140
- # Offense count: 47
184
+ # Offense count: 50
141
185
  RSpec/MultipleExpectations:
142
- Max: 23
186
+ Max: 21
143
187
 
144
188
  # Offense count: 3
145
189
  # Configuration parameters: AllowedGroups.
@@ -147,7 +191,8 @@ RSpec/NestedGroups:
147
191
  Max: 4
148
192
 
149
193
  # Offense count: 1
150
- # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata.
194
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
195
+ # SupportedInflectors: default, active_support
151
196
  RSpec/SpecFilePathFormat:
152
197
  Exclude:
153
198
  - '**/spec/routing/**/*'
@@ -156,4 +201,11 @@ RSpec/SpecFilePathFormat:
156
201
  # Offense count: 1
157
202
  RSpec/StubbedMock:
158
203
  Exclude:
159
- - 'spec/suma/cli/compare_spec.rb'
204
+ - 'spec/suma/schema_comparer_integration_spec.rb'
205
+
206
+ # Offense count: 1
207
+ # Configuration parameters: AllowedMethods.
208
+ # AllowedMethods: respond_to_missing?
209
+ Style/OptionalBooleanParameter:
210
+ Exclude:
211
+ - 'lib/suma/cli/check_svg_quality.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,76 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ Suma is a Ruby gem for processing EXPRESS schemas (ISO 10303 STEP standards). It reads Metanorma site manifests, discovers EXPRESS schemas, compiles them into documentation, and can export/compare schemas.
8
+
9
+ ## Commands
10
+
11
+ ### Development
12
+
13
+ ```bash
14
+ bundle install # Install dependencies
15
+ rake spec # Run RSpec tests
16
+ rake rubocop # Run RuboCop linter
17
+ rake # Run both tests and linter (default task)
18
+ rspec spec/path/to_spec.rb # Run a single spec file
19
+ rspec spec/path/to_spec.rb:42 # Run a single test at line 42
20
+ ```
21
+
22
+ ### CLI usage
23
+
24
+ ```bash
25
+ exe/suma build METANORMA_YAML # Build collection from site manifest
26
+ exe/suma export -o OUTPUT_DIR schema1.yml schema2.exp # Export schemas (YAML manifest or .exp files)
27
+ exe/suma compare TRIAL_SCHEMA REFERENCE_SCHEMA -v VER # Compare schemas, generate .changes.yaml
28
+ exe/suma reformat PATH # Reformat EXP files (use -r for recursive)
29
+ exe/suma generate-schemas METANORMA_YAML SCHEMAS_YAML # Generate schema manifest from site manifest
30
+ exe/suma extract-terms SCHEMA_YAML GLOSSARIST_DIR # Extract terms to Glossarist v2 format
31
+ exe/suma validate links SCHEMAS_FILE DOCS_PATH # Validate EXPRESS cross-reference links
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ ### Core pipeline (build command)
37
+
38
+ 1. `Processor.run` receives a Metanorma site manifest YAML
39
+ 2. `SiteConfig` reads the site manifest, finds collection YAML paths
40
+ 3. `CollectionConfig` extends `Metanorma::Collection::Config` with a `CollectionManifest` that discovers `schemas.yaml` files
41
+ 4. `SchemaCollection` loads all discovered schemas via `Expressir`, exports plain `.exp` files, and compiles documentation (Metanorma `.adoc` → XML/HTML)
42
+ 5. `SchemaExporter` handles exporting schemas to a directory (with optional ZIP packaging)
43
+
44
+ ### Key classes
45
+
46
+ - **`ExpressSchema`** — wraps a single EXPRESS schema file; parses via `Expressir::Express::Parser`, can output plain or annotated `.exp`
47
+ - **`SchemaAttachment`** — compiles one schema into a Metanorma `.adoc` document and renders it via `Metanorma::Compile`
48
+ - **`SchemaDocument`** (extends `SchemaAttachment`) — adds cross-reference bookmarks and uses XML-only output
49
+ - **`SchemaCollection`** — orchestrates processing of all schemas from a config
50
+ - **`SchemaExporter`** — standalone export of schemas from manifest or `.exp` files to a directory, with optional ZIP
51
+ - **`CollectionManifest`** — traverses collection YAML files, builds unified `Expressir::SchemaManifest`
52
+
53
+ ### CLI structure
54
+
55
+ - `Suma::Cli::Core` (Thor subclass) — top-level CLI entrypoint
56
+ - Subcommands delegate to `Cli::Build`, `Cli::Export`, `Cli::Compare`, `Cli::Validate`, `Cli::Reformat`, `Cli::GenerateSchemas`, `Cli::ExtractTerms`, `Cli::ConvertJsdai`
57
+ - Thor extension (`ThorExt::Start`) adds `-h`/`--help` support and error formatting
58
+
59
+ ### External dependencies
60
+
61
+ - **expressir** — EXPRESS schema parsing and manifest handling
62
+ - **metanorma** — document compilation (`.adoc` → XML/HTML/PDF)
63
+ - **lutaml-model** — YAML/XML model serialization (used in `SiteConfig`, `CollectionConfig`)
64
+ - **glossarist** — term extraction output format
65
+ - **eengine** (optional, external binary) — schema comparison via `Eengine::Wrapper`
66
+
67
+ ### Schema comparison flow
68
+
69
+ The `compare` command uses an external `eengine` binary to diff two EXPRESS schemas, producing XML. `EengineConverter` then converts that XML into a `.changes.yaml` file managed by `Expressir::Changes::SchemaChange`.
70
+
71
+ ## Code style
72
+
73
+ - Ruby 3.0+ with `frozen_string_literal: true` in every file
74
+ - RuboCop with performance, rake, and rspec plugins; inherits from riboseinc OSS guide
75
+ - Some CLI files are excluded from RuboCop in `.rubocop.yml`
76
+ - Use `Utils.log` for user-facing output (prefixes with `[suma]`)
data/Gemfile CHANGED
@@ -6,7 +6,9 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "canon"
9
- gem "metanorma-standoc"
9
+ gem "metanorma", github: "metanorma/metanorma", branch: "main"
10
+ gem "metanorma-plugin-lutaml", github: "metanorma/metanorma-plugin-lutaml", branch: "main"
11
+ gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
10
12
  gem "nokogiri"
11
13
  gem "openssl", "~> 3.0"
12
14
  gem "rake"
data/README.adoc CHANGED
@@ -864,6 +864,137 @@ For complete documentation of all Expressir commands and options, see the
864
864
  https://github.com/lutaml/expressir[Expressir documentation].
865
865
 
866
866
 
867
+ === Check SVG quality command
868
+
869
+ ==== General
870
+
871
+ The `check_svg_quality` command validates SVG files for conformance and quality
872
+ using the https://github.com/claricle/svg_conform[svg_conform] library. It supports
873
+ both batch directory scanning and single file analysis.
874
+
875
+ By default, it uses the `metanorma` profile which allows raster images
876
+ (PNG, JPEG) and other features specific to Metanorma documentation.
877
+
878
+ [source,sh]
879
+ ----
880
+ $ suma check_svg_quality [PATH] [options]
881
+ ----
882
+
883
+ Parameters:
884
+
885
+ `PATH`:: Path to directory containing SVG files or a single SVG file.
886
+ Defaults to `schemas` directory.
887
+
888
+ ==== Batch directory scanning
889
+
890
+ When given a directory, it scans all SVG files and produces a quality report
891
+ sorted by error count (most problematic files first).
892
+
893
+ [example]
894
+ .Check all SVG files in schemas directory
895
+ [source,sh]
896
+ ----
897
+ $ suma check_svg_quality ../iso-10303/schemas
898
+ ----
899
+
900
+ .Output
901
+ ----
902
+ 🔍 Scanning 3878 SVG files...
903
+
904
+ ╔══════════════════════════════════════════════════════════════════════════════╗
905
+ ║ 🔍 SVG Quality Report Sorted by error count (most first) ║
906
+ ╚══════════════════════════════════════════════════════════════════════════════╝
907
+
908
+ 📊 OVERVIEW
909
+
910
+ ● Total Files : 3878
911
+ ● Valid : 3845 ✅
912
+ ● Invalid : 33 ❌
913
+ ...
914
+
915
+ 💥 CRITICAL QUALITY (2 files)
916
+
917
+ ✗ 0/100 813 errors schemas/resources/action_schema/action_schemaexpg2.svg
918
+ ✗ 25/100 156 errors schemas/modules/activity/mimexpg1.svg
919
+ ----
920
+
921
+ ==== Single file analysis
922
+
923
+ When given a path to a single SVG file, it shows detailed error breakdown
924
+ grouped by requirement type.
925
+
926
+ [example]
927
+ .Analyze a single SVG file
928
+ [source,sh]
929
+ ----
930
+ $ suma check_svg_quality schemas/resources/action_schema/action_schemaexpg2.svg
931
+ ----
932
+
933
+ .Output
934
+ ----
935
+ 📄 SVG Quality Report: schemas/resources/action_schema/action_schemaexpg2.svg
936
+
937
+ Valid: NO ❌
938
+ Errors: 813
939
+
940
+ 📋 Error Details
941
+
942
+ style (56 occurrences)
943
+ - Style property 'enable-background' removed
944
+ - Style property 'overflow' removed
945
+ ...
946
+
947
+ allowed_elements (109 occurrences)
948
+ - The element 'image' is not allowed as a child of 'svg'
949
+ ...
950
+
951
+ color_restrictions (108 occurrences)
952
+ - Color 'rgb(33, 128, 255)' in style property 'fill' is not allowed
953
+ ...
954
+ ----
955
+
956
+ Options:
957
+
958
+ `--profile=PROFILE`:: Validation profile to use (default: `metanorma`).
959
+ Other options: `svg_1_2_rfc`, `svg_1_2_rfc_with_rdf`, `base`, `lucid_fix`
960
+
961
+ `--sort=MODE`:: Sort order: `errors` (most errors first, default) or
962
+ `quality` (lowest quality scores first)
963
+
964
+ `--format=FORMAT`:: Output format: `terminal` (default), `json`, or `yaml`
965
+
966
+ `--output=PATH`, `-o PATH`:: Write output to file instead of stdout
967
+
968
+ `--min_errors=N`:: Filter to show only files with at least N errors
969
+
970
+ `--limit=N`:: Limit output to top N files
971
+
972
+ `--progress`:: Show progress during batch scanning
973
+
974
+ `--summary-only`:: Show only summary, not individual file details
975
+
976
+ [example]
977
+ .Check with custom profile
978
+ [source,sh]
979
+ ----
980
+ $ suma check_svg_quality schemas/ --profile svg_1_2_rfc
981
+ ----
982
+
983
+ [example]
984
+ .Output as JSON
985
+ [source,sh]
986
+ ----
987
+ $ suma check_svg_quality schemas/ --format json --output quality_report.json
988
+ ----
989
+
990
+ [example]
991
+ .Show only files with 100+ errors
992
+ [source,sh]
993
+ ----
994
+ $ suma check_svg_quality schemas/ --min_errors 100
995
+ ----
996
+
997
+
867
998
  == Usage: Ruby
868
999
 
869
1000
  === General
@@ -31,16 +31,15 @@ module Suma
31
31
  private
32
32
 
33
33
  def run(manifest, options)
34
- # Set schemas_all_path to match metanorma_yaml_path
35
34
  schemas_all_path = options[:schemas_all_path] ||
36
35
  manifest.gsub("metanorma", "schemas")
37
36
 
38
- Processor.run(
37
+ Processor.new(
39
38
  metanorma_yaml_path: manifest,
40
39
  schemas_all_path: schemas_all_path,
41
40
  compile: options[:compile],
42
41
  output_directory: "_site",
43
- )
42
+ ).run
44
43
  end
45
44
 
46
45
  def log_error(error)
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require_relative "../svg_quality"
5
+ require_relative "../svg_quality/report"
6
+ require_relative "../svg_quality/batch_report"
7
+ require_relative "../svg_quality/formatters/terminal_formatter"
8
+ require_relative "../svg_quality/formatters/json_formatter"
9
+ require_relative "../svg_quality/formatters/yaml_formatter"
10
+
11
+ module Suma
12
+ module Cli
13
+ # Check SVG quality using svg_conform Validator API - thin CLI wrapper
14
+ class CheckSvgQuality
15
+ DATA_PATH = "schemas"
16
+ DEFAULT_PATTERN = "**/*.svg"
17
+ DEFAULT_PROFILE = :metanorma
18
+
19
+ def initialize(pattern: DEFAULT_PATTERN, profile: DEFAULT_PROFILE,
20
+ format: "terminal", output: nil, min_errors: nil,
21
+ summary_only: false, progress: false, limit: nil,
22
+ sort: "errors")
23
+ @options = {
24
+ pattern: pattern,
25
+ profile: profile,
26
+ format: format,
27
+ output: output,
28
+ min_errors: min_errors,
29
+ summary_only: summary_only,
30
+ progress: progress,
31
+ limit: limit,
32
+ sort: sort.to_sym,
33
+ }
34
+ end
35
+
36
+ def run(path = DATA_PATH)
37
+ require "svg_conform"
38
+
39
+ path_obj = Pathname.new(path).expand_path
40
+
41
+ # Enable progress by default when outputting to terminal
42
+ show_progress = options[:progress] || ($stdout.tty? && !options[:output])
43
+ if show_progress
44
+ $stdout.sync = true
45
+ $stderr.sync = true
46
+ end
47
+
48
+ if path_obj.file?
49
+ # Single file mode - show detailed errors
50
+ analyze_single_file(path_obj)
51
+ else
52
+ # Directory mode - show batch report
53
+ svg_files = find_svg_files(path_obj)
54
+
55
+ if svg_files.empty?
56
+ puts "No SVG files found in #{path}"
57
+ return
58
+ end
59
+
60
+ puts "🔍 Scanning #{svg_files.size} SVG files..."
61
+ puts
62
+
63
+ reports = analyze_files_one_by_one(svg_files, show_progress)
64
+ batch_report = SvgQuality::BatchReport.new(reports)
65
+ sorted_report = sort_report(batch_report)
66
+ output_report(sorted_report)
67
+ end
68
+ end
69
+
70
+ def analyze_single_file(path)
71
+ validator = SvgConform::Validator.new
72
+ result = validator.validate_file(path.to_s, profile: options[:profile])
73
+
74
+ puts "📄 SVG Quality Report: #{path}"
75
+ puts ""
76
+ puts " Valid: #{result.valid? ? 'YES ✅' : 'NO ❌'}"
77
+ puts " Errors: #{result.error_count}"
78
+ puts ""
79
+
80
+ if result.errors.any?
81
+ puts " 📋 Error Details"
82
+ puts ""
83
+
84
+ # Group errors by requirement_id
85
+ by_req = result.errors.group_by(&:requirement_id)
86
+
87
+ by_req.each do |req_id, errors|
88
+ puts " #{req_id} (#{errors.size} occurrences)"
89
+ errors.first(5).each do |e|
90
+ puts " - #{e.message}"
91
+ end
92
+ if errors.size > 5
93
+ puts " ... and #{errors.size - 5} more"
94
+ end
95
+ puts ""
96
+ end
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ attr_reader :options
103
+
104
+ def find_svg_files(path)
105
+ if path.directory?
106
+ Pathname.glob(path.join(options[:pattern])).select(&:file?)
107
+ elsif path.file? && path.extname == ".svg"
108
+ [path]
109
+ else
110
+ []
111
+ end
112
+ end
113
+
114
+ def analyze_files_one_by_one(files, show_progress = false)
115
+ validator = SvgConform::Validator.new
116
+ reports = []
117
+
118
+ files.each_with_index do |file, index|
119
+ result = validator.validate_file(file.to_s,
120
+ profile: options[:profile])
121
+ report = SvgQuality::Report.new(file.to_s, result)
122
+ reports << report
123
+
124
+ if show_progress
125
+ tier = report.quality_tier
126
+ status = report.valid? ? "✅" : "❌"
127
+ msg = " [#{index + 1}/#{files.size}] #{tier[:emoji]} #{report.error_count} errors #{status} #{shorten_path(file)}\n"
128
+ $stderr.print msg
129
+ $stderr.flush
130
+ end
131
+ end
132
+
133
+ reports
134
+ end
135
+
136
+ def sort_report(batch_report)
137
+ case options[:sort]
138
+ when :quality
139
+ batch_report.sort_by_quality
140
+ else
141
+ batch_report.sort_by_errors
142
+ end
143
+ end
144
+
145
+ def output_report(batch_report)
146
+ filtered = batch_report.filter_by_min_errors(options[:min_errors])
147
+ limited = filtered.limit(options[:limit])
148
+
149
+ formatter = case options[:format].to_sym
150
+ when :json
151
+ SvgQuality::Formatters::JsonFormatter.new(limited,
152
+ output: options[:output])
153
+ when :yaml
154
+ SvgQuality::Formatters::YamlFormatter.new(limited,
155
+ output: options[:output])
156
+ else
157
+ SvgQuality::Formatters::TerminalFormatter.new(limited,
158
+ output: options[:output], sort: options[:sort])
159
+ end
160
+
161
+ puts formatter.format
162
+ end
163
+
164
+ def shorten_path(path)
165
+ p = Pathname.new(path)
166
+ if p.absolute?
167
+ begin
168
+ p.relative_path_from(Pathname.pwd)
169
+ rescue StandardError
170
+ p
171
+ end
172
+ else
173
+ p
174
+ end.to_s
175
+ end
176
+ end
177
+ end
178
+ end