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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Suma
4
- VERSION = "0.5.9"
4
+ VERSION = "0.5.10"
5
5
  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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: suma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.9
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