suma 0.5.9 → 0.5.11
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/.rubocop_todo.yml +27 -0
- data/CLAUDE.md +17 -5
- data/README.adoc +101 -0
- data/lib/suma/anchor_validator.rb +85 -0
- data/lib/suma/artifact_server.rb +102 -0
- data/lib/suma/cli/artifact_server_cli.rb +22 -0
- data/lib/suma/cli/build.rb +20 -3
- data/lib/suma/cli/core.rb +50 -4
- data/lib/suma/cli/export.rb +1 -1
- data/lib/suma/cli/extract_terms.rb +1 -1
- data/lib/suma/cli/generate_register.rb +1 -1
- data/lib/suma/cli/ingest_translations.rb +36 -0
- data/lib/suma/cli/package_gcr.rb +51 -0
- data/lib/suma/cli/validate.rb +16 -0
- data/lib/suma/cli.rb +4 -1
- data/lib/suma/closure_validator.rb +141 -37
- data/lib/suma/corpus_graph.rb +80 -17
- data/lib/suma/gcr_package.rb +230 -0
- data/lib/suma/link_validation_result.rb +8 -0
- data/lib/suma/link_validator.rb +0 -3
- data/lib/suma/output_canonicalizer.rb +107 -0
- data/lib/suma/owl_projection.rb +3 -3
- data/lib/suma/processor.rb +100 -20
- data/lib/suma/provenance.rb +92 -12
- data/lib/suma/schema_collection.rb +1 -0
- data/lib/suma/schema_compiler.rb +79 -13
- data/lib/suma/schema_exporter.rb +12 -0
- data/lib/suma/schema_index.rb +5 -5
- data/lib/suma/schema_manifest_generator.rb +10 -3
- data/lib/suma/schema_profile.rb +12 -0
- data/lib/suma/schema_template/anchors.rb +154 -0
- data/lib/suma/schema_template/document.rb +2 -93
- data/lib/suma/schema_template/plain.rb +10 -6
- data/lib/suma/schema_template.rb +1 -0
- data/lib/suma/site_build.rb +153 -0
- data/lib/suma/staged_collection_builder.rb +146 -10
- data/lib/suma/term_extractor.rb +10 -4
- data/lib/suma/translation_ingester.rb +169 -0
- data/lib/suma/urn_resolver.rb +3 -3
- data/lib/suma/version.rb +1 -1
- data/lib/suma/worker_pool.rb +5 -1
- data/lib/suma.rb +7 -0
- metadata +13 -2
data/lib/suma/term_extractor.rb
CHANGED
|
@@ -94,7 +94,9 @@ module Suma
|
|
|
94
94
|
section_ref = get_section_ref(schema)
|
|
95
95
|
|
|
96
96
|
Glossarist::ManagedConceptCollection.new.tap do |collection|
|
|
97
|
-
|
|
97
|
+
# expressir leaves collection attributes nil when the clause is
|
|
98
|
+
# absent; guard every walk over parsed-model collections.
|
|
99
|
+
Array(schema.entities).each do |entity|
|
|
98
100
|
managed_data = build_managed_data(schema, entity, section_ref,
|
|
99
101
|
classification)
|
|
100
102
|
|
|
@@ -154,7 +156,7 @@ module Suma
|
|
|
154
156
|
data.sources = [build_source_ref(schema, entity)]
|
|
155
157
|
|
|
156
158
|
notes = NoteProcessor.call(
|
|
157
|
-
entity.remarks,
|
|
159
|
+
Array(entity.remarks),
|
|
158
160
|
definitions: data.definition,
|
|
159
161
|
xref_to_mention: method(:xref_to_mention),
|
|
160
162
|
)
|
|
@@ -273,12 +275,16 @@ module Suma
|
|
|
273
275
|
|
|
274
276
|
entity_ref = urn_mention(term_urn("express-language.entity"), "entity")
|
|
275
277
|
|
|
276
|
-
|
|
278
|
+
# expressir 2.4.44 returns nil for entities without SUBTYPE_OF
|
|
279
|
+
# (older versions returned []); guard both shapes.
|
|
280
|
+
subtypes = entity.subtype_of || []
|
|
281
|
+
|
|
282
|
+
if subtypes.empty?
|
|
277
283
|
"#{entity_type} " \
|
|
278
284
|
"that represents the " \
|
|
279
285
|
"#{entity_name_to_text(entity.id)} #{entity_ref}"
|
|
280
286
|
else
|
|
281
|
-
entity_subtypes =
|
|
287
|
+
entity_subtypes = subtypes.map do |e|
|
|
282
288
|
urn_mention(express_entity_urn("#{schema.id}.#{e.id}"), e.id)
|
|
283
289
|
end
|
|
284
290
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "glossarist"
|
|
5
|
+
require "yaml"
|
|
6
|
+
|
|
7
|
+
module Suma
|
|
8
|
+
# Overlays translated content onto extracted Glossarist concepts (T5).
|
|
9
|
+
#
|
|
10
|
+
# A translations file is a YAML sequence of localized-concept documents,
|
|
11
|
+
# each carrying an identity key plus the content to apply:
|
|
12
|
+
#
|
|
13
|
+
# - concept_id: example_schema.bolt # managed id (data.identifier)
|
|
14
|
+
# # or the managed concept's uuid
|
|
15
|
+
# - id: <localized uuid> # alternative identity key
|
|
16
|
+
# data:
|
|
17
|
+
# language_code: fra
|
|
18
|
+
# terms: [...]
|
|
19
|
+
# definition: [...]
|
|
20
|
+
#
|
|
21
|
+
# Every field the entry provides replaces the localization's field; every
|
|
22
|
+
# field it omits falls back, explicitly, to the localization's current
|
|
23
|
+
# content (for a new language: the source-language localization's). All
|
|
24
|
+
# (de)serialization goes through the Glossarist/lutaml-model framework;
|
|
25
|
+
# entries are merged as serialized hashes and reified with of_yaml.
|
|
26
|
+
class TranslationIngester
|
|
27
|
+
def initialize(concepts_dir:, translations:, output_dir: nil)
|
|
28
|
+
@concepts_dir = concepts_dir
|
|
29
|
+
@translations = translations
|
|
30
|
+
@output_dir = output_dir || concepts_dir
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash{String=>Array<String>}] concept file → applied entry
|
|
34
|
+
# identities
|
|
35
|
+
def ingest
|
|
36
|
+
entries = load_entries
|
|
37
|
+
applied = {}
|
|
38
|
+
each_concept_document do |file, doc|
|
|
39
|
+
matched = entries_for(doc, entries)
|
|
40
|
+
next if matched.empty?
|
|
41
|
+
|
|
42
|
+
applied[file] = matched.map { |entry| apply_entry(doc, entry) }
|
|
43
|
+
write_document(file, doc)
|
|
44
|
+
end
|
|
45
|
+
assert_no_unmatched!(entries, applied)
|
|
46
|
+
applied
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def load_entries
|
|
52
|
+
content = YAML.safe_load_file(@translations, permitted_classes: [],
|
|
53
|
+
aliases: true)
|
|
54
|
+
entries = content.is_a?(Hash) ? content["translations"] : content
|
|
55
|
+
unless entries.is_a?(Array)
|
|
56
|
+
raise Error,
|
|
57
|
+
"translations file must be a YAML sequence of localized " \
|
|
58
|
+
"concepts: #{@translations}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
entries
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def each_concept_document
|
|
65
|
+
Dir.glob(File.join(@concepts_dir, "*.yaml")).each do |file|
|
|
66
|
+
doc = Glossarist::V3::ConceptDocument.from_yamls(File.read(file))
|
|
67
|
+
yield file, doc
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def entries_for(doc, entries)
|
|
72
|
+
entries.select { |entry| entry_matches?(doc, entry) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def entry_matches?(doc, entry)
|
|
76
|
+
if entry["concept_id"]
|
|
77
|
+
[doc.concept.data.id, doc.concept.uuid].include?(entry["concept_id"])
|
|
78
|
+
else
|
|
79
|
+
localized_uuids(doc).include?(entry["id"])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def localized_uuids(doc)
|
|
84
|
+
doc.localizations.map(&:id) +
|
|
85
|
+
doc.concept.data.localized_concepts.values
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def apply_entry(doc, entry)
|
|
89
|
+
incoming = Glossarist::LocalizedConcept
|
|
90
|
+
.of_yaml(entry.except("concept_id"))
|
|
91
|
+
localized = merge_localization(doc, entry, incoming)
|
|
92
|
+
replace_localization(doc, localized)
|
|
93
|
+
entry["concept_id"] || entry["id"]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def replace_localization(doc, localized)
|
|
97
|
+
language = localized.data.language_code
|
|
98
|
+
doc.localizations.reject! { |l10n| l10n.data.language_code == language }
|
|
99
|
+
doc.localizations << localized
|
|
100
|
+
doc.concept.data.localized_concepts[language] = localized.uuid
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Entry fields win; absent fields keep the localization's current
|
|
104
|
+
# content (or the source-language content for a new language). The
|
|
105
|
+
# merge happens on the raw entry data — a parsed model would
|
|
106
|
+
# materialize empty collections and wipe the fields it omits.
|
|
107
|
+
#
|
|
108
|
+
# Glossarist parses the serialized "id" key into `#id` (not `#uuid`),
|
|
109
|
+
# so the persisted id is preserved and stamped back onto the merged
|
|
110
|
+
# localization explicitly; a new language gets the same v5 id
|
|
111
|
+
# TermExtractor's linked localizations use.
|
|
112
|
+
def merge_localization(doc, entry, incoming)
|
|
113
|
+
current = current_localization(doc, entry, incoming)
|
|
114
|
+
base = current || doc.localizations.first
|
|
115
|
+
return incoming unless base
|
|
116
|
+
|
|
117
|
+
identity = entry["id"] || current&.id ||
|
|
118
|
+
new_localization_uuid(doc, incoming.data.language_code)
|
|
119
|
+
localized = Glossarist::LocalizedConcept
|
|
120
|
+
.of_yaml(merged_hash(base, entry, identity))
|
|
121
|
+
localized.uuid = identity
|
|
122
|
+
localized
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def merged_hash(base, entry, identity)
|
|
126
|
+
merged = base.to_yaml_hash
|
|
127
|
+
merged["data"] = (merged["data"] || {})
|
|
128
|
+
.merge(entry["data"] || {})
|
|
129
|
+
merged["id"] = identity
|
|
130
|
+
merged
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def current_localization(doc, entry, incoming)
|
|
134
|
+
if entry["id"]
|
|
135
|
+
doc.localizations.find { |l10n| l10n.id == entry["id"] }
|
|
136
|
+
else
|
|
137
|
+
doc.localizations.find do |l10n|
|
|
138
|
+
l10n.data.language_code == incoming.data.language_code
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Same convention as TermExtractor's linked localizations.
|
|
144
|
+
def new_localization_uuid(doc, language)
|
|
145
|
+
Glossarist::Utilities::UUID.uuid_v5(
|
|
146
|
+
Glossarist::Utilities::UUID::OID_NAMESPACE,
|
|
147
|
+
"#{doc.concept.data.id}-#{language}",
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def write_document(file, doc)
|
|
152
|
+
output = File.join(@output_dir, File.basename(file))
|
|
153
|
+
FileUtils.mkdir_p(@output_dir) if output != file
|
|
154
|
+
File.write(output, doc.to_yamls, encoding: "utf-8")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def assert_no_unmatched!(entries, applied)
|
|
158
|
+
matched = applied.values.flatten
|
|
159
|
+
unmatched = entries.reject do |entry|
|
|
160
|
+
matched.include?(entry["concept_id"] || entry["id"])
|
|
161
|
+
end
|
|
162
|
+
return if unmatched.empty?
|
|
163
|
+
|
|
164
|
+
raise Error,
|
|
165
|
+
"unmatched translation entries: " \
|
|
166
|
+
"#{unmatched.map { |e| e['concept_id'] || e['id'] }.join(', ')}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
data/lib/suma/urn_resolver.rb
CHANGED
|
@@ -96,7 +96,7 @@ module Suma
|
|
|
96
96
|
artifacts = {}
|
|
97
97
|
schemas.each do |schema|
|
|
98
98
|
artifacts[compose(:tech, schema.id)] = schema_payload(schema)
|
|
99
|
-
schema.children.each do |element|
|
|
99
|
+
Array(schema.children).each do |element|
|
|
100
100
|
payload = element_payload(schema, element)
|
|
101
101
|
artifacts[compose(:tech, "#{schema.id}.#{element.id}")] = payload if
|
|
102
102
|
payload
|
|
@@ -111,7 +111,7 @@ module Suma
|
|
|
111
111
|
def add_attribute_artifacts(artifacts, schema, element)
|
|
112
112
|
return unless element.is_a?(Expressir::Model::Declarations::Entity)
|
|
113
113
|
|
|
114
|
-
element.attributes.each do |attribute|
|
|
114
|
+
Array(element.attributes).each do |attribute|
|
|
115
115
|
key = compose(:tech,
|
|
116
116
|
"#{schema.id}.#{element.id}.#{attribute.id}")
|
|
117
117
|
artifacts[key] = attribute_artifact(schema, element, attribute)
|
|
@@ -150,7 +150,7 @@ module Suma
|
|
|
150
150
|
"interface" => Array(schema.interfaces).filter_map do |interface|
|
|
151
151
|
interface.schema&.id
|
|
152
152
|
end,
|
|
153
|
-
"elements" => schema.children.filter_map do |element|
|
|
153
|
+
"elements" => Array(schema.children).filter_map do |element|
|
|
154
154
|
element.id if declared_element?(element)
|
|
155
155
|
end.sort,
|
|
156
156
|
}
|
data/lib/suma/version.rb
CHANGED
data/lib/suma/worker_pool.rb
CHANGED
|
@@ -45,7 +45,11 @@ module Suma
|
|
|
45
45
|
item, index = pair
|
|
46
46
|
begin
|
|
47
47
|
results[index] = block.call(item)
|
|
48
|
-
|
|
48
|
+
# SystemStackError is not StandardError: deep per-item recursion
|
|
49
|
+
# (a pathological EXPRESS AST in the export pretty-printer)
|
|
50
|
+
# overflows the ~1 MB thread stack, and swallowing it would
|
|
51
|
+
# leave silent holes in results. The caller falls back.
|
|
52
|
+
rescue SystemStackError, StandardError => e
|
|
49
53
|
errors << e
|
|
50
54
|
break
|
|
51
55
|
end
|
data/lib/suma.rb
CHANGED
|
@@ -7,6 +7,8 @@ require "fileutils"
|
|
|
7
7
|
module Suma
|
|
8
8
|
autoload :VERSION, "suma/version"
|
|
9
9
|
|
|
10
|
+
autoload :AnchorValidator, "suma/anchor_validator"
|
|
11
|
+
autoload :ArtifactServer, "suma/artifact_server"
|
|
10
12
|
autoload :SchemaProfile, "suma/schema_profile"
|
|
11
13
|
autoload :BuildPlan, "suma/build_plan"
|
|
12
14
|
autoload :Processor, "suma/processor"
|
|
@@ -20,14 +22,17 @@ module Suma
|
|
|
20
22
|
autoload :CorpusGraph, "suma/corpus_graph"
|
|
21
23
|
autoload :CollectionManifest, "suma/collection_manifest"
|
|
22
24
|
autoload :EengineConverter, "suma/eengine_converter"
|
|
25
|
+
autoload :GcrPackage, "suma/gcr_package"
|
|
23
26
|
autoload :ExpressReformatter, "suma/express_reformatter"
|
|
24
27
|
autoload :ExpressSchema, "suma/express_schema"
|
|
25
28
|
autoload :LinkValidation, "suma/link_validation"
|
|
29
|
+
autoload :LinkValidationResult, "suma/link_validation_result"
|
|
26
30
|
autoload :LinkValidator, "suma/link_validator"
|
|
27
31
|
autoload :ManifestTraverser, "suma/manifest_traverser"
|
|
28
32
|
autoload :MappingDrift, "suma/mapping_drift"
|
|
29
33
|
autoload :NoteProcessor, "suma/note_processor"
|
|
30
34
|
autoload :NullCache, "suma/null_cache"
|
|
35
|
+
autoload :OutputCanonicalizer, "suma/output_canonicalizer"
|
|
31
36
|
autoload :OwlProjection, "suma/owl_projection"
|
|
32
37
|
autoload :RegisterManifestGenerator, "suma/register_manifest_generator"
|
|
33
38
|
autoload :SchemaCache, "suma/schema_cache"
|
|
@@ -42,11 +47,13 @@ module Suma
|
|
|
42
47
|
autoload :SchemaNaming, "suma/schema_naming"
|
|
43
48
|
autoload :SchemaTemplate, "suma/schema_template"
|
|
44
49
|
autoload :SiteConfig, "suma/site_config"
|
|
50
|
+
autoload :SiteBuild, "suma/site_build"
|
|
45
51
|
autoload :StagedCollectionBuilder, "suma/staged_collection_builder"
|
|
46
52
|
autoload :SvgQuality, "suma/svg_quality"
|
|
47
53
|
autoload :TermClassification, "suma/term_classification"
|
|
48
54
|
autoload :TermExtractor, "suma/term_extractor"
|
|
49
55
|
autoload :ThorExt, "suma/thor_ext"
|
|
56
|
+
autoload :TranslationIngester, "suma/translation_ingester"
|
|
50
57
|
autoload :Urn, "suma/urn"
|
|
51
58
|
autoload :UrnResolver, "suma/urn_resolver"
|
|
52
59
|
autoload :Utils, "suma/utils"
|
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.5.
|
|
4
|
+
version: 0.5.11
|
|
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-10-
|
|
11
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: expressir
|
|
@@ -185,11 +185,14 @@ files:
|
|
|
185
185
|
- bin/setup
|
|
186
186
|
- exe/suma
|
|
187
187
|
- lib/suma.rb
|
|
188
|
+
- lib/suma/anchor_validator.rb
|
|
189
|
+
- lib/suma/artifact_server.rb
|
|
188
190
|
- lib/suma/build_plan.rb
|
|
189
191
|
- lib/suma/change_classification.rb
|
|
190
192
|
- lib/suma/change_impact.rb
|
|
191
193
|
- lib/suma/change_manifest.rb
|
|
192
194
|
- lib/suma/cli.rb
|
|
195
|
+
- lib/suma/cli/artifact_server_cli.rb
|
|
193
196
|
- lib/suma/cli/build.rb
|
|
194
197
|
- lib/suma/cli/check_svg_quality.rb
|
|
195
198
|
- lib/suma/cli/compare.rb
|
|
@@ -199,6 +202,8 @@ files:
|
|
|
199
202
|
- lib/suma/cli/extract_terms.rb
|
|
200
203
|
- lib/suma/cli/generate_register.rb
|
|
201
204
|
- lib/suma/cli/generate_schemas.rb
|
|
205
|
+
- lib/suma/cli/ingest_translations.rb
|
|
206
|
+
- lib/suma/cli/package_gcr.rb
|
|
202
207
|
- lib/suma/cli/reformat.rb
|
|
203
208
|
- lib/suma/cli/resolve.rb
|
|
204
209
|
- lib/suma/cli/validate.rb
|
|
@@ -213,17 +218,20 @@ files:
|
|
|
213
218
|
- lib/suma/eengine_converter.rb
|
|
214
219
|
- lib/suma/express_reformatter.rb
|
|
215
220
|
- lib/suma/express_schema.rb
|
|
221
|
+
- lib/suma/gcr_package.rb
|
|
216
222
|
- lib/suma/jsdai.rb
|
|
217
223
|
- lib/suma/jsdai/figure.rb
|
|
218
224
|
- lib/suma/jsdai/figure_image.rb
|
|
219
225
|
- lib/suma/jsdai/figure_xml.rb
|
|
220
226
|
- lib/suma/link_validation.rb
|
|
227
|
+
- lib/suma/link_validation_result.rb
|
|
221
228
|
- lib/suma/link_validator.rb
|
|
222
229
|
- lib/suma/link_validator/step.rb
|
|
223
230
|
- lib/suma/manifest_traverser.rb
|
|
224
231
|
- lib/suma/mapping_drift.rb
|
|
225
232
|
- lib/suma/note_processor.rb
|
|
226
233
|
- lib/suma/null_cache.rb
|
|
234
|
+
- lib/suma/output_canonicalizer.rb
|
|
227
235
|
- lib/suma/owl_projection.rb
|
|
228
236
|
- lib/suma/processor.rb
|
|
229
237
|
- lib/suma/provenance.rb
|
|
@@ -241,8 +249,10 @@ files:
|
|
|
241
249
|
- lib/suma/schema_naming.rb
|
|
242
250
|
- lib/suma/schema_profile.rb
|
|
243
251
|
- lib/suma/schema_template.rb
|
|
252
|
+
- lib/suma/schema_template/anchors.rb
|
|
244
253
|
- lib/suma/schema_template/document.rb
|
|
245
254
|
- lib/suma/schema_template/plain.rb
|
|
255
|
+
- lib/suma/site_build.rb
|
|
246
256
|
- lib/suma/site_config.rb
|
|
247
257
|
- lib/suma/staged_collection_builder.rb
|
|
248
258
|
- lib/suma/svg_quality.rb
|
|
@@ -256,6 +266,7 @@ files:
|
|
|
256
266
|
- lib/suma/term_classification.rb
|
|
257
267
|
- lib/suma/term_extractor.rb
|
|
258
268
|
- lib/suma/thor_ext.rb
|
|
269
|
+
- lib/suma/translation_ingester.rb
|
|
259
270
|
- lib/suma/urn.rb
|
|
260
271
|
- lib/suma/urn_resolver.rb
|
|
261
272
|
- lib/suma/utils.rb
|