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
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Suma
4
+ module SvgQuality
5
+ # Simple report object wrapping svg_conform ValidationResult
6
+ class Report
7
+ attr_reader :file_path, :error_count, :errors
8
+
9
+ def initialize(file_path, validation_result)
10
+ @file_path = file_path
11
+ @validation_result = validation_result
12
+ @error_count = validation_result&.error_count || 0
13
+ @errors = validation_result&.errors || []
14
+ end
15
+
16
+ def valid?
17
+ @validation_result&.valid? || false
18
+ end
19
+
20
+ def quality_tier
21
+ QualityTiers.for_error_count(@error_count)
22
+ end
23
+
24
+ def quality_score
25
+ return 100 if @error_count.zero?
26
+ return 0 if @error_count >= 200
27
+
28
+ [100 - (@error_count * 0.5), 0].max.round
29
+ end
30
+
31
+ def errors_by_severity
32
+ {
33
+ critical: @errors.count { |e| e.requirement_id =~ /critical/i },
34
+ high: @errors.count { |e| e.requirement_id =~ /high/i },
35
+ medium: @errors.count { |e| e.requirement_id =~ /medium/i },
36
+ low: @errors.count { |e| e.requirement_id =~ /low/i },
37
+ }
38
+ end
39
+
40
+ def to_h
41
+ {
42
+ file_path: @file_path,
43
+ valid: valid?,
44
+ error_count: @error_count,
45
+ quality_score: quality_score,
46
+ quality_tier: quality_tier[:name],
47
+ errors_by_severity: errors_by_severity,
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "svg_conform"
4
+
5
+ module Suma
6
+ module SvgQuality
7
+ # Quality levels computed from error count
8
+ module QualityTiers
9
+ CRITICAL = { name: :critical, min_errors: 200, emoji: "💥" }.freeze
10
+ HIGH = { name: :high, min_errors: 100, emoji: "🔴" }.freeze
11
+ MEDIUM = { name: :medium, min_errors: 50, emoji: "⚠️" }.freeze
12
+ LOW = { name: :low, min_errors: 20, emoji: "🔶" }.freeze
13
+ MINOR = { name: :minor, min_errors: 0, emoji: "✅" }.freeze
14
+
15
+ ALL = [CRITICAL, HIGH, MEDIUM, LOW, MINOR].freeze
16
+
17
+ def self.for_error_count(count)
18
+ ALL.find { |tier| count >= tier[:min_errors] } || MINOR
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def validator(profile: :svg_1_2_rfc)
24
+ SvgConform::Validator.new
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require_relative "utils"
5
+ require "expressir"
6
+ require "glossarist"
7
+
8
+ module Suma
9
+ class TermExtractor
10
+ REDUNDANT_NOTE_REGEX =
11
+ %r{
12
+ ^An? # Starts with "A" or "An"
13
+ \s.*?\sis\sa\stype\sof # Text followed by "is a type of"
14
+ (\sa|\san)? # Optional " a" or " an"
15
+ \s\{\{[^\}]*\}\} # Text in double curly braces
16
+ \s*?\.?$ # Optional whitespace and period at the end
17
+ }x
18
+
19
+ def initialize(schema_manifest_file, output_path, language_code: "eng")
20
+ @schema_manifest_file = File.expand_path(schema_manifest_file)
21
+ @output_path = output_path
22
+ @language_code = language_code
23
+ end
24
+
25
+ def call
26
+ get_exp_files.map do |exp_file|
27
+ extract(exp_file)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def get_exp_files
34
+ config = Expressir::SchemaManifest.from_file(@schema_manifest_file)
35
+ paths = config.schemas.map(&:path)
36
+
37
+ if paths.empty?
38
+ raise Errno::ENOENT, "No EXPRESS files found in `#{@schema_manifest_file}`."
39
+ end
40
+
41
+ paths
42
+ end
43
+
44
+ def extract(exp_file)
45
+ exp_path_rel = Pathname.new(exp_file).relative_path_from(Pathname.getwd)
46
+ Utils.log "Building terms: #{exp_path_rel}"
47
+
48
+ repo = Expressir::Express::Parser.from_file(exp_file)
49
+ schema = repo.schemas.first
50
+
51
+ raise Error, "Schema must have an associated file" unless schema.file
52
+
53
+ collection = build_managed_concept_collection(schema)
54
+ output_data(collection)
55
+ collection
56
+ end
57
+
58
+ def output_data(collection)
59
+ FileUtils.mkdir_p(File.expand_path(@output_path)) unless File.exist?(@output_path)
60
+ Utils.log "Saving collection to files in: #{@output_path}"
61
+ collection.save_to_files(File.expand_path(@output_path))
62
+ end
63
+
64
+ def build_managed_concept_collection(schema)
65
+ source_ref = get_source_ref(schema)
66
+
67
+ Glossarist::ManagedConceptCollection.new.tap do |collection|
68
+ schema.entities.each do |entity|
69
+ localized_concept = build_localized_concept(
70
+ schema: schema,
71
+ entity: entity,
72
+ source_ref: source_ref,
73
+ )
74
+ localized_concept_id = get_localized_concept_identifier(schema, entity)
75
+
76
+ managed_data = Glossarist::ManagedConceptData.new.tap do |data|
77
+ data.id = get_entity_identifier(schema, entity)
78
+ data.localizations[@language_code] = localized_concept
79
+ data.localized_concepts = { @language_code => localized_concept_id }
80
+ end
81
+
82
+ managed_concept = Glossarist::ManagedConcept.new.tap do |concept|
83
+ concept.id = get_entity_identifier(schema, entity)
84
+ concept.uuid = concept.id
85
+ concept.data = managed_data
86
+ end
87
+
88
+ collection.store(managed_concept)
89
+ end
90
+ end
91
+ end
92
+
93
+ def build_localized_concept(schema:, entity:, source_ref:)
94
+ schema_domain = get_domain(schema)
95
+
96
+ localized_concept_data = Glossarist::ConceptData.new.tap do |data|
97
+ data.terms = get_entity_terms(entity)
98
+ data.definition = get_entity_definitions(entity, schema)
99
+ data.language_code = @language_code
100
+ data.domain = schema_domain
101
+ data.sources = [source_ref] if source_ref
102
+
103
+ notes = get_entity_notes(entity, schema_domain, data.definition)
104
+ data.notes = notes if notes && !notes.empty?
105
+ data.examples = []
106
+ end
107
+
108
+ Glossarist::LocalizedConcept.new.tap { |c| c.data = localized_concept_data }
109
+ end
110
+
111
+ def get_entity_identifier(schema, entity)
112
+ "#{schema.id}.#{entity.id}"
113
+ end
114
+
115
+ def get_localized_concept_identifier(schema, entity)
116
+ "#{schema.id}.#{entity.id}-#{@language_code}"
117
+ end
118
+
119
+ def get_source_ref(schema)
120
+ origin = Glossarist::Citation.new.tap do |citation|
121
+ citation.ref = "ISO 10303"
122
+ custom_locality = build_custom_locality(schema)
123
+ citation.custom_locality = custom_locality unless custom_locality.empty?
124
+ end
125
+
126
+ Glossarist::ConceptSource.new(type: "authoritative", origin: origin)
127
+ end
128
+
129
+ def build_custom_locality(schema)
130
+ localities = []
131
+ localities << Glossarist::CustomLocality.new(name: "schema", value: schema.id)
132
+
133
+ version_item = schema.version.items.detect { |i| i.name == "version" }
134
+ if version_item
135
+ localities << Glossarist::CustomLocality.new(name: "version", value: version_item.value)
136
+ end
137
+
138
+ localities
139
+ end
140
+
141
+ def get_domain(schema)
142
+ prefix = if schema.id.end_with?("_arm", "_mim")
143
+ "application module"
144
+ else
145
+ "resource"
146
+ end
147
+
148
+ "#{prefix}: #{schema.id}"
149
+ end
150
+
151
+ def get_entity_terms(entity)
152
+ [
153
+ Glossarist::Designation::Base.new(
154
+ designation: entity.id,
155
+ type: "expression",
156
+ normative_status: "preferred",
157
+ ),
158
+ ]
159
+ end
160
+
161
+ def get_entity_definitions(entity, schema)
162
+ schema_type = extract_file_type(schema.file)
163
+ schema_domain = get_domain(schema)
164
+
165
+ definition = generate_entity_definition(entity, schema_domain, schema_type)
166
+ [Glossarist::DetailedDefinition.new(content: definition)]
167
+ end
168
+
169
+ def get_entity_notes(entity, schema_domain, definitions)
170
+ notes = []
171
+
172
+ if entity.remarks && !entity.remarks.empty?
173
+ trimmed_def = trim_definition(entity.remarks)
174
+ if trimmed_def && !trimmed_def.empty?
175
+ notes << Glossarist::DetailedDefinition.new(
176
+ content: convert_express_xref(trimmed_def, schema_domain),
177
+ )
178
+ end
179
+ end
180
+
181
+ notes = only_keep_first_sentence(notes)
182
+ notes = remove_see_content(notes)
183
+ notes = remove_redundant_note(notes)
184
+ notes = remove_invalid_references(notes)
185
+ compare_with_definitions(notes, definitions)
186
+ end
187
+
188
+ def only_keep_first_sentence(notes)
189
+ notes.each do |note|
190
+ if note&.content && should_preserve_complete_structure?(note.content)
191
+ next
192
+ end
193
+
194
+ if note&.content
195
+ new_content = note.content
196
+ .split(".\n").first.strip
197
+ .split(". ").first.strip
198
+ note.content = new_content.end_with?(".") ? new_content : "#{new_content}."
199
+ end
200
+ end
201
+ end
202
+
203
+ def should_preserve_complete_structure?(content)
204
+ return false if content.nil? || content.empty?
205
+
206
+ lines = content.split("\n")
207
+ first_paragraph = lines.first&.strip
208
+
209
+ if first_paragraph&.end_with?(":") && lines.length > 1
210
+ if first_paragraph.count(".").positive?
211
+ return false
212
+ end
213
+
214
+ remaining_content = lines[1..].join("\n")
215
+ return starts_with_list?(remaining_content.strip)
216
+ end
217
+
218
+ false
219
+ end
220
+
221
+ def compare_with_definitions(notes, definitions)
222
+ if notes&.first&.content == definitions&.first&.content
223
+ return []
224
+ end
225
+
226
+ notes
227
+ end
228
+
229
+ def remove_invalid_references(notes)
230
+ notes.reject do |note|
231
+ note.content.include?("image::") ||
232
+ note.content.match?(/<<(.*?){1,999}>>/)
233
+ end
234
+ end
235
+
236
+ def remove_redundant_note(notes)
237
+ notes.reject do |note|
238
+ note.content.match?(REDUNDANT_NOTE_REGEX) &&
239
+ !note.content.include?("\n")
240
+ end
241
+ end
242
+
243
+ def remove_see_content(notes)
244
+ notes.each do |note|
245
+ note.content = note.content.gsub(/\s+\(see(.*?){1,999}\)/, "")
246
+ end
247
+ end
248
+
249
+ def extract_file_type(filename)
250
+ match = filename.match(/(arm|mim|bom)_annotated\.exp$/)
251
+ return "resource" unless match
252
+
253
+ {
254
+ "arm" => "module_arm",
255
+ "mim" => "module_mim",
256
+ "bom" => "business_object_model",
257
+ }[match.captures[0]] || "resource"
258
+ end
259
+
260
+ def starts_with_list?(content)
261
+ return false if content.nil? || content.empty?
262
+
263
+ content.match?(/^\s*[*\-+]\s+/) || content.match?(/^\s*\d+\.\s+/)
264
+ end
265
+
266
+ def trim_definition(definition)
267
+ return nil if definition.nil? || definition.empty?
268
+
269
+ definition_str = definition.is_a?(Array) ? definition.join("\n\n") : definition.to_s
270
+
271
+ return nil if definition_str.empty?
272
+
273
+ paragraphs = definition_str.split("\n\n")
274
+ first_paragraph = paragraphs.first
275
+
276
+ combined = if paragraphs.length == 1
277
+ apply_first_sentence_logic(first_paragraph)
278
+ elsif first_paragraph.end_with?(":") && paragraphs.length > 1 && starts_with_list?(paragraphs[1])
279
+ complete_list = extract_complete_list(paragraphs, 1)
280
+ "#{first_paragraph}\n\n#{complete_list}"
281
+ else
282
+ apply_first_sentence_logic(first_paragraph)
283
+ end
284
+
285
+ combined = "#{combined}\n"
286
+ combined.gsub!(/\n\/\/.*?\n/, "\n")
287
+ combined.strip!
288
+
289
+ express_reference_to_mention(combined)
290
+ end
291
+
292
+ def apply_first_sentence_logic(paragraph)
293
+ new_content = paragraph
294
+ .split(".\n").first.strip
295
+ .split(". ").first.strip
296
+
297
+ new_content.end_with?(".") ? new_content : "#{new_content}."
298
+ end
299
+
300
+ def extract_complete_list(paragraphs, start_index)
301
+ return paragraphs[start_index] if start_index >= paragraphs.length
302
+
303
+ combined = paragraphs[start_index].dup
304
+ current_index = start_index + 1
305
+ in_continuation_block = combined.include?("--") && !combined.match?(/--.*--/m)
306
+
307
+ while current_index < paragraphs.length
308
+ next_para = paragraphs[current_index]
309
+
310
+ if next_para.match?(/^--\s*$/) || next_para.end_with?("--")
311
+ in_continuation_block = !in_continuation_block
312
+ combined += "\n\n#{next_para}"
313
+ current_index += 1
314
+ next
315
+ end
316
+
317
+ if in_continuation_block
318
+ combined += "\n\n#{next_para}"
319
+ current_index += 1
320
+ next
321
+ end
322
+
323
+ if starts_with_list?(next_para) || is_list_continuation?(next_para)
324
+ combined += "\n\n#{next_para}"
325
+ current_index += 1
326
+ in_continuation_block = true if next_para.include?("--") && !next_para.match?(/--.*--/m)
327
+ else
328
+ break
329
+ end
330
+ end
331
+
332
+ combined
333
+ end
334
+
335
+ def is_list_continuation?(content)
336
+ return false if content.nil? || content.empty?
337
+
338
+ content.match?(/^\+\s*$/) ||
339
+ content.match?(/^--\s*$/) ||
340
+ content.match?(/^\s{2,}/) ||
341
+ content.start_with?("which", "where", "that")
342
+ end
343
+
344
+ def express_reference_to_mention(description)
345
+ description
346
+ .gsub(/<<express:([^,]+)>>/) do |_match|
347
+ "{{#{Regexp.last_match[1].split('.').last}}}"
348
+ end.gsub(/<<express:([^,]+),([^>]+)>>/) do |_match|
349
+ "{{#{Regexp.last_match[1].split('.').last}," \
350
+ "#{Regexp.last_match[2]}}}"
351
+ end
352
+ end
353
+
354
+ def entity_name_to_text(entity_id)
355
+ entity_id.downcase.gsub("_", " ")
356
+ end
357
+
358
+ def generate_entity_definition(entity, _domain, schema_type)
359
+ return "" if entity.nil?
360
+
361
+ entity_type = case schema_type
362
+ when "module_arm"
363
+ "{{application object}}"
364
+ when "module_mim"
365
+ "{{entity data type}}"
366
+ when "resource", "business_object_model"
367
+ "{{entity data type}}"
368
+ else
369
+ raise Error, "[suma] encountered unsupported schema_type"
370
+ end
371
+
372
+ if entity.subtype_of.empty?
373
+ "#{entity_type} " \
374
+ "that represents the " \
375
+ "#{entity_name_to_text(entity.id)} {{entity}}"
376
+ else
377
+ entity_subtypes = entity.subtype_of.map { |e| "{{#{e.id}}}" }
378
+
379
+ "#{entity_type} that is a type of " \
380
+ "#{entity_subtypes.join(' and ')} " \
381
+ "that represents the " \
382
+ "#{entity_name_to_text(entity.id)} {{entity}}"
383
+ end
384
+ end
385
+
386
+ def convert_express_xref(content, schema_domain)
387
+ content.gsub(/<<express:(.*),(.*)>>/) do
388
+ "{{<#{schema_domain}>" \
389
+ "#{Regexp.last_match(1).split('.').last},#{Regexp.last_match(2)}}}"
390
+ end
391
+ end
392
+ end
393
+ end
data/lib/suma/utils.rb CHANGED
@@ -3,8 +3,16 @@
3
3
  module Suma
4
4
  module Utils
5
5
  class << self
6
- def log(message)
7
- puts "[suma] #{message}"
6
+ attr_writer :output
7
+
8
+ def output
9
+ @output ||= $stderr
10
+ end
11
+
12
+ def log(message, level: :info)
13
+ return if level == :debug && !ENV["SUMA_DEBUG"]
14
+
15
+ output.puts "[suma] #{message}"
8
16
  end
9
17
  end
10
18
  end
data/lib/suma/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Suma
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
data/lib/suma.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "suma/processor"
8
8
 
9
9
  module Suma
10
10
  class Error < StandardError; end
11
-
12
- # Your code goes here...
11
+ class SchemaNotFoundError < Error; end
12
+ class CompilationError < Error; end
13
+ class EengineNotAvailableError < Error; end
13
14
  end
data/suma.gemspec CHANGED
@@ -34,12 +34,13 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
34
34
  spec.require_paths = ["lib"]
35
35
 
36
36
  spec.add_dependency "expressir", ">= 2.1.29", "~> 2.1"
37
- spec.add_dependency "glossarist", "~> 2.4.0"
38
- spec.add_dependency "lutaml-model", "~> 0.7"
37
+ spec.add_dependency "glossarist", "~> 2.4"
38
+ spec.add_dependency "lutaml-model", "~> 0.8.0"
39
39
  spec.add_dependency "metanorma", "~> 2.3"
40
40
  spec.add_dependency "plurimath"
41
41
  spec.add_dependency "ruby-progressbar"
42
42
  spec.add_dependency "rubyzip", "~> 2.3"
43
+ spec.add_dependency "svg_conform", "~> 0.1.0"
43
44
  spec.add_dependency "table_tennis"
44
45
  spec.add_dependency "thor", ">= 0.20"
45
46
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: suma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-13 00:00:00.000000000 Z
11
+ date: 2026-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expressir
@@ -36,28 +36,28 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 2.4.0
39
+ version: '2.4'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 2.4.0
46
+ version: '2.4'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: lutaml-model
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.7'
53
+ version: 0.8.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.7'
60
+ version: 0.8.0
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: metanorma
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +114,20 @@ dependencies:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
116
  version: '2.3'
117
+ - !ruby/object:Gem::Dependency
118
+ name: svg_conform
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 0.1.0
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.1.0
117
131
  - !ruby/object:Gem::Dependency
118
132
  name: table_tennis
119
133
  requirement: !ruby/object:Gem::Requirement
@@ -158,6 +172,7 @@ files:
158
172
  - ".rspec"
159
173
  - ".rubocop.yml"
160
174
  - ".rubocop_todo.yml"
175
+ - CLAUDE.md
161
176
  - CODE_OF_CONDUCT.md
162
177
  - Gemfile
163
178
  - README.adoc
@@ -168,6 +183,7 @@ files:
168
183
  - lib/suma.rb
169
184
  - lib/suma/cli.rb
170
185
  - lib/suma/cli/build.rb
186
+ - lib/suma/cli/check_svg_quality.rb
171
187
  - lib/suma/cli/compare.rb
172
188
  - lib/suma/cli/convert_jsdai.rb
173
189
  - lib/suma/cli/export.rb
@@ -181,18 +197,28 @@ files:
181
197
  - lib/suma/eengine/errors.rb
182
198
  - lib/suma/eengine/wrapper.rb
183
199
  - lib/suma/eengine_converter.rb
184
- - lib/suma/export_standalone_schema.rb
185
200
  - lib/suma/express_schema.rb
186
201
  - lib/suma/jsdai.rb
187
202
  - lib/suma/jsdai/figure.rb
188
203
  - lib/suma/jsdai/figure_image.rb
189
204
  - lib/suma/jsdai/figure_xml.rb
205
+ - lib/suma/link_validator.rb
190
206
  - lib/suma/processor.rb
191
207
  - lib/suma/schema_attachment.rb
192
208
  - lib/suma/schema_collection.rb
209
+ - lib/suma/schema_comparer.rb
193
210
  - lib/suma/schema_document.rb
194
211
  - lib/suma/schema_exporter.rb
212
+ - lib/suma/schema_index.rb
213
+ - lib/suma/schema_manifest_generator.rb
195
214
  - lib/suma/site_config.rb
215
+ - lib/suma/svg_quality.rb
216
+ - lib/suma/svg_quality/batch_report.rb
217
+ - lib/suma/svg_quality/formatters/json_formatter.rb
218
+ - lib/suma/svg_quality/formatters/terminal_formatter.rb
219
+ - lib/suma/svg_quality/formatters/yaml_formatter.rb
220
+ - lib/suma/svg_quality/report.rb
221
+ - lib/suma/term_extractor.rb
196
222
  - lib/suma/thor_ext.rb
197
223
  - lib/suma/utils.rb
198
224
  - lib/suma/version.rb
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Suma
4
- # Simple schema class for standalone EXPRESS files
5
- # Used when exporting individual .exp files that are not part of a manifest
6
- class ExportStandaloneSchema
7
- attr_accessor :id, :path
8
-
9
- def initialize(id:, path:)
10
- @id = id
11
- @path = path
12
- end
13
- end
14
- end