suma 0.5.9 → 0.5.10
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 +4 -0
- 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 +124 -20
- data/lib/suma/corpus_graph.rb +79 -16
- 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/processor.rb +94 -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_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 +11 -3
- data/lib/suma/translation_ingester.rb +169 -0
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +7 -0
- metadata +12 -1
|
@@ -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/version.rb
CHANGED
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,7 +1,7 @@
|
|
|
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.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -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
|