suma 0.2.5 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +3 -0
- data/.github/workflows/release.yml +5 -1
- data/.gitignore +10 -1
- data/.rubocop_todo.yml +237 -28
- data/CLAUDE.md +102 -0
- data/Gemfile +3 -1
- data/README.adoc +188 -1
- data/exe/suma +1 -1
- data/lib/suma/cli/build.rb +2 -8
- data/lib/suma/cli/check_svg_quality.rb +172 -0
- data/lib/suma/cli/compare.rb +6 -158
- data/lib/suma/cli/convert_jsdai.rb +0 -2
- data/lib/suma/cli/core.rb +119 -0
- data/lib/suma/cli/export.rb +1 -10
- data/lib/suma/cli/extract_terms.rb +10 -654
- data/lib/suma/cli/generate_register.rb +34 -0
- data/lib/suma/cli/generate_schemas.rb +8 -124
- data/lib/suma/cli/reformat.rb +0 -1
- data/lib/suma/cli/validate.rb +0 -2
- data/lib/suma/cli/validate_links.rb +14 -291
- data/lib/suma/cli.rb +12 -102
- data/lib/suma/collection_config.rb +0 -2
- data/lib/suma/collection_manifest.rb +7 -111
- data/lib/suma/eengine/wrapper.rb +0 -1
- data/lib/suma/eengine.rb +8 -0
- data/lib/suma/express_schema.rb +43 -31
- data/lib/suma/jsdai/figure.rb +0 -3
- data/lib/suma/jsdai/figure_xml.rb +12 -9
- data/lib/suma/jsdai.rb +5 -8
- data/lib/suma/link_validator.rb +211 -0
- data/lib/suma/manifest_traverser.rb +92 -0
- data/lib/suma/processor.rb +76 -105
- data/lib/suma/register_manifest_generator.rb +163 -0
- data/lib/suma/schema_category.rb +83 -0
- data/lib/suma/schema_collection.rb +28 -63
- data/lib/suma/schema_comparer.rb +117 -0
- data/lib/suma/schema_compiler.rb +86 -0
- data/lib/suma/schema_discovery.rb +75 -0
- data/lib/suma/schema_exporter.rb +7 -35
- data/lib/suma/schema_index.rb +53 -0
- data/lib/suma/schema_manifest_generator.rb +113 -0
- data/lib/suma/schema_naming.rb +111 -0
- data/lib/suma/schema_template/document.rb +141 -0
- data/lib/suma/schema_template/plain.rb +46 -0
- data/lib/suma/schema_template.rb +19 -0
- data/lib/suma/svg_quality/batch_report.rb +78 -0
- data/lib/suma/svg_quality/formatters/json_formatter.rb +30 -0
- data/lib/suma/svg_quality/formatters/terminal_formatter.rb +168 -0
- data/lib/suma/svg_quality/formatters/yaml_formatter.rb +32 -0
- data/lib/suma/svg_quality/formatters.rb +12 -0
- data/lib/suma/svg_quality/report.rb +52 -0
- data/lib/suma/svg_quality.rb +30 -0
- data/lib/suma/term_extractor.rb +466 -0
- data/lib/suma/urn.rb +61 -0
- data/lib/suma/utils.rb +10 -2
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +34 -5
- data/suma.gemspec +3 -2
- metadata +53 -9
- data/lib/suma/export_standalone_schema.rb +0 -14
- data/lib/suma/schema_attachment.rb +0 -130
- data/lib/suma/schema_document.rb +0 -132
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "expressir"
|
|
5
|
+
require "glossarist"
|
|
6
|
+
|
|
7
|
+
module Suma
|
|
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
|
+
def initialize(schema_manifest_file, output_path, urn:,
|
|
19
|
+
language_code: "eng")
|
|
20
|
+
@schema_manifest_file = File.expand_path(schema_manifest_file)
|
|
21
|
+
@output_path = output_path
|
|
22
|
+
@language_code = language_code
|
|
23
|
+
@urn = Suma::Urn.new(urn)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call
|
|
27
|
+
validate_inputs
|
|
28
|
+
get_exp_files.map do |exp_file|
|
|
29
|
+
extract(exp_file)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_inputs
|
|
36
|
+
unless File.exist?(@schema_manifest_file)
|
|
37
|
+
raise Errno::ENOENT, "Specified SCHEMA_MANIFEST_FILE " \
|
|
38
|
+
"`#{@schema_manifest_file}` not found."
|
|
39
|
+
end
|
|
40
|
+
unless File.file?(@schema_manifest_file)
|
|
41
|
+
raise Errno::ENOENT, "Specified SCHEMA_MANIFEST_FILE " \
|
|
42
|
+
"`#{@schema_manifest_file}` is not a file."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def get_exp_files
|
|
47
|
+
config = Expressir::SchemaManifest.from_file(@schema_manifest_file)
|
|
48
|
+
paths = config.schemas.map(&:path)
|
|
49
|
+
|
|
50
|
+
if paths.empty?
|
|
51
|
+
raise Errno::ENOENT,
|
|
52
|
+
"No EXPRESS files found in `#{@schema_manifest_file}`."
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
paths
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def extract(exp_file)
|
|
59
|
+
exp_path_rel = Pathname.new(exp_file).relative_path_from(Pathname.getwd)
|
|
60
|
+
Utils.log "Building terms: #{exp_path_rel}"
|
|
61
|
+
|
|
62
|
+
repo = Expressir::Express::Parser.from_file(exp_file)
|
|
63
|
+
schema = repo.schemas.first
|
|
64
|
+
|
|
65
|
+
raise Error, "Schema must have an associated file" unless schema.file
|
|
66
|
+
|
|
67
|
+
collection = build_managed_concept_collection(schema)
|
|
68
|
+
output_data(collection)
|
|
69
|
+
collection
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def output_data(collection)
|
|
73
|
+
output_dir = File.expand_path(@output_path)
|
|
74
|
+
FileUtils.mkdir_p(output_dir)
|
|
75
|
+
Utils.log "Saving collection to files in: #{@output_path}"
|
|
76
|
+
|
|
77
|
+
collection.each do |concept|
|
|
78
|
+
doc = Glossarist::V3::ConceptDocument.from_managed_concept(concept)
|
|
79
|
+
doc.localizations = concept.data.localizations.keys.map do |lang|
|
|
80
|
+
concept.localization(lang)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
filename = "#{concept.uuid.gsub(/[^\w.-]/, '_')}.yaml"
|
|
84
|
+
File.write(File.join(output_dir, filename), doc.to_yamls,
|
|
85
|
+
encoding: "utf-8")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_managed_concept_collection(schema)
|
|
90
|
+
source_ref = get_source_ref(schema)
|
|
91
|
+
section_ref = get_section_ref(schema)
|
|
92
|
+
|
|
93
|
+
Glossarist::ManagedConceptCollection.new.tap do |collection|
|
|
94
|
+
schema.entities.each do |entity|
|
|
95
|
+
localized_concept_id = Glossarist::Utilities::UUID.uuid_v5(
|
|
96
|
+
Glossarist::Utilities::UUID::OID_NAMESPACE,
|
|
97
|
+
"#{schema.id}.#{entity.id}-#{@language_code}",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
localized_concept = build_localized_concept(
|
|
101
|
+
schema: schema,
|
|
102
|
+
entity: entity,
|
|
103
|
+
source_ref: source_ref,
|
|
104
|
+
uuid: localized_concept_id,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
managed_data = Glossarist::V3::ManagedConceptData.new.tap do |data|
|
|
108
|
+
data.id = "#{schema.id}.#{entity.id}"
|
|
109
|
+
data.localizations.store(@language_code, localized_concept)
|
|
110
|
+
data.localized_concepts = { @language_code => localized_concept_id }
|
|
111
|
+
data.domains = [section_ref] if section_ref
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
managed_concept = Glossarist::V3::ManagedConcept.new.tap do |concept|
|
|
115
|
+
concept.id = managed_data.id
|
|
116
|
+
concept.uuid = Glossarist::Utilities::UUID.uuid_v5(
|
|
117
|
+
Glossarist::Utilities::UUID::OID_NAMESPACE,
|
|
118
|
+
managed_data.id,
|
|
119
|
+
)
|
|
120
|
+
concept.data = managed_data
|
|
121
|
+
concept.schema_version = "v3"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
collection.store(managed_concept)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_localized_concept(schema:, entity:, source_ref:, uuid:)
|
|
130
|
+
schema_domain = get_domain(schema)
|
|
131
|
+
|
|
132
|
+
localized_concept_data = Glossarist::V3::ConceptData.new.tap do |data|
|
|
133
|
+
data.terms = get_entity_terms(entity)
|
|
134
|
+
data.definition = get_entity_definitions(entity, schema)
|
|
135
|
+
data.language_code = @language_code
|
|
136
|
+
data.domain = schema_domain
|
|
137
|
+
data.sources = [source_ref] if source_ref
|
|
138
|
+
|
|
139
|
+
notes = get_entity_notes(entity, schema_domain, data.definition)
|
|
140
|
+
data.notes = notes if notes && !notes.empty?
|
|
141
|
+
data.examples = []
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Glossarist::V3::LocalizedConcept.new(
|
|
145
|
+
data: localized_concept_data, uuid: uuid,
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def schema_urn(schema)
|
|
150
|
+
@urn.for_schema(schema.id)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def term_urn(concept_identifier)
|
|
154
|
+
@urn.for_term(concept_identifier)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def express_entity_urn(full_ref)
|
|
158
|
+
@urn.for_entity(full_ref)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def urn_mention(urn, display)
|
|
162
|
+
"{{#{urn},#{display}}}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def get_section_ref(schema)
|
|
166
|
+
return nil unless @urn
|
|
167
|
+
|
|
168
|
+
Glossarist::ConceptReference.new(
|
|
169
|
+
concept_id: "section-#{schema.id}",
|
|
170
|
+
source: schema_urn(schema),
|
|
171
|
+
ref_type: "section",
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def get_source_ref(schema)
|
|
176
|
+
ref = Glossarist::Citation::Ref.new
|
|
177
|
+
ref.source = schema_urn(schema)
|
|
178
|
+
|
|
179
|
+
build_custom_locality(schema).each do |cl|
|
|
180
|
+
case cl.name
|
|
181
|
+
when "version" then ref.version = cl.value
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
origin = Glossarist::V3::Citation.new
|
|
186
|
+
origin.ref = ref
|
|
187
|
+
Glossarist::V3::ConceptSource.new(id: schema.id, type: "authoritative",
|
|
188
|
+
origin: origin)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def build_custom_locality(schema)
|
|
192
|
+
localities = []
|
|
193
|
+
|
|
194
|
+
version_item = schema.version.items.detect { |i| i.name == "version" }
|
|
195
|
+
if version_item
|
|
196
|
+
localities << Glossarist::CustomLocality.new(name: "version",
|
|
197
|
+
value: version_item.value)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
localities
|
|
201
|
+
end
|
|
202
|
+
|
|
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
|
+
def get_entity_terms(entity)
|
|
214
|
+
[
|
|
215
|
+
Glossarist::Designation::Base.new(
|
|
216
|
+
designation: entity.id,
|
|
217
|
+
type: "expression",
|
|
218
|
+
normative_status: "preferred",
|
|
219
|
+
),
|
|
220
|
+
]
|
|
221
|
+
end
|
|
222
|
+
|
|
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)
|
|
228
|
+
[Glossarist::V3::DetailedDefinition.new(content: definition)]
|
|
229
|
+
end
|
|
230
|
+
|
|
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
|
+
def entity_name_to_text(entity_id)
|
|
420
|
+
entity_id.downcase.gsub("_", " ")
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def generate_entity_definition(entity, schema, schema_type)
|
|
424
|
+
return "" if entity.nil?
|
|
425
|
+
|
|
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
|
|
439
|
+
|
|
440
|
+
entity_ref = urn_mention(term_urn("express-language.entity"), "entity")
|
|
441
|
+
|
|
442
|
+
if entity.subtype_of.empty?
|
|
443
|
+
"#{entity_type} " \
|
|
444
|
+
"that represents the " \
|
|
445
|
+
"#{entity_name_to_text(entity.id)} #{entity_ref}"
|
|
446
|
+
else
|
|
447
|
+
entity_subtypes = entity.subtype_of.map do |e|
|
|
448
|
+
urn_mention(express_entity_urn("#{schema.id}.#{e.id}"), e.id)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
"#{entity_type} that is a type of " \
|
|
452
|
+
"#{entity_subtypes.join(' and ')} " \
|
|
453
|
+
"that represents the " \
|
|
454
|
+
"#{entity_name_to_text(entity.id)} #{entity_ref}"
|
|
455
|
+
end
|
|
456
|
+
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
|
+
end
|
|
466
|
+
end
|
data/lib/suma/urn.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Value object encapsulating URN semantics for ISO 10303 datasets.
|
|
5
|
+
#
|
|
6
|
+
# Normalises a URN prefix (stripping any trailing wildcard `:*`) and
|
|
7
|
+
# provides factory methods for composing leaf URNs:
|
|
8
|
+
#
|
|
9
|
+
# - `#for_schema(id)` → `<base>:tech:<id>`
|
|
10
|
+
# - `#for_term(id)` → `<base>:term:<id>`
|
|
11
|
+
# - `#for_entity(ref)` → `<base>:tech:<ref>`
|
|
12
|
+
#
|
|
13
|
+
# The wildcard form is preserved via `#wildcard` and `#aliases` so callers
|
|
14
|
+
# can populate `urnAliases` in register.yaml without re-implementing the
|
|
15
|
+
# normalisation logic.
|
|
16
|
+
class Urn
|
|
17
|
+
WILDCARD_SUFFIX = ":*"
|
|
18
|
+
TECH_COMPONENT = "tech"
|
|
19
|
+
TERM_COMPONENT = "term"
|
|
20
|
+
|
|
21
|
+
attr_reader :base
|
|
22
|
+
|
|
23
|
+
def initialize(raw)
|
|
24
|
+
@base = strip_wildcard(raw.to_s)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
base
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def wildcard
|
|
32
|
+
"#{base}#{WILDCARD_SUFFIX}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def aliases
|
|
36
|
+
[wildcard]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def for_schema(schema_id)
|
|
40
|
+
compose(TECH_COMPONENT, schema_id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def for_term(concept_identifier)
|
|
44
|
+
compose(TERM_COMPONENT, concept_identifier)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def for_entity(full_ref)
|
|
48
|
+
compose(TECH_COMPONENT, full_ref)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def strip_wildcard(value)
|
|
54
|
+
value.sub(/#{Regexp.escape(WILDCARD_SUFFIX)}\z/o, "")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def compose(component, identifier)
|
|
58
|
+
"#{base}:#{component}:#{identifier}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/suma/utils.rb
CHANGED
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
module Suma
|
|
4
4
|
module Utils
|
|
5
5
|
class << self
|
|
6
|
-
|
|
7
|
-
|
|
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
data/lib/suma.rb
CHANGED
|
@@ -3,11 +3,40 @@
|
|
|
3
3
|
require "expressir"
|
|
4
4
|
require "lutaml/model"
|
|
5
5
|
|
|
6
|
-
require_relative "suma/version"
|
|
7
|
-
require_relative "suma/processor"
|
|
8
|
-
|
|
9
6
|
module Suma
|
|
10
|
-
|
|
7
|
+
autoload :VERSION, "suma/version"
|
|
8
|
+
|
|
9
|
+
autoload :Processor, "suma/processor"
|
|
10
|
+
autoload :CollectionConfig, "suma/collection_config"
|
|
11
|
+
autoload :CollectionManifest, "suma/collection_manifest"
|
|
12
|
+
autoload :EengineConverter, "suma/eengine_converter"
|
|
13
|
+
autoload :ExpressSchema, "suma/express_schema"
|
|
14
|
+
autoload :LinkValidator, "suma/link_validator"
|
|
15
|
+
autoload :ManifestTraverser, "suma/manifest_traverser"
|
|
16
|
+
autoload :RegisterManifestGenerator, "suma/register_manifest_generator"
|
|
17
|
+
autoload :SchemaCategory, "suma/schema_category"
|
|
18
|
+
autoload :SchemaCollection, "suma/schema_collection"
|
|
19
|
+
autoload :SchemaComparer, "suma/schema_comparer"
|
|
20
|
+
autoload :SchemaCompiler, "suma/schema_compiler"
|
|
21
|
+
autoload :SchemaDiscovery, "suma/schema_discovery"
|
|
22
|
+
autoload :SchemaExporter, "suma/schema_exporter"
|
|
23
|
+
autoload :SchemaIndex, "suma/schema_index"
|
|
24
|
+
autoload :SchemaManifestGenerator, "suma/schema_manifest_generator"
|
|
25
|
+
autoload :SchemaNaming, "suma/schema_naming"
|
|
26
|
+
autoload :SchemaTemplate, "suma/schema_template"
|
|
27
|
+
autoload :SiteConfig, "suma/site_config"
|
|
28
|
+
autoload :SvgQuality, "suma/svg_quality"
|
|
29
|
+
autoload :TermExtractor, "suma/term_extractor"
|
|
30
|
+
autoload :ThorExt, "suma/thor_ext"
|
|
31
|
+
autoload :Urn, "suma/urn"
|
|
32
|
+
autoload :Utils, "suma/utils"
|
|
11
33
|
|
|
12
|
-
|
|
34
|
+
autoload :Cli, "suma/cli"
|
|
35
|
+
autoload :Eengine, "suma/eengine"
|
|
36
|
+
autoload :Jsdai, "suma/jsdai"
|
|
37
|
+
|
|
38
|
+
class Error < StandardError; end
|
|
39
|
+
class SchemaNotFoundError < Error; end
|
|
40
|
+
class CompilationError < Error; end
|
|
41
|
+
class EengineNotAvailableError < Error; end
|
|
13
42
|
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.
|
|
38
|
-
spec.add_dependency "lutaml-model", "~> 0.
|
|
37
|
+
spec.add_dependency "glossarist", "~> 2.8", ">= 2.8.7"
|
|
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
|
|