svg_conform 0.1.11 → 0.1.12

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: 9300ab0026e52b929e0707e14c19ce45394da831dfd5e44f4afad14e1a2237cd
4
- data.tar.gz: b19b0d2dbce1397abb478105704c994a32f418972240c9c149d375d8be519e2c
3
+ metadata.gz: 162ed406493cfabb9a3cb94a3a6ba2ee3ed4ae95d3edf912034c17974d5cc496
4
+ data.tar.gz: 56cb8b2d5e94bcce3cc1e32b513a738ac2140f0e86848e6c101fc08ed16cfc7a
5
5
  SHA512:
6
- metadata.gz: d413baac7ebb781837ff482103d751ce3222c170c613707b8411d5c6614dff71ac874802e0e5df14e276c0d56f537d32259787559359dfb954f6e3392088102d
7
- data.tar.gz: ca1a62500f77534fca87d5b0e0136328ed81b9d9a7a81510e312a3760f31157e7a42af4934bacda3af2d6915b675bb9f4afdba2746332922cb23351bcc47fba2
6
+ metadata.gz: 1bcbdf1a7dbc43f044aa875d7a310b85064bcd47d5f227cf962303d1f3a0d024fe926b87ffc201fbf5256b0e150cfa71b5a8702e84b062b37d7935a50bc96b5b
7
+ data.tar.gz: 817c9ac5561b509c39e9f4d228b4165d3943a242bb83a88c77e8388bbce99c6746dc9c9df464a729ce44160c331b08ce9dfcc06e98c676c2944631eaf35346e5
@@ -5,6 +5,7 @@ name: release
5
5
  permissions:
6
6
  contents: write
7
7
  packages: write
8
+ id-token: write
8
9
 
9
10
  on:
10
11
  workflow_dispatch:
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-01-27 00:27:35 UTC using RuboCop version 1.82.1.
3
+ # on 2026-04-04 08:57:24 UTC using RuboCop version 1.86.0.
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
@@ -8,11 +8,9 @@
8
8
 
9
9
  # Offense count: 1
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, IndentationWidth.
12
- # SupportedStyles: with_first_argument, with_fixed_indentation
13
- Layout/ArgumentAlignment:
11
+ Gemspec/RequireMFA:
14
12
  Exclude:
15
- - 'lib/svg_conform/requirements/invalid_id_references_requirement.rb'
13
+ - 'svg_conform.gemspec'
16
14
 
17
15
  # Offense count: 1
18
16
  # This cop supports safe autocorrection (--autocorrect).
@@ -24,28 +22,42 @@ Layout/BlockAlignment:
24
22
 
25
23
  # Offense count: 1
26
24
  # This cop supports safe autocorrection (--autocorrect).
27
- # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
28
- # SupportedHashRocketStyles: key, separator, table
29
- # SupportedColonStyles: key, separator, table
30
- # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
31
- Layout/HashAlignment:
25
+ # Configuration parameters: EnforcedStyle.
26
+ # SupportedStyles: leading, trailing
27
+ Layout/LineContinuationLeadingSpace:
32
28
  Exclude:
33
- - 'spec/svg_conform/validation_context_spec.rb'
29
+ - 'spec/svgcheck_compatibility_spec.rb'
30
+
31
+ # Offense count: 5
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
34
+ # SupportedStyles: aligned, indented
35
+ Layout/LineEndStringConcatenationIndentation:
36
+ Exclude:
37
+ - 'lib/svg_conform/external_checkers/svgcheck/validation_pipeline.rb'
38
+ - 'spec/svgcheck_compatibility_spec.rb'
34
39
 
35
- # Offense count: 662
40
+ # Offense count: 649
36
41
  # This cop supports safe autocorrection (--autocorrect).
37
42
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
38
43
  # URISchemes: http, https
39
44
  Layout/LineLength:
40
45
  Enabled: false
41
46
 
42
- # Offense count: 2
47
+ # Offense count: 13
43
48
  # This cop supports safe autocorrection (--autocorrect).
44
- # Configuration parameters: AllowInHeredoc.
45
- Layout/TrailingWhitespace:
49
+ Lint/AmbiguousOperatorPrecedence:
46
50
  Exclude:
47
- - 'lib/svg_conform/requirements/invalid_id_references_requirement.rb'
48
- - 'spec/svg_conform/validation_context_spec.rb'
51
+ - 'lib/svg_conform/compatibility_analyzer.rb'
52
+ - 'lib/svg_conform/semantic_comparator.rb'
53
+ - 'lib/svg_conform/validation_result.rb'
54
+
55
+ # Offense count: 1
56
+ # This cop supports unsafe autocorrection (--autocorrect-all).
57
+ # Configuration parameters: RequireParenthesesForMethodChains.
58
+ Lint/AmbiguousRange:
59
+ Exclude:
60
+ - 'lib/svg_conform/compatibility/report_formatter.rb'
49
61
 
50
62
  # Offense count: 3
51
63
  # Configuration parameters: AllowedMethods.
@@ -73,10 +85,11 @@ Lint/DuplicateCaseCondition:
73
85
  Exclude:
74
86
  - 'lib/svg_conform/semantic_comparator.rb'
75
87
 
76
- # Offense count: 1
88
+ # Offense count: 2
77
89
  Lint/DuplicateMethods:
78
90
  Exclude:
79
91
  - 'lib/svg_conform/sax_validation_handler.rb'
92
+ - 'spec/svg_conform/validation_context_spec.rb'
80
93
 
81
94
  # Offense count: 2
82
95
  Lint/HashCompareByIdentity:
@@ -92,11 +105,18 @@ Lint/MissingSuper:
92
105
  - 'lib/svg_conform/sax_validation_handler.rb'
93
106
 
94
107
  # Offense count: 1
95
- Lint/UnreachableCode:
108
+ # This cop supports unsafe autocorrection (--autocorrect-all).
109
+ Lint/RedundantDirGlobSort:
96
110
  Exclude:
97
- - 'lib/svg_conform/commands/check.rb'
111
+ - 'lib/tasks/fixtures.rake'
98
112
 
99
- # Offense count: 146
113
+ # Offense count: 1
114
+ # This cop supports unsafe autocorrection (--autocorrect-all).
115
+ Lint/SuppressedExceptionInNumberConversion:
116
+ Exclude:
117
+ - 'lib/svg_conform/remediations/viewbox_remediation.rb'
118
+
119
+ # Offense count: 145
100
120
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
101
121
  Metrics/AbcSize:
102
122
  Enabled: false
@@ -117,7 +137,7 @@ Metrics/BlockNesting:
117
137
  Metrics/CyclomaticComplexity:
118
138
  Enabled: false
119
139
 
120
- # Offense count: 259
140
+ # Offense count: 256
121
141
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
122
142
  Metrics/MethodLength:
123
143
  Max: 154
@@ -132,6 +152,18 @@ Metrics/ParameterLists:
132
152
  Metrics/PerceivedComplexity:
133
153
  Enabled: false
134
154
 
155
+ # Offense count: 9
156
+ # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
157
+ # AllowedMethods: call
158
+ # WaywardPredicates: infinite?, nonzero?
159
+ Naming/PredicateMethod:
160
+ Exclude:
161
+ - 'lib/svg_conform/compatibility/file_processor.rb'
162
+ - 'lib/svg_conform/node_helpers.rb'
163
+ - 'lib/svg_conform/remediations/base_remediation.rb'
164
+ - 'lib/svg_conform/requirements/style_requirement.rb'
165
+ - 'lib/svg_conform/semantic_comparator.rb'
166
+
135
167
  # Offense count: 2
136
168
  # Configuration parameters: MinSize.
137
169
  Performance/CollectionLiteralInLoop:
@@ -139,6 +171,12 @@ Performance/CollectionLiteralInLoop:
139
171
  - 'lib/svg_conform/batch_report.rb'
140
172
  - 'lib/svg_conform/compatibility/pattern_discovery.rb'
141
173
 
174
+ # Offense count: 2
175
+ # This cop supports unsafe autocorrection (--autocorrect-all).
176
+ Performance/MapCompact:
177
+ Exclude:
178
+ - 'lib/svg_conform/remediations/viewbox_remediation.rb'
179
+
142
180
  # Offense count: 9
143
181
  # Configuration parameters: IgnoredMetadata.
144
182
  RSpec/DescribeClass:
@@ -168,6 +206,11 @@ RSpec/LeakyConstantDeclaration:
168
206
  Exclude:
169
207
  - 'spec/svg_conform/profiles/svg_1_2_rfc_profile_spec.rb'
170
208
 
209
+ # Offense count: 1
210
+ RSpec/LeakyLocalVariable:
211
+ Exclude:
212
+ - 'spec/svgcheck_compatibility_spec.rb'
213
+
171
214
  # Offense count: 2
172
215
  # Configuration parameters: .
173
216
  # SupportedStyles: have_received, receive
@@ -183,11 +226,22 @@ RSpec/MultipleDescribes:
183
226
  RSpec/MultipleExpectations:
184
227
  Max: 8
185
228
 
229
+ # Offense count: 44
230
+ # This cop supports unsafe autocorrection (--autocorrect-all).
231
+ RSpec/Output:
232
+ Enabled: false
233
+
186
234
  # Offense count: 1
187
235
  Rake/MethodDefinitionInTask:
188
236
  Exclude:
189
237
  - 'lib/tasks/svgcheck.rake'
190
238
 
239
+ # Offense count: 3
240
+ # This cop supports safe autocorrection (--autocorrect).
241
+ Style/ComparableClamp:
242
+ Exclude:
243
+ - 'lib/svg_conform/css_color.rb'
244
+
191
245
  # Offense count: 2
192
246
  # This cop supports safe autocorrection (--autocorrect).
193
247
  # Configuration parameters: EnforcedStyle, AllowComments.
@@ -205,9 +259,28 @@ Style/FormatStringToken:
205
259
  EnforcedStyle: unannotated
206
260
 
207
261
  # Offense count: 1
208
- Style/MissingRespondToMissing:
262
+ # This cop supports unsafe autocorrection (--autocorrect-all).
263
+ # Configuration parameters: AllowSplatArgument.
264
+ Style/HashConversion:
265
+ Exclude:
266
+ - 'lib/svg_conform/sax_validation_handler.rb'
267
+
268
+ # Offense count: 2
269
+ # This cop supports unsafe autocorrection (--autocorrect-all).
270
+ Style/HashSlice:
209
271
  Exclude:
210
- - 'lib/svg_conform/cli.rb'
272
+ - 'spec/svg_conform_spec.rb'
273
+
274
+ # Offense count: 1
275
+ # Configuration parameters: AllowedClasses.
276
+ Style/OneClassPerFile:
277
+ Exclude:
278
+ - 'lib/svg_conform/compatibility/report_formatter.rb'
279
+
280
+ # Offense count: 1
281
+ Style/OpenStructUse:
282
+ Exclude:
283
+ - 'lib/svg_conform/validator.rb'
211
284
 
212
285
  # Offense count: 4
213
286
  # Configuration parameters: AllowedMethods.
@@ -216,10 +289,46 @@ Style/OptionalBooleanParameter:
216
289
  Exclude:
217
290
  - 'lib/svg_conform/semantic_comparator.rb'
218
291
 
292
+ # Offense count: 1
293
+ # This cop supports unsafe autocorrection (--autocorrect-all).
294
+ Style/PartitionInsteadOfDoubleSelect:
295
+ Exclude:
296
+ - 'lib/svg_conform/requirements/style_requirement.rb'
297
+
298
+ # Offense count: 1
299
+ # This cop supports unsafe autocorrection (--autocorrect-all).
300
+ Style/ReduceToHash:
301
+ Exclude:
302
+ - 'lib/svg_conform/requirements/base_requirement.rb'
303
+
219
304
  # Offense count: 1
220
305
  # This cop supports safe autocorrection (--autocorrect).
221
306
  # Configuration parameters: AllowedMethods.
222
- # AllowedMethods: nonzero?
307
+ # AllowedMethods: infinite?, nonzero?
223
308
  Style/RedundantCondition:
224
309
  Exclude:
225
310
  - 'lib/svg_conform/external_checkers/svgcheck/parser.rb'
311
+
312
+ # Offense count: 2
313
+ # This cop supports unsafe autocorrection (--autocorrect-all).
314
+ Style/RedundantFormat:
315
+ Exclude:
316
+ - 'lib/svg_conform/compatibility/report_formatter.rb'
317
+
318
+ # Offense count: 2
319
+ # This cop supports unsafe autocorrection (--autocorrect-all).
320
+ Style/SelectByKind:
321
+ Exclude:
322
+ - 'lib/svg_conform/sax_validation_handler.rb'
323
+
324
+ # Offense count: 1
325
+ # This cop supports unsafe autocorrection (--autocorrect-all).
326
+ Style/SelectByRegexp:
327
+ Exclude:
328
+ - 'lib/svg_conform/remediations/color_remediation.rb'
329
+
330
+ # Offense count: 1
331
+ # This cop supports safe autocorrection (--autocorrect).
332
+ Style/SuperArguments:
333
+ Exclude:
334
+ - 'lib/svg_conform/requirements/invalid_id_references_requirement.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,115 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Build/Test Commands
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ bin/setup
10
+
11
+ # Run tests
12
+ rake spec
13
+
14
+ # Run a single spec file
15
+ bundle exec rspec spec/svg_conform/validator_spec.rb
16
+
17
+ # Run tests with detailed output
18
+ bundle exec rspec --format documentation
19
+
20
+ # Run linting
21
+ rake rubocop
22
+
23
+ # Run both tests and linting
24
+ rake
25
+
26
+ # Run a specific test by line number
27
+ bundle exec rspec spec/svg_conform/validator_spec.rb:42
28
+ ```
29
+
30
+ ## Architecture
31
+
32
+ ### Two-Mode Validation System
33
+
34
+ SvgConform uses two distinct operating modes:
35
+
36
+ 1. **SAX Validation Mode** (always used for validation):
37
+ - Memory-safe streaming XML parser
38
+ - Constant memory regardless of file size
39
+ - Handles files of any size (tested with 100MB+)
40
+ - Read-only, cannot modify documents
41
+ - Implemented via `SaxDocument` and `SaxValidationHandler`
42
+
43
+ 2. **DOM Remediation Mode** (only when applying fixes):
44
+ - Full document tree loaded in memory
45
+ - XPath queries and tree modification
46
+ - Memory scales with file size
47
+ - Only activated when `fix: true` is specified
48
+
49
+ ### Core Classes
50
+
51
+ - `Validator` - Main entry point; normalizes all inputs to SAX validation
52
+ - `SaxDocument` - SAX-based document wrapper for memory-safe validation
53
+ - `Document` - DOM-based document wrapper for modifications
54
+ - `Profile` - Collection of requirements and remediations; loaded from YAML
55
+ - `Profiles` - Factory for loading/retrieving profile instances
56
+ - `ValidationContext` - Carries state during validation (errors, data collected)
57
+ - `ValidationResult` - Holds validation outcomes after checking
58
+ - `ConformanceReport` - Formats validation results for output
59
+
60
+ ### Requirements System
61
+
62
+ Requirements validate SVG documents. All requirements must support SAX validation:
63
+
64
+ - Requirements inherit from `BaseRequirement` in `lib/svg_conform/requirements/`
65
+ - Must implement `validate_sax_element(element, context)` for immediate checks
66
+ - Can optionally implement `collect_sax_data()` + `validate_sax_complete()` for deferred validation (e.g., cross-reference checks)
67
+ - Use `context.data` hash for per-document state (not instance variables - they leak across validations)
68
+ - Never attempt DOM operations in SAX callbacks
69
+
70
+ Key requirement classes: `NamespaceRequirement`, `ViewboxRequiredRequirement`, `IdReferenceRequirement`, `ForbiddenContentRequirement`, `ColorRestrictionsRequirement`, etc.
71
+
72
+ ### Remediations System
73
+
74
+ Remediations fix validation failures. They run in DOM mode:
75
+
76
+ - Inherit from `BaseRemediation` in `lib/svg_conform/remediations/`
77
+ - Linked to requirements via `targets` array in profile YAML
78
+ - Only execute when `should_execute?(failed_requirements)` returns true
79
+
80
+ ### Profile Configuration
81
+
82
+ Profiles are defined in YAML under `config/profiles/`:
83
+ - `base.yml` - Common requirements shared by other profiles
84
+ - `metanorma.yml`, `svg_1_2_rfc.yml`, etc. - Specific profile definitions
85
+ - Profile class map is built dynamically from filesystem in `Profile.build_class_map`
86
+
87
+ Profiles support `import` to inherit from other profiles (e.g., `metanorma` imports `base`).
88
+
89
+ ### CLI Structure
90
+
91
+ - `lib/svg_conform/cli.rb` - Thor-based CLI entry point
92
+ - `lib/svg_conform/commands/check.rb` - Main validation command
93
+ - `lib/svg_conform/commands/profiles.rb` - Profile listing command
94
+ - `lib/svg_conform/commands/svgcheck.rb` - SVGCheck compatibility subcommands
95
+
96
+ ### Input Handling
97
+
98
+ The `Validator` accepts multiple input types and always uses SAX:
99
+ - String (XML content) → parsed directly with SAX
100
+ - Moxml/Nokogiri documents → serialized once, then SAX validated
101
+ - Document/Element objects → same serialization approach
102
+
103
+ This ensures memory safety for large files regardless of input type.
104
+
105
+ ### Key Files
106
+
107
+ - `lib/svg_conform.rb` - Main require file with autoloads
108
+ - `lib/svg_conform/element_proxy.rb` - Lightweight SAX element representation
109
+ - `lib/svg_conform/validation_context.rb` - Validation state container
110
+ - `lib/svg_conform/sax_document.rb` - SAX document wrapper
111
+ - `lib/svg_conform/sax_validation_handler.rb` - SAX callback handler
112
+
113
+ ### Error Handling Pattern
114
+
115
+ The CLI uses `exit 1` for error exits. When improving error handling, prefer raising proper Ruby exceptions (`ValidationError`, `ProfileError`, `ArgumentError`) rather than using `exit`/`abort` directly, as Thor provides mechanisms for displaying errors gracefully to users.
@@ -11,6 +11,16 @@ require_relative "commands/profiles"
11
11
  module SvgConform
12
12
  # Thor-based CLI for SvgConform with svgcheck-like functionality
13
13
  class Cli < Thor
14
+ # Exit with non-zero status when a command raises an error
15
+ def self.exit_on_failure?
16
+ true
17
+ end
18
+
19
+ # Use strict mode to catch unknown options at parse time rather than runtime
20
+ def self.check_unknown_options?(*)
21
+ true
22
+ end
23
+
14
24
  def initialize(*args)
15
25
  super
16
26
  end
@@ -95,13 +105,10 @@ module SvgConform
95
105
  puts "SvgConform #{SvgConform::VERSION}"
96
106
  end
97
107
 
98
- private
99
-
100
- def method_missing(method_name, *_args)
101
- puts Paint["Unknown command: #{method_name}", :red]
102
- puts
103
- help
104
- exit 1
108
+ # Let Thor handle unknown commands - it raises Thor::UndefinedCommandError
109
+ # which provides helpful suggestions (e.g., "Did you mean?")
110
+ def self.handle_unknown_command?(*)
111
+ false
105
112
  end
106
113
  end
107
114
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thor"
3
4
  require "paint"
4
5
  require "table_tennis"
5
6
  require "fileutils"
@@ -9,6 +10,12 @@ module SvgConform
9
10
  module Commands
10
11
  # Check command for validating SVG files (single or batch)
11
12
  class Check
13
+ # User-facing errors should be Thor::Error for nice CLI display
14
+ class CheckError < Thor::Error; end
15
+ class FileNotFoundError < CheckError; end
16
+ class DirectoryNotFoundError < CheckError; end
17
+ class NoFilesFoundError < CheckError; end
18
+
12
19
  def initialize(files, options)
13
20
  @files = Array(files)
14
21
  @options = options
@@ -19,8 +26,7 @@ module SvgConform
19
26
  files_to_process = determine_files
20
27
 
21
28
  if files_to_process.empty?
22
- puts Paint["Error: No SVG files found", :red]
23
- exit 1
29
+ raise NoFilesFoundError, "No SVG files found"
24
30
  end
25
31
 
26
32
  # Single file mode vs batch mode
@@ -38,20 +44,17 @@ module SvgConform
38
44
  # Directory mode: recursive scan
39
45
  dir = @options[:directory]
40
46
  unless Dir.exist?(dir)
41
- puts Paint["Error: Directory '#{dir}' not found", :red]
42
- exit 1
47
+ raise DirectoryNotFoundError, "Directory '#{dir}' not found"
43
48
  end
44
- Dir.glob(File.join(dir, "**/*.svg")).sort
49
+
50
+ Dir.glob(File.join(dir, "**/*.svg"))
45
51
  elsif @files.empty?
46
- puts Paint["Error: No files or directory specified", :red]
47
- exit 1
48
- []
52
+ raise NoFilesFoundError, "No files or directory specified"
49
53
  else
50
54
  # File mode: validate each file exists
51
55
  @files.each do |file|
52
56
  unless File.exist?(file)
53
- puts Paint["Error: File '#{file}' not found", :red]
54
- exit 1
57
+ raise FileNotFoundError, "File '#{file}' not found"
55
58
  end
56
59
  end
57
60
  @files
@@ -61,38 +64,33 @@ module SvgConform
61
64
  def process_single_file(file)
62
65
  @file = file
63
66
 
64
- begin
65
- # Validate the file
66
- validator = SvgConform::Validator.new
67
- result = validator.validate_file(@file,
68
- profile: @options[:profile].to_sym)
69
-
70
- # Generate report
71
- report = SvgConform::ConformanceReport.from_svg_conform_result(
72
- File.basename(@file),
73
- result,
74
- profile: @options[:profile].to_sym,
75
- )
76
-
77
- # Output based on format
78
- case @options[:format]
79
- when "yaml"
80
- output_yaml(report)
81
- when "json"
82
- output_json(report)
83
- else
84
- output_table(report)
85
- end
67
+ # Validate the file
68
+ validator = SvgConform::Validator.new
69
+ result = validator.validate_file(@file,
70
+ profile: @options[:profile].to_sym)
71
+
72
+ # Generate report
73
+ report = SvgConform::ConformanceReport.from_svg_conform_result(
74
+ File.basename(@file),
75
+ result,
76
+ profile: @options[:profile].to_sym,
77
+ )
78
+
79
+ # Output based on format
80
+ case @options[:format]
81
+ when "yaml"
82
+ output_yaml(report)
83
+ when "json"
84
+ output_json(report)
85
+ else
86
+ output_table(report)
87
+ end
86
88
 
87
- # Create remediated file if requested
88
- create_remediated_file(result) if @options[:fix]
89
+ # Create remediated file if requested
90
+ create_remediated_file(result) if @options[:fix]
89
91
 
90
- # Exit with appropriate code
91
- exit(report.valid? ? 0 : 1)
92
- rescue StandardError => e
93
- puts Paint["Error: #{e.message}", :red]
94
- exit 1
95
- end
92
+ # Exit with appropriate code based on validation result
93
+ exit(report.valid? ? 0 : 1)
96
94
  end
97
95
 
98
96
  def output_table(report)
@@ -275,15 +273,13 @@ module SvgConform
275
273
 
276
274
  # Multiple files require output-dir
277
275
  if files.length > 1 && !@options[:output_dir]
278
- puts Paint["Error: --output-dir required for multiple files with --fix",
279
- :red]
280
- exit 1
276
+ raise CheckError,
277
+ "--output-dir required for multiple files with --fix"
281
278
  end
282
279
 
283
280
  # In-place requires force
284
281
  if @options[:in_place] && !@options[:force]
285
- puts Paint["Error: --in-place requires --force flag for safety", :red]
286
- exit 1
282
+ raise CheckError, "--in-place requires --force flag for safety"
287
283
  end
288
284
  end
289
285
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thor"
3
4
  require "paint"
4
5
  require "table_tennis"
5
6
 
@@ -7,6 +8,8 @@ module SvgConform
7
8
  module Commands
8
9
  # Profiles command for listing available validation profiles
9
10
  class Profiles
11
+ class ProfilesError < Thor::Error; end
12
+
10
13
  def initialize(options)
11
14
  @options = options
12
15
  # Paint doesn't need initialization like Pastel
@@ -20,9 +23,6 @@ module SvgConform
20
23
  else
21
24
  display_profile_list(profiles)
22
25
  end
23
- rescue StandardError => e
24
- puts Paint["Error: #{e.message}", :red]
25
- exit 1
26
26
  end
27
27
 
28
28
  private
@@ -6,6 +6,8 @@ module SvgConform
6
6
  module Commands
7
7
  # Compare command for comparing SvgConform validation with svgcheck reports
8
8
  class SvgcheckCompare
9
+ class CompareError < Thor::Error; end
10
+
9
11
  def initialize(file, options)
10
12
  @file = file
11
13
  @options = options
@@ -17,46 +19,36 @@ module SvgConform
17
19
  validation_file = determine_validation_file(@file)
18
20
 
19
21
  unless File.exist?(validation_file)
20
- puts Paint["Error: File '#{validation_file}' not found", :red]
21
- exit 1
22
+ raise CompareError, "File '#{validation_file}' not found"
22
23
  end
23
24
 
24
- begin
25
- # Find or use specified svgcheck report
26
- svgcheck_report_path = find_svgcheck_report
27
- unless svgcheck_report_path && File.exist?(svgcheck_report_path)
28
- puts Paint["Error: svgcheck report not found", :red]
29
- if svgcheck_report_path
30
- puts Paint["Expected: #{svgcheck_report_path}",
31
- :dim]
32
- end
33
- exit 1
34
- end
35
-
36
- # Load svgcheck report using the new external checker parser
37
- parser = SvgConform::ExternalCheckers::Svgcheck::Parser.new
38
- error_content = File.read(svgcheck_report_path)
39
- svgcheck_report = parser.parse(error_content, nil,
40
- filename: File.basename(@file))
41
-
42
- # Generate SvgConform report using the correct validation file
43
- validator = SvgConform::Validator.new
44
- result = validator.validate_file(validation_file,
45
- profile: @options[:profile].to_sym)
46
- svg_conform_report = SvgConform::ConformanceReport.from_svg_conform_result(
47
- File.basename(@file),
48
- result,
49
- profile: @options[:profile].to_sym,
50
- )
51
-
52
- # Compare the reports
53
- comparator = SvgConform::ReportComparator.new
54
- comparator.compare_reports(svg_conform_report, svgcheck_report,
55
- File.basename(@file))
56
- rescue StandardError => e
57
- puts Paint["Error: #{e.message}", :red]
58
- exit 1
25
+ # Find or use specified svgcheck report
26
+ svgcheck_report_path = find_svgcheck_report
27
+ unless svgcheck_report_path && File.exist?(svgcheck_report_path)
28
+ raise CompareError,
29
+ "svgcheck report not found#{" - expected: #{svgcheck_report_path}" if svgcheck_report_path}"
59
30
  end
31
+
32
+ # Load svgcheck report using the new external checker parser
33
+ parser = SvgConform::ExternalCheckers::Svgcheck::Parser.new
34
+ error_content = File.read(svgcheck_report_path)
35
+ svgcheck_report = parser.parse(error_content, nil,
36
+ filename: File.basename(@file))
37
+
38
+ # Generate SvgConform report using the correct validation file
39
+ validator = SvgConform::Validator.new
40
+ result = validator.validate_file(validation_file,
41
+ profile: @options[:profile].to_sym)
42
+ svg_conform_report = SvgConform::ConformanceReport.from_svg_conform_result(
43
+ File.basename(@file),
44
+ result,
45
+ profile: @options[:profile].to_sym,
46
+ )
47
+
48
+ # Compare the reports
49
+ comparator = SvgConform::ReportComparator.new
50
+ comparator.compare_reports(svg_conform_report, svgcheck_report,
51
+ File.basename(@file))
60
52
  end
61
53
 
62
54
  private
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thor"
3
4
  require_relative "../compatibility/compatibility_analyzer"
4
5
 
5
6
  module SvgConform
6
7
  module Commands
7
8
  # Command for comparing SvgConform with svgcheck compatibility
8
9
  class SvgcheckCompatibility
10
+ class CompatibilityError < Thor::Error; end
11
+
9
12
  def initialize(options = {})
10
13
  @options = options
11
14
  end
@@ -29,14 +32,16 @@ module SvgConform
29
32
 
30
33
  def validate_options
31
34
  mode = @options[:mode] || @options["mode"] || "check"
32
- raise "Invalid mode: #{mode}. Must be 'check' or 'repair'" unless %w[
33
- check repair
34
- ].include?(mode)
35
+ unless %w[check repair].include?(mode)
36
+ raise CompatibilityError,
37
+ "Invalid mode: #{mode}. Must be 'check' or 'repair'"
38
+ end
35
39
 
36
40
  file = @options[:file] || @options["file"]
37
- return unless file && !file.match?(/\.(svg|xml)$/i)
38
-
39
- raise "File must have .svg or .xml extension: #{file}"
41
+ if file && !file.match?(/\.(svg|xml)$/i)
42
+ raise CompatibilityError,
43
+ "File must have .svg or .xml extension: #{file}"
44
+ end
40
45
  end
41
46
  end
42
47
  end
@@ -96,7 +96,7 @@ module SvgConform
96
96
  attribute :id, :string
97
97
  attribute :description, :string
98
98
  attribute :type, :string, polymorphic_class: true, default: -> {
99
- self.class.name.split("::").last
99
+ self.class.name&.split("::")&.last
100
100
  }
101
101
 
102
102
  yaml do
@@ -161,8 +161,8 @@ module SvgConform
161
161
 
162
162
  attrs = node.attributes || []
163
163
  # Convert array of Moxml::Attribute objects to hash
164
- attrs.each_with_object({}) do |attr, hash|
165
- hash[attr.name] = attr.value
164
+ attrs.to_h do |attr|
165
+ [attr.name, attr.value]
166
166
  end
167
167
  end
168
168
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SvgConform
4
- VERSION = "0.1.11"
4
+ VERSION = "0.1.12"
5
5
  end
@@ -83,8 +83,8 @@ RSpec.describe SvgConform::Commands::Check do
83
83
 
84
84
  expect do
85
85
  command.execute
86
- end.to output(/Error: File 'nonexistent.svg' not found/).to_stdout
87
- .and raise_error(SystemExit)
86
+ end.to raise_error(SvgConform::Commands::Check::FileNotFoundError,
87
+ /nonexistent.svg/)
88
88
  end
89
89
  end
90
90
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svg_conform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-27 00:00:00.000000000 Z
11
+ date: 2026-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -87,6 +87,7 @@ files:
87
87
  - ".rspec"
88
88
  - ".rubocop.yml"
89
89
  - ".rubocop_todo.yml"
90
+ - CLAUDE.md
90
91
  - CODE_OF_CONDUCT.md
91
92
  - Gemfile
92
93
  - README.adoc