suma 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,15 +6,6 @@ require "glossarist"
6
6
 
7
7
  module Suma
8
8
  class TermExtractor
9
- REDUNDANT_NOTE_REGEX =
10
- %r{
11
- ^An? # Starts with "A" or "An"
12
- \s.*?\sis\sa\stype\sof # Text followed by "is a type of"
13
- (\sa|\san)? # Optional " a" or " an"
14
- \s\{\{[^\}]*\}\} # Text in double curly braces
15
- \s*?\.?$ # Optional whitespace and period at the end
16
- }x
17
-
18
9
  def initialize(schema_manifest_file, output_path, urn:,
19
10
  language_code: "eng")
20
11
  @schema_manifest_file = File.expand_path(schema_manifest_file)
@@ -64,7 +55,9 @@ module Suma
64
55
 
65
56
  raise Error, "Schema must have an associated file" unless schema.file
66
57
 
67
- collection = build_managed_concept_collection(schema)
58
+ classification = TermClassification.for_schema(id: schema.id,
59
+ path: schema.file)
60
+ collection = build_managed_concept_collection(schema, classification)
68
61
  output_data(collection)
69
62
  collection
70
63
  end
@@ -86,7 +79,7 @@ module Suma
86
79
  end
87
80
  end
88
81
 
89
- def build_managed_concept_collection(schema)
82
+ def build_managed_concept_collection(schema, classification)
90
83
  source_ref = get_source_ref(schema)
91
84
  section_ref = get_section_ref(schema)
92
85
 
@@ -102,6 +95,7 @@ module Suma
102
95
  entity: entity,
103
96
  source_ref: source_ref,
104
97
  uuid: localized_concept_id,
98
+ classification: classification,
105
99
  )
106
100
 
107
101
  managed_data = Glossarist::V3::ManagedConceptData.new.tap do |data|
@@ -126,17 +120,22 @@ module Suma
126
120
  end
127
121
  end
128
122
 
129
- def build_localized_concept(schema:, entity:, source_ref:, uuid:)
130
- schema_domain = get_domain(schema)
123
+ def build_localized_concept(schema:, entity:, source_ref:, uuid:,
124
+ classification:)
125
+ schema_domain = classification.domain_for(schema.id)
131
126
 
132
127
  localized_concept_data = Glossarist::V3::ConceptData.new.tap do |data|
133
128
  data.terms = get_entity_terms(entity)
134
- data.definition = get_entity_definitions(entity, schema)
129
+ data.definition = get_entity_definitions(entity, schema, classification)
135
130
  data.language_code = @language_code
136
131
  data.domain = schema_domain
137
132
  data.sources = [source_ref] if source_ref
138
133
 
139
- notes = get_entity_notes(entity, schema_domain, data.definition)
134
+ notes = NoteProcessor.call(
135
+ entity.remarks,
136
+ definitions: data.definition,
137
+ xref_to_mention: method(:xref_to_mention),
138
+ )
140
139
  data.notes = notes if notes && !notes.empty?
141
140
  data.examples = []
142
141
  end
@@ -162,6 +161,10 @@ module Suma
162
161
  "{{#{urn},#{display}}}"
163
162
  end
164
163
 
164
+ def xref_to_mention(full_ref, display)
165
+ urn_mention(express_entity_urn(full_ref), display)
166
+ end
167
+
165
168
  def get_section_ref(schema)
166
169
  return nil unless @urn
167
170
 
@@ -200,16 +203,6 @@ module Suma
200
203
  localities
201
204
  end
202
205
 
203
- def get_domain(schema)
204
- prefix = if schema.id.end_with?("_arm", "_mim")
205
- "application module"
206
- else
207
- "resource"
208
- end
209
-
210
- "#{prefix}: #{schema.id}"
211
- end
212
-
213
206
  def get_entity_terms(entity)
214
207
  [
215
208
  Glossarist::Designation::Base.new(
@@ -220,222 +213,22 @@ module Suma
220
213
  ]
221
214
  end
222
215
 
223
- def get_entity_definitions(entity, schema)
224
- schema_type = extract_file_type(schema.file)
225
- get_domain(schema)
226
-
227
- definition = generate_entity_definition(entity, schema, schema_type)
216
+ def get_entity_definitions(entity, schema, classification)
217
+ definition = generate_entity_definition(entity, schema, classification)
228
218
  [Glossarist::V3::DetailedDefinition.new(content: definition)]
229
219
  end
230
220
 
231
- def get_entity_notes(entity, schema_domain, definitions)
232
- notes = []
233
-
234
- if entity.remarks && !entity.remarks.empty?
235
- trimmed_def = trim_definition(entity.remarks)
236
- if trimmed_def && !trimmed_def.empty?
237
- notes << Glossarist::V3::DetailedDefinition.new(
238
- content: convert_express_xref(trimmed_def, schema_domain),
239
- )
240
- end
241
- end
242
-
243
- notes = only_keep_first_sentence(notes)
244
- notes = remove_see_content(notes)
245
- notes = remove_redundant_note(notes)
246
- notes = remove_invalid_references(notes)
247
- compare_with_definitions(notes, definitions)
248
- end
249
-
250
- def only_keep_first_sentence(notes)
251
- notes.each do |note|
252
- if note&.content && should_preserve_complete_structure?(note.content)
253
- next
254
- end
255
-
256
- if note&.content
257
- new_content = note.content
258
- .split(".\n").first.strip
259
- .split(". ").first.strip
260
- note.content = new_content.end_with?(".") ? new_content : "#{new_content}."
261
- end
262
- end
263
- end
264
-
265
- def should_preserve_complete_structure?(content)
266
- return false if content.nil? || content.empty?
267
-
268
- lines = content.split("\n")
269
- first_paragraph = lines.first&.strip
270
-
271
- if first_paragraph&.end_with?(":") && lines.length > 1
272
- if first_paragraph.count(".").positive?
273
- return false
274
- end
275
-
276
- remaining_content = lines[1..].join("\n")
277
- return starts_with_list?(remaining_content.strip)
278
- end
279
-
280
- false
281
- end
282
-
283
- def compare_with_definitions(notes, definitions)
284
- if notes&.first&.content == definitions&.first&.content
285
- return []
286
- end
287
-
288
- notes
289
- end
290
-
291
- def remove_invalid_references(notes)
292
- notes.reject do |note|
293
- note.content.include?("image::") ||
294
- note.content.match?(/<<(.*?){1,999}>>/)
295
- end
296
- end
297
-
298
- def remove_redundant_note(notes)
299
- notes.reject do |note|
300
- note.content.match?(REDUNDANT_NOTE_REGEX) &&
301
- !note.content.include?("\n")
302
- end
303
- end
304
-
305
- def remove_see_content(notes)
306
- notes.each do |note|
307
- note.content = note.content.gsub(/\s+\(see(.*?){1,999}\)/, "")
308
- end
309
- end
310
-
311
- def extract_file_type(filename)
312
- match = filename.match(/(arm|mim|bom)_annotated\.exp$/)
313
- return "resource" unless match
314
-
315
- {
316
- "arm" => "module_arm",
317
- "mim" => "module_mim",
318
- "bom" => "business_object_model",
319
- }[match.captures[0]] || "resource"
320
- end
321
-
322
- def starts_with_list?(content)
323
- return false if content.nil? || content.empty?
324
-
325
- content.match?(/^\s*[*\-+]\s+/) || content.match?(/^\s*\d+\.\s+/)
326
- end
327
-
328
- def trim_definition(definition)
329
- return nil if definition.nil? || definition.empty?
330
-
331
- definition_str = definition.is_a?(Array) ? definition.join("\n\n") : definition.to_s
332
-
333
- return nil if definition_str.empty?
334
-
335
- paragraphs = definition_str.split("\n\n")
336
- first_paragraph = paragraphs.first
337
-
338
- combined = if paragraphs.length == 1
339
- apply_first_sentence_logic(first_paragraph)
340
- elsif first_paragraph.end_with?(":") && paragraphs.length > 1 && starts_with_list?(paragraphs[1])
341
- complete_list = extract_complete_list(paragraphs, 1)
342
- "#{first_paragraph}\n\n#{complete_list}"
343
- else
344
- apply_first_sentence_logic(first_paragraph)
345
- end
346
-
347
- combined = "#{combined}\n"
348
- combined.gsub!(/\n\/\/.*?\n/, "\n")
349
- combined.strip!
350
-
351
- express_reference_to_mention(combined)
352
- end
353
-
354
- def apply_first_sentence_logic(paragraph)
355
- new_content = paragraph
356
- .split(".\n").first.strip
357
- .split(". ").first.strip
358
-
359
- new_content.end_with?(".") ? new_content : "#{new_content}."
360
- end
361
-
362
- def extract_complete_list(paragraphs, start_index)
363
- return paragraphs[start_index] if start_index >= paragraphs.length
364
-
365
- combined = paragraphs[start_index].dup
366
- current_index = start_index + 1
367
- in_continuation_block = combined.include?("--") && !combined.match?(/--.*--/m)
368
-
369
- while current_index < paragraphs.length
370
- next_para = paragraphs[current_index]
371
-
372
- if next_para.match?(/^--\s*$/) || next_para.end_with?("--")
373
- in_continuation_block = !in_continuation_block
374
- combined += "\n\n#{next_para}"
375
- current_index += 1
376
- next
377
- end
378
-
379
- if in_continuation_block
380
- combined += "\n\n#{next_para}"
381
- current_index += 1
382
- next
383
- end
384
-
385
- if starts_with_list?(next_para) || is_list_continuation?(next_para)
386
- combined += "\n\n#{next_para}"
387
- current_index += 1
388
- in_continuation_block = true if next_para.include?("--") && !next_para.match?(/--.*--/m)
389
- else
390
- break
391
- end
392
- end
393
-
394
- combined
395
- end
396
-
397
- def is_list_continuation?(content)
398
- return false if content.nil? || content.empty?
399
-
400
- content.match?(/^\+\s*$/) ||
401
- content.match?(/^--\s*$/) ||
402
- content.match?(/^\s{2,}/) ||
403
- content.start_with?("which", "where", "that")
404
- end
405
-
406
- def express_reference_to_mention(description)
407
- description
408
- .gsub(/<<express:([\w.]+)>>/) do |_match|
409
- full_ref = Regexp.last_match[1]
410
- entity_id = full_ref.split(".").last
411
- urn_mention(express_entity_urn(full_ref), entity_id)
412
- end.gsub(/<<express:([\w.]+),([\w. ][\w. ]*)>>/) do |_match|
413
- full_ref = Regexp.last_match[1]
414
- display = Regexp.last_match(2)
415
- urn_mention(express_entity_urn(full_ref), display)
416
- end
417
- end
418
-
419
221
  def entity_name_to_text(entity_id)
420
222
  entity_id.downcase.gsub("_", " ")
421
223
  end
422
224
 
423
- def generate_entity_definition(entity, schema, schema_type)
225
+ def generate_entity_definition(entity, schema, classification)
424
226
  return "" if entity.nil?
425
227
 
426
- entity_type = case schema_type
427
- when "module_arm"
428
- urn_mention(term_urn("general.application_object"),
429
- "application object")
430
- when "module_mim"
431
- urn_mention(term_urn("express-language.entity_data_type"),
432
- "entity data type")
433
- when "resource", "business_object_model"
434
- urn_mention(term_urn("express-language.entity_data_type"),
435
- "entity data type")
436
- else
437
- raise Error, "[suma] encountered unsupported schema_type"
438
- end
228
+ entity_type = urn_mention(
229
+ term_urn(classification.entity_term),
230
+ classification.entity_display,
231
+ )
439
232
 
440
233
  entity_ref = urn_mention(term_urn("express-language.entity"), "entity")
441
234
 
@@ -454,13 +247,5 @@ module Suma
454
247
  "#{entity_name_to_text(entity.id)} #{entity_ref}"
455
248
  end
456
249
  end
457
-
458
- def convert_express_xref(content, _schema_domain)
459
- content.gsub(/<<express:([\w.]+),([\w. ][\w. ]*)>>/) do
460
- full_ref = Regexp.last_match(1)
461
- display = Regexp.last_match(2)
462
- urn_mention(express_entity_urn(full_ref), display)
463
- end
464
- end
465
250
  end
466
251
  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.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/suma.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "expressir"
4
4
  require "lutaml/model"
5
+ require "fileutils"
5
6
 
6
7
  module Suma
7
8
  autoload :VERSION, "suma/version"
@@ -10,9 +11,12 @@ module Suma
10
11
  autoload :CollectionConfig, "suma/collection_config"
11
12
  autoload :CollectionManifest, "suma/collection_manifest"
12
13
  autoload :EengineConverter, "suma/eengine_converter"
14
+ autoload :ExpressReformatter, "suma/express_reformatter"
13
15
  autoload :ExpressSchema, "suma/express_schema"
16
+ autoload :LinkValidation, "suma/link_validation"
14
17
  autoload :LinkValidator, "suma/link_validator"
15
18
  autoload :ManifestTraverser, "suma/manifest_traverser"
19
+ autoload :NoteProcessor, "suma/note_processor"
16
20
  autoload :RegisterManifestGenerator, "suma/register_manifest_generator"
17
21
  autoload :SchemaCategory, "suma/schema_category"
18
22
  autoload :SchemaCollection, "suma/schema_collection"
@@ -26,6 +30,7 @@ module Suma
26
30
  autoload :SchemaTemplate, "suma/schema_template"
27
31
  autoload :SiteConfig, "suma/site_config"
28
32
  autoload :SvgQuality, "suma/svg_quality"
33
+ autoload :TermClassification, "suma/term_classification"
29
34
  autoload :TermExtractor, "suma/term_extractor"
30
35
  autoload :ThorExt, "suma/thor_ext"
31
36
  autoload :Urn, "suma/urn"
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.3.0
4
+ version: 0.5.0
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-07-03 00:00:00.000000000 Z
11
+ date: 2026-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expressir
@@ -206,13 +206,17 @@ files:
206
206
  - lib/suma/eengine/errors.rb
207
207
  - lib/suma/eengine/wrapper.rb
208
208
  - lib/suma/eengine_converter.rb
209
+ - lib/suma/express_reformatter.rb
209
210
  - lib/suma/express_schema.rb
210
211
  - lib/suma/jsdai.rb
211
212
  - lib/suma/jsdai/figure.rb
212
213
  - lib/suma/jsdai/figure_image.rb
213
214
  - lib/suma/jsdai/figure_xml.rb
215
+ - lib/suma/link_validation.rb
214
216
  - lib/suma/link_validator.rb
217
+ - lib/suma/link_validator/step.rb
215
218
  - lib/suma/manifest_traverser.rb
219
+ - lib/suma/note_processor.rb
216
220
  - lib/suma/processor.rb
217
221
  - lib/suma/register_manifest_generator.rb
218
222
  - lib/suma/schema_category.rb
@@ -235,6 +239,8 @@ files:
235
239
  - lib/suma/svg_quality/formatters/terminal_formatter.rb
236
240
  - lib/suma/svg_quality/formatters/yaml_formatter.rb
237
241
  - lib/suma/svg_quality/report.rb
242
+ - lib/suma/svg_quality/scanner.rb
243
+ - lib/suma/term_classification.rb
238
244
  - lib/suma/term_extractor.rb
239
245
  - lib/suma/thor_ext.rb
240
246
  - lib/suma/urn.rb