suma 0.2.5 → 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
@@ -1,12 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thor"
2
4
  require_relative "../thor_ext"
3
- require "fileutils"
4
- require "yaml"
5
- require "pathname"
5
+ require_relative "../schema_manifest_generator"
6
6
 
7
7
  module Suma
8
8
  module Cli
9
- # GenerateSchemas command to generate Schemas YAML by Metanorma YAML
10
9
  class GenerateSchemas < Thor
11
10
  desc "generate_schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE",
12
11
  "Generate EXPRESS schema manifest file from Metanorma site manifest"
@@ -14,125 +13,12 @@ module Suma
14
13
  desc: "Exclude schemas paths by pattern " \
15
14
  "(e.g. `*_lf.exp`)"
16
15
 
17
- YAML_FILE_EXTENSIONS = [".yaml", ".yml"].freeze
18
-
19
- def generate_schemas(metanorma_manifest_file, schema_manifest_file) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
20
- metanorma_manifest_file = File.expand_path(metanorma_manifest_file)
21
-
22
- unless File.exist?(metanorma_manifest_file)
23
- raise Errno::ENOENT, "Specified file `#{metanorma_manifest_file}` " \
24
- "not found."
25
- end
26
-
27
- unless File.file?(metanorma_manifest_file)
28
- raise ArgumentError, "Specified path `#{metanorma_manifest_file}` " \
29
- "is not a file."
30
- end
31
-
32
- [metanorma_manifest_file, schema_manifest_file].each do |file|
33
- if !YAML_FILE_EXTENSIONS.include?(File.extname(file))
34
- raise ArgumentError, "Specified file `#{file}` is not a YAML file."
35
- end
36
- end
37
-
38
- run(
39
- metanorma_manifest_file, schema_manifest_file,
40
- exclude_paths: options[:exclude_paths]
41
- )
42
- end
43
-
44
- private
45
-
46
- def run(metanorma_manifest_file, schema_manifest_file, exclude_paths: nil)
47
- metanorma_data = load_yaml(metanorma_manifest_file)
48
- collection_files = metanorma_data["metanorma"]["source"]["files"]
49
- manifest_files = load_manifest_files(collection_files)
50
- all_schemas = load_project_schemas(manifest_files, exclude_paths,
51
- schema_manifest_file)
52
- all_schemas["schemas"] = all_schemas["schemas"].sort.to_h
53
- output_data(all_schemas, schema_manifest_file)
54
- end
55
-
56
- def output_data(all_schemas, path)
57
- puts "Writing the Schemas YAML file to #{File.expand_path(path)}..."
58
- # debug use only
59
- # puts all_schemas.to_yaml
60
- File.write(File.expand_path(path), all_schemas.to_yaml)
61
- puts "Writing the Schemas YAML file to #{File.expand_path(path)}...Done"
62
- end
63
-
64
- def load_yaml(file_path)
65
- YAML.safe_load(
66
- File.read(file_path, encoding: "UTF-8"),
67
- aliases: true,
68
- )
69
- end
70
-
71
- def load_manifest_files(collection_files)
72
- manifest_files = collection_files.map do |c|
73
- collection_data = load_yaml(c)
74
- collection_data["manifest"]["docref"].map { |docref| docref["file"] }
75
- end
76
- manifest_files.flatten
77
- end
78
-
79
- def load_project_schemas( # rubocop:disable Metrics/AbcSize
80
- manifest_files, exclude_paths, schema_manifest_file
81
- )
82
- all_schemas = { "schemas" => {} }
83
-
84
- manifest_files.each do |file|
85
- # load schemas.yaml from the location of the collection.yml file
86
- schemas_file_path = File.expand_path(
87
- file.gsub("collection.yml", "schemas.yaml"),
88
- )
89
- unless File.exist?(schemas_file_path)
90
- puts "Schemas file not found: #{schemas_file_path}"
91
- next
92
- end
93
-
94
- schemas_data = load_yaml(schemas_file_path)
95
-
96
- if schemas_data["schemas"]
97
- schemas_data["schemas"] = fix_path(
98
- schemas_data,
99
- schemas_file_path,
100
- schema_manifest_file,
101
- )
102
- all_schemas["schemas"].merge!(schemas_data["schemas"])
103
- end
104
-
105
- if exclude_paths
106
- all_schemas["schemas"].delete_if do |_key, value|
107
- value["path"].match?(
108
- Regexp.new(exclude_paths.gsub("*", "(.*){1,999}")),
109
- )
110
- end
111
- end
112
- end
113
-
114
- all_schemas
115
- end
116
-
117
- def fix_path(schemas_data, schemas_file_path, schema_manifest_file) # rubocop:disable Metrics/AbcSize
118
- schema_manifest_path = File.expand_path(schema_manifest_file, Dir.pwd)
119
-
120
- schemas_data["schemas"].each do |key, value|
121
- # resolve the path in the schema file by the path in the schemas.yaml
122
- path_in_schema = File.expand_path(
123
- value["path"],
124
- File.dirname(schemas_file_path),
125
- )
126
-
127
- # calculate the relative path from the schema manifest file
128
- fixed_path = Pathname.new(path_in_schema).relative_path_from(
129
- Pathname.new(File.dirname(schema_manifest_path)),
130
- )
131
-
132
- { key => value.merge!("path" => fixed_path.to_s) }
133
- end
134
-
135
- schemas_data["schemas"]
16
+ def generate_schemas(metanorma_manifest_file, schema_manifest_file)
17
+ SchemaManifestGenerator.new(
18
+ metanorma_manifest_file,
19
+ schema_manifest_file,
20
+ exclude_paths: options[:exclude_paths],
21
+ ).generate
136
22
  end
137
23
  end
138
24
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "thor"
4
4
  require_relative "../utils"
5
+ require_relative "../link_validator"
5
6
  require "expressir"
6
7
 
7
8
  module Suma
8
9
  module Cli
9
- # ValidateLinks command for managing EXPRESS links
10
10
  class ValidateLinks < Thor
11
11
  desc "extract_and_validate SCHEMAS_FILE DOCUMENTS_PATH [OUTPUT_FILE]",
12
12
  "Extract and validate express links without creating intermediate file"
@@ -16,47 +16,38 @@ module Suma
16
16
  load_dependencies
17
17
  paths = prepare_file_paths(schemas_file, documents_path, output_file)
18
18
 
19
- # Load schemas and extract links
20
19
  schemas_config = load_schemas_config(paths[:schemas_file])
21
- exp_files = collect_schema_paths(schemas_config,
22
- paths[:schemas_file_rel])
20
+ exp_files = collect_schema_paths(schemas_config, paths[:schemas_file_rel])
23
21
  adoc_files = find_adoc_files(paths[:documents_path])
24
22
 
25
23
  all_files = adoc_files + exp_files
26
24
  display_file_counts(adoc_files, exp_files)
27
25
 
28
- # Extract links from files
29
26
  links_by_file = extract_links(all_files)
30
27
 
31
- # Validate links against schemas
32
28
  repo = load_express_schemas(schemas_config)
33
- unresolved_links = validate_links(links_by_file, repo)
29
+ index = SchemaIndex.new(repo)
30
+ unresolved = LinkValidator.new(index).validate(links_by_file)
34
31
 
35
- # Generate and output results
36
32
  write_validation_results(paths[:output_file], paths[:output_file_rel],
37
- unresolved_links, links_by_file)
33
+ unresolved, links_by_file)
38
34
  end
39
35
 
40
36
  private
41
37
 
42
- # Load all required dependencies for link validation
43
38
  def load_dependencies
44
- # Lazy-load dependencies only when this command is actually used
45
39
  require "expressir"
46
40
  require "ruby-progressbar"
47
41
  require "pathname"
48
42
  end
49
43
 
50
- # Prepare and normalize all file paths needed for the operation
51
44
  def prepare_file_paths(schemas_file, documents_path, output_file)
52
- # Convert to absolute paths
53
45
  schemas_file_path = Pathname.new(schemas_file).expand_path
54
- documents_path = Pathname.new(documents_path).expand_path
46
+ documents_path_exp = Pathname.new(documents_path).expand_path
55
47
  output_file_path = Pathname.new(output_file).expand_path
56
48
 
57
- # Store relative paths for display
58
49
  schemas_file_rel = Pathname.new(schemas_file_path).relative_path_from(Pathname.pwd).to_s
59
- documents_path_rel = Pathname.new(documents_path).relative_path_from(Pathname.pwd).to_s
50
+ documents_path_rel = Pathname.new(documents_path_exp).relative_path_from(Pathname.pwd).to_s
60
51
  output_file_rel = Pathname.new(output_file_path).relative_path_from(Pathname.pwd).to_s
61
52
 
62
53
  puts "Extracting and validating express links using schemas from #{schemas_file_rel}..."
@@ -65,46 +56,35 @@ module Suma
65
56
  {
66
57
  schemas_file: schemas_file_path,
67
58
  schemas_file_rel: schemas_file_rel,
68
- documents_path: documents_path,
59
+ documents_path: documents_path_exp,
69
60
  documents_path_rel: documents_path_rel,
70
61
  output_file: output_file_path,
71
62
  output_file_rel: output_file_rel,
72
63
  }
73
64
  end
74
65
 
75
- # Load and initialize the schemas configuration
76
66
  def load_schemas_config(schemas_file_path)
77
67
  schemas_config = Expressir::SchemaManifest.from_yaml(File.read(schemas_file_path))
78
- # Ensure the config is initialized with the correct path to resolve relative paths
79
68
  schemas_config.set_initial_path(schemas_file_path.to_s)
80
69
  schemas_config
81
70
  rescue StandardError => e
82
- puts "Error loading schemas file: #{e.message}"
83
- exit(1)
71
+ raise Suma::Error, "Error loading schemas file: #{e.message}"
84
72
  end
85
73
 
86
- # Collect paths to all schema files from the config
87
74
  def collect_schema_paths(schemas_config, schemas_file_rel)
88
- exp_files = []
89
- schemas_config.schemas.each do |schema|
90
- exp_files << schema.path if schema.path
91
- end
92
-
75
+ exp_files = schemas_config.schemas.filter_map(&:path)
93
76
  puts "Found #{exp_files.size} EXPRESS schema files from #{schemas_file_rel}"
94
77
  exp_files
95
78
  end
96
79
 
97
- # Find all AsciiDoc files in the specified path
98
80
  def find_adoc_files(documents_path)
99
81
  Dir.glob(documents_path.join("**", "*.adoc").to_s)
100
82
  end
101
83
 
102
- # Display counts of discovered files
103
84
  def display_file_counts(adoc_files, exp_files)
104
85
  puts "Found #{adoc_files.size} AsciiDoc files and #{exp_files.size} EXPRESS files"
105
86
  end
106
87
 
107
- # Create a standardized progress bar
108
88
  def create_progress_bar(title, total)
109
89
  ProgressBar.create(
110
90
  title: title,
@@ -116,7 +96,6 @@ module Suma
116
96
  )
117
97
  end
118
98
 
119
- # Extract EXPRESS links from all files
120
99
  def extract_links(files)
121
100
  links_by_file = {}
122
101
  link_count = 0
@@ -127,7 +106,6 @@ module Suma
127
106
  progress.increment
128
107
  begin
129
108
  content = File.read(file)
130
- # Extract links while ignoring text after comma
131
109
  express_links = content.scan(/<<express:([^,>]+)(?:,[^>]+)?>>/).flatten.uniq
132
110
 
133
111
  if express_links.any?
@@ -143,282 +121,31 @@ module Suma
143
121
  links_by_file
144
122
  end
145
123
 
146
- # Load all EXPRESS schemas for validation
147
124
  def load_express_schemas(schemas_config)
148
- # Get all schema paths for validation
149
125
  schema_paths = {}
150
- schemas_config.schemas.each do |schema|
151
- schema_paths[schema.id] = schema.path
152
- end
126
+ schemas_config.schemas.each { |s| schema_paths[s.id] = s.path }
153
127
 
154
128
  puts "Loading #{schema_paths.size} EXPRESS schemas for validation..."
155
129
 
156
- # Setup progress bar for schema loading
157
- loading_progress = create_progress_bar("Loading schemas",
158
- schema_paths.size)
130
+ loading_progress = create_progress_bar("Loading schemas", schema_paths.size)
159
131
 
160
- # Try to load all schemas with progress tracking
161
132
  begin
162
133
  repo = Expressir::Express::Parser.from_files(schema_paths.values) do |filename, _schemas, error|
163
134
  loading_progress.increment
164
- if error
165
- puts "\nWarning: Error loading schema #{filename}: #{error.message}"
166
- end
135
+ puts "\nWarning: Error loading schema #{filename}: #{error.message}" if error
167
136
  end
168
137
 
169
138
  puts "Successfully loaded #{repo.schemas.size} schemas"
170
139
  repo
171
140
  rescue StandardError => e
172
- puts "Error loading schemas: #{e.message}"
173
- exit(1)
174
- end
175
- end
176
-
177
- # Validate all links against loaded schemas
178
- def validate_links(links_by_file, repo)
179
- unresolved_links = []
180
- total_links = links_by_file.values.sum(&:size)
181
-
182
- progress = create_progress_bar("Validating links", total_links)
183
-
184
- links_by_file.each do |file, links|
185
- validate_file_links(file, links, repo, progress, unresolved_links)
186
- end
187
-
188
- unresolved_links
189
- end
190
-
191
- # Validate links in a specific file
192
- def validate_file_links(file, links, repo, progress, unresolved_links)
193
- file_content = File.read(file)
194
- file_lines = file_content.lines
195
-
196
- links.each do |link|
197
- progress.increment
198
- line_idx = find_link_line(file_lines, link)
199
- next unless line_idx
200
-
201
- # Parse link (schema only, schema.element, or schema.element.path)
202
- parts = link.split(".")
203
-
204
- if parts.size == 1
205
- validate_schema_only_link(parts[0], repo, file, line_idx, link,
206
- unresolved_links)
207
- else
208
- validate_schema_element_link(parts, repo, file, line_idx, link,
209
- unresolved_links)
210
- end
211
- end
212
- rescue StandardError => e
213
- puts "Warning: Error processing file #{file}: #{e.message}"
214
- end
215
-
216
- # Find the line where a link appears in the file
217
- def find_link_line(file_lines, link)
218
- file_lines.each_with_index do |line, idx|
219
- # Match both with and without comma text
220
- if /<<express:#{Regexp.escape(link)}(?:,[^>]+)?>>/.match?(line)
221
- return idx
222
- end
223
- end
224
- nil
225
- end
226
-
227
- # Validate a link that only references a schema
228
- def validate_schema_only_link(schema_name, repo, file, line_idx, link,
229
- unresolved_links)
230
- # Check if schema exists
231
- schema = repo.schemas.find do |s|
232
- s.id.downcase == schema_name.downcase
233
- end
234
-
235
- if !schema
236
- unresolved_links << {
237
- file: file,
238
- line: line_idx + 1,
239
- link: link,
240
- reason: "Schema '#{schema_name}' not found",
241
- }
242
- end
243
- end
244
-
245
- # Validate a link with schema.element or deeper paths
246
- def validate_schema_element_link(parts, repo, file, line_idx, link,
247
- unresolved_links)
248
- schema_name = parts[0]
249
- element_name = parts[1]
250
-
251
- # Check if schema exists
252
- schema = repo.schemas.find do |s|
253
- s.id.downcase == schema_name.downcase
254
- end
255
-
256
- if !schema
257
- unresolved_links << {
258
- file: file,
259
- line: line_idx + 1,
260
- link: link,
261
- reason: "Schema '#{schema_name}' not found",
262
- }
263
- return
264
- end
265
-
266
- # Find the element in the schema
267
- element = find_schema_element(schema, element_name)
268
-
269
- if !element
270
- unresolved_links << {
271
- file: file,
272
- line: line_idx + 1,
273
- link: link,
274
- reason: "Element '#{element_name}' not found in schema '#{schema_name}'",
275
- }
276
- return
277
- end
278
-
279
- # If we have more than 2 parts, validate the deeper path
280
- if parts.size > 2
281
- validation_error = validate_deep_path(schema, element, parts[2..],
282
- file, line_idx, link)
283
- unresolved_links << validation_error if validation_error
284
- end
285
- end
286
-
287
- # Find an element in a schema by name
288
- def find_schema_element(schema, element_name)
289
- # Try to find element in various collections
290
- element_collections = [
291
- schema.entities,
292
- schema.types,
293
- schema.constants,
294
- schema.functions,
295
- schema.rules,
296
- schema.procedures,
297
- schema.subtype_constraints,
298
- ]
299
-
300
- element_collections.each do |collection|
301
- element = collection&.find do |e|
302
- e.id.downcase == element_name.downcase
303
- end
304
- return element if element
305
- end
306
-
307
- nil
308
- end
309
-
310
- # Validate deeper paths in a link (schema.element.path.subpath)
311
- def validate_deep_path(schema, element, path_parts, file, line_idx,
312
- full_link)
313
- current_element = element
314
- current_path = "#{schema.id}.#{element.id}"
315
-
316
- # Process each part of the path
317
- path_parts.each do |part|
318
- # The validation logic depends on the type of current element
319
- case current_element
320
- when Expressir::Express::Entity
321
- # For entities, check attributes
322
- attribute = current_element.attributes&.find do |a|
323
- a.id.downcase == part.downcase
324
- end
325
-
326
- unless attribute
327
- return {
328
- file: file,
329
- line: line_idx + 1,
330
- link: full_link,
331
- reason: "Attribute '#{part}' not found in entity '#{current_path}'",
332
- }
333
- end
334
-
335
- current_element = attribute
336
- current_path += ".#{part}"
337
-
338
- when Expressir::Express::Type
339
- # For types, validation depends on type kind
340
- if current_element.respond_to?(:base_type) && current_element.base_type
341
- # For derived types, find the base type
342
- base_type = find_base_type(schema, current_element.base_type)
343
-
344
- unless base_type
345
- return {
346
- file: file,
347
- line: line_idx + 1,
348
- link: full_link,
349
- reason: "Base type not found for '#{current_path}'",
350
- }
351
- end
352
-
353
- # Continue validation using base type
354
- current_element = base_type
355
-
356
- elsif current_element.respond_to?(:elements) && current_element.elements
357
- # For enum types, check enum values
358
- enum_value = current_element.elements.find do |e|
359
- e.id.downcase == part.downcase
360
- end
361
-
362
- unless enum_value
363
- return {
364
- file: file,
365
- line: line_idx + 1,
366
- link: full_link,
367
- reason: "Enumeration value '#{part}' not found in type '#{current_path}'",
368
- }
369
- end
370
-
371
- current_element = enum_value
372
- current_path += ".#{part}"
373
-
374
- else
375
- # For other types, we can't navigate deeper
376
- return {
377
- file: file,
378
- line: line_idx + 1,
379
- link: full_link,
380
- reason: "Cannot navigate deeper from type '#{current_path}'",
381
- }
382
- end
383
-
384
- else
385
- # For other element types, navigation is not supported
386
- return {
387
- file: file,
388
- line: line_idx + 1,
389
- link: full_link,
390
- reason: "Cannot navigate deeper from '#{current_path}'",
391
- }
392
- end
393
- end
394
-
395
- # If we've processed all parts without returning an error, path is valid
396
- nil
397
- end
398
-
399
- # Find a base type in a schema
400
- def find_base_type(schema, type_ref)
401
- # Skip built-in types
402
- return nil if %w[INTEGER REAL STRING BOOLEAN NUMBER BINARY
403
- LOGICAL].include?(type_ref.to_s.upcase)
404
-
405
- # Find the referenced type in the schema
406
- if type_ref.is_a?(String)
407
- find_schema_element(schema, type_ref)
408
- elsif type_ref.respond_to?(:id)
409
- # It's already a type object
410
- type_ref
411
- else
412
- nil
141
+ raise Suma::Error, "Error loading schemas: #{e.message}"
413
142
  end
414
143
  end
415
144
 
416
- # Write validation results to the output file
417
145
  def write_validation_results(output_file_path, output_file_rel,
418
146
  unresolved_links, links_by_file)
419
147
  total_links = links_by_file.values.sum(&:size)
420
148
 
421
- # Prepare results for output
422
149
  results = []
423
150
  results << "Validation complete. Checked #{total_links} links."
424
151
 
@@ -427,17 +154,15 @@ module Suma
427
154
  else
428
155
  results << "❌ Found #{unresolved_links.size} unresolved links:"
429
156
  unresolved_links.each do |issue|
430
- results << "#{issue[:file]}:#{issue[:line]} - <<express:#{issue[:link]}>> - #{issue[:reason]}"
157
+ results << "#{issue.file}:#{issue.line} - <<express:#{issue.link}>> - #{issue.reason}"
431
158
  end
432
159
  end
433
160
 
434
- # Write results to output file
435
161
  begin
436
162
  File.write(output_file_path, results.join("\n"))
437
163
  puts "Validation results written to #{output_file_rel}"
438
164
  rescue StandardError => e
439
165
  puts "Error writing to output file: #{e.message}"
440
- # Still print results to console as fallback
441
166
  puts results
442
167
  end
443
168
  end
data/lib/suma/cli.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "thor"
4
4
  require_relative "thor_ext"
5
5
  require_relative "cli/validate"
6
+ require_relative "cli/check_svg_quality"
6
7
  require "expressir"
7
8
  require "expressir/cli"
8
9
 
@@ -97,6 +98,44 @@ module Suma
97
98
  desc "validate SUBCOMMAND ...ARGS", "Validate express documents"
98
99
  subcommand "validate", Cli::Validate
99
100
 
101
+ desc "check_svg_quality [PATH]",
102
+ "Check SVG quality and sort by severity (critical files first)"
103
+ option :pattern, type: :string, default: Cli::CheckSvgQuality::DEFAULT_PATTERN,
104
+ desc: "Glob pattern for finding SVG files"
105
+ option :profile, type: :string,
106
+ default: Cli::CheckSvgQuality::DEFAULT_PROFILE,
107
+ desc: "Validation profile to use (metanorma, svg_1_2_rfc, etc.)"
108
+ option :format, type: :string, default: "terminal",
109
+ desc: "Output format: terminal, yaml, json"
110
+ option :output, type: :string, aliases: "-o",
111
+ desc: "Output file path"
112
+ option :min_errors, type: :numeric,
113
+ desc: "Minimum error count threshold"
114
+ option :limit, type: :numeric, default: nil,
115
+ desc: "Maximum number of files to show (default: unlimited)"
116
+ option :sort, type: :string, default: "errors",
117
+ desc: "Sort by: errors (most errors first) or quality (lowest scores first)"
118
+ option :progress, type: :boolean, default: false,
119
+ desc: "Show progress during processing"
120
+ option :summary_only, type: :boolean, default: false,
121
+ desc: "Show only summary"
122
+ def check_svg_quality(path = Cli::CheckSvgQuality::DATA_PATH)
123
+ require_relative "cli/check_svg_quality"
124
+
125
+ analyzer = Cli::CheckSvgQuality.new(
126
+ pattern: options[:pattern],
127
+ profile: options[:profile],
128
+ format: options[:format],
129
+ output: options[:output],
130
+ min_errors: options[:min_errors],
131
+ summary_only: options[:summary_only],
132
+ progress: options[:progress],
133
+ limit: options[:limit],
134
+ sort: options[:sort],
135
+ )
136
+ analyzer.run(path)
137
+ end
138
+
100
139
  desc "expressir SUBCOMMAND ...ARGS", "Expressir commands"
101
140
  subcommand "expressir", Expressir::Cli
102
141
 
@@ -8,7 +8,6 @@ module Suma
8
8
  attribute :schemas_only, Lutaml::Model::Type::Boolean
9
9
  attribute :entry, CollectionManifest, collection: true,
10
10
  initialize_empty: true
11
- # attribute :schema_source, Lutaml::Model::Type::String
12
11
  attr_accessor :schema_config
13
12
 
14
13
  yaml do
@@ -59,9 +58,9 @@ module Suma
59
58
  export_config
60
59
  end
61
60
 
62
- def lookup(attr_sym, match)
63
- results = entry.select { |e| e.send(attr_sym) == match }
64
- results << self if send(attr_sym) == match
61
+ def lookup_schemas_only
62
+ results = entry.select(&:schemas_only)
63
+ results << self if schemas_only
65
64
  results
66
65
  end
67
66