relaton-cli 3.0.0 → 3.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38a1d2c4a0c1ae1bd81c6fd588a3b1b83f98828e11620fe6b981e17223f093cf
4
- data.tar.gz: 7cb684d5bf4d320606c643d53b27b63ba5354c89e73019b4455dd2942bd42c52
3
+ metadata.gz: 9e09dab74faf731f638fb7b31271e8dedd483dec7752b232ffe1d1ed82230bb4
4
+ data.tar.gz: d35d03f375a776575bfc86d58cf188b3da4f9120907562ceb9120b72083dfda3
5
5
  SHA512:
6
- metadata.gz: 52517b0e9ccaa115aea67a37ff21af8163b0657a51710217b8f02e5a7f39cf51609b2582c90bfe0d5b02e040752a97672c8892d1a26c84ad95e083a8c9085bec
7
- data.tar.gz: cb9bf490e6c3dc59c6ca6c8b096867dc51be43b91eaeee8759ccb5684cb9be02715477f211e4a554adda9c5661ca26ff7d2b64ef54e00ecd0f59394c031c5a63
6
+ metadata.gz: f53553f7d991fb26fe8679ce9c5f898d8d33fa836cf7224e12ffaa1b831d0da27693916f9d99d3aab365f4698900e29c7b356c73bf7c497ffe3c453534ea000c
7
+ data.tar.gz: 5f9ddb547964442309a65aea8ba3b28959693ea4c373bb1594cdeafcae9bcc0c28957b68516613dbff917bd0aa7b5208adfacb6def16597e340352c536753fec
data/Gemfile CHANGED
@@ -6,6 +6,17 @@ gemspec
6
6
  # The single combined `relaton` gem lives at the repo root.
7
7
  gem "relaton", path: "../.."
8
8
 
9
+ # parsanol 1.3.111 is the trivia-exempt release (parsanol#355): the
10
+ # emission-2 artifacts (pubid#614) parse parameter-shaped under it, so
11
+ # the pin rides it; the root gemspec floor carries the same story.
12
+
13
+ # The pubid 2.0.0 FINAL gem still ships emission-1 artifacts - under
14
+ # 1.3.111 its parses break exactly as parsanol#352 describes. The
15
+ # emission-2 set ships from 2.0.0.pre.alpha.64, so the pin names it;
16
+ # without it the resolver picks the final and every structured-corpus
17
+ # docid with a part+date fails ("Unknown parameter type: colon").
18
+ gem "pubid", "2.0.0.pre.alpha.64"
19
+
9
20
  # https://github.com/rails/rails/issues/55886
10
21
  # add openssl as an explicit gem to fix SSL verification issues in GHA.
11
22
  # gem "openssl"
@@ -33,3 +44,4 @@ gem "webmock"
33
44
  # (leptris#1742), pinned to match the relaton root bundle — the battery
34
45
  # re-run accompanies this bump.
35
46
  gem "leptris", "1.9.370.1"
47
+
@@ -127,7 +127,14 @@ module Relaton
127
127
  # per row against 0.44 KB for the derived values alone — 543 MB vs 76 MB
128
128
  # on a 177k-row corpus. Anything else derived from pubid must therefore
129
129
  # be computed in `add` too; by write time the object is gone.
130
- Row = Struct.new(:rendered, :file, :key, :id_hash)
130
+ #
131
+ # `derived` marks an alias row (this document's other spellings, or a
132
+ # committed fetcher alias) as opposed to the document's own primary
133
+ # id — `indexed_rows` prunes a derived row whose render a document
134
+ # owns as its primary, so an alias can never repoint a spelling at
135
+ # the wrong file (`Type#add_or_update` keys on the rendered string
136
+ # and overwrites).
137
+ Row = Struct.new(:rendered, :file, :key, :id_hash, :derived)
131
138
 
132
139
  attr_reader :rows
133
140
 
@@ -144,7 +151,24 @@ module Relaton
144
151
  @rows = []
145
152
  end
146
153
 
147
- def add(rendered, file)
154
+ # Committed rows an id-sharing file already covers: the fetcher's
155
+ # aliases (a record-less registry's id pointing at the record-bearing
156
+ # document's file -- relaton-iana#8, #9). Carried as rows of their
157
+ # own so either spelling resolves to the one document.
158
+ def add_alias_rows(rows)
159
+ rows.each do |id_hash, file|
160
+ pubid = from_hash(id_hash)
161
+ next unless pubid
162
+
163
+ row = Row.new(pubid.to_s, file)
164
+ row.id_hash = id_hash
165
+ row.key = key_string(pubid)
166
+ row.derived = true
167
+ @rows << row
168
+ end
169
+ end
170
+
171
+ def add(rendered, file, alias_ids = nil)
148
172
  row = Row.new(rendered, file)
149
173
  # Precompute the expensive derived values (root.number walk,
150
174
  # lutaml to_hash) once during the streaming pass — computing
@@ -163,6 +187,43 @@ module Relaton
163
187
  row.key = key_string(pubid)
164
188
  end
165
189
  @rows << row
190
+ add_rendered_alias_rows(pubid, hash, rendered, file, alias_ids)
191
+ end
192
+
193
+ # The document's other spellings as rows of their own, so every id
194
+ # the dataset prints resolves: the parse's canonical render of the
195
+ # primary ("ISO/IEC DIR 1" from the file's own
196
+ # "ISO/IEC Directives Part 1") and each further docidentifier's
197
+ # render. All point at the same file. A docid that does not parse
198
+ # is skipped, and one that renders as an already-added spelling of
199
+ # this document adds no second row. Cross-document collisions are
200
+ # not decided here — `indexed_rows` prunes a derived row whose
201
+ # spelling a document owns as its primary.
202
+ def add_rendered_alias_rows(pubid, primary_hash, rendered, file, alias_ids)
203
+ seen = { rendered => true }
204
+ canon = pubid&.to_s
205
+ if canon && !canon.empty? && !seen.key?(canon)
206
+ seen[canon] = true
207
+ push_derived_row(canon, file, primary_hash, key_string(pubid))
208
+ end
209
+ Array(alias_ids).each do |content|
210
+ alias_pubid = parse(content.to_s)
211
+ next unless alias_pubid
212
+
213
+ render = alias_pubid.to_s
214
+ next if render.empty? || seen.key?(render)
215
+
216
+ seen[render] = true
217
+ push_derived_row(render, file, alias_pubid.to_hash, key_string(alias_pubid))
218
+ end
219
+ end
220
+
221
+ def push_derived_row(rendered, file, id_hash, key)
222
+ row = Row.new(rendered, file)
223
+ row.id_hash = id_hash
224
+ row.key = key
225
+ row.derived = true
226
+ @rows << row
166
227
  end
167
228
 
168
229
  def count
@@ -223,12 +284,36 @@ module Relaton
223
284
  # `Relaton::Ieee::DataFetcher#build_index` already skips the same rows
224
285
  # for the same reason and logs the loss; this matches it.
225
286
  #
287
+ # A derived (alias) row is dropped when a document owns the spelling
288
+ # as its own primary id, or when an earlier alias row claimed it:
289
+ # `Type#add_or_update` keys id_lookup on the rendered string, so
290
+ # letting both publish would silently repoint the spelling at
291
+ # whichever file sorted last. The document's primary id — and, when
292
+ # two derived rows survive, the earlier document — wins.
293
+ #
226
294
  # Memoized, and the single place the sort happens — `each_shard` and
227
295
  # `write_monolith` previously sorted the corpus independently, so both
228
296
  # ran on every build.
229
297
  def indexed_rows
230
- @indexed_rows ||= @rows.reject { |row| row.id_hash.nil? }
231
- .sort_by { |row| [row.key, row.rendered] }
298
+ @indexed_rows ||= begin
299
+ owned = {}
300
+ @rows.each { |row| owned[row.rendered] = true unless row.derived }
301
+ claimed = {}
302
+ @rows.reject { |row| row.id_hash.nil? }
303
+ .reject do |row|
304
+ next false unless row.derived
305
+
306
+ next true if owned.key?(row.rendered)
307
+
308
+ if claimed.key?(row.rendered)
309
+ true
310
+ else
311
+ claimed[row.rendered] = true
312
+ false
313
+ end
314
+ end
315
+ .sort_by { |row| [row.key, row.rendered] }
316
+ end
232
317
  end
233
318
 
234
319
  # Rows dropped by `indexed_rows` — reported so the loss is never silent.
@@ -504,12 +589,34 @@ module Relaton
504
589
  # the same name carries plain strings, and one of those written into a
505
590
  # row would break both the monolith (`yaml_nested` walks a Hash) and the
506
591
  # consumer (`FileIO#deserialize_id` calls `from_hash`).
592
+ #
593
+ # The first id per file becomes the map; ids beyond the first are the
594
+ # fetcher's aliases onto that file (see .committed_alias_rows).
507
595
  def read_committed_index(path)
508
596
  rows = YAML.safe_load(File.read(path), permitted_classes: [Symbol])
509
- Array(rows).each_with_object({}) do |row, acc|
510
- next unless row.is_a?(Hash) && row[:id].is_a?(Hash)
597
+ @committed_rows = Array(rows).select do |row|
598
+ row.is_a?(Hash) && row[:id].is_a?(Hash) && row[:file]
599
+ end
600
+ @committed_rows.each_with_object({}) do |row, acc|
601
+ acc[row[:file]] ||= row[:id]
602
+ end
603
+ end
511
604
 
512
- acc[row[:file]] = row[:id]
605
+ # `{ id hash => file }` for committed rows beyond each file's first --
606
+ # the fetcher's alias ids, which no data file of their own carries.
607
+ # Without this pass the machine index drops them (the file-keyed
608
+ # fallback resolves one id per file), and a spelling the corpus
609
+ # aliased stops resolving.
610
+ def committed_alias_rows
611
+ committed_index # force the load and @committed_rows
612
+ seen = {}
613
+ (@committed_rows || []).filter_map do |row|
614
+ if seen[row[:file]]
615
+ [row[:id], row[:file]]
616
+ else
617
+ seen[row[:file]] = true
618
+ nil
619
+ end
513
620
  end
514
621
  end
515
622
 
@@ -567,7 +674,8 @@ module Relaton
567
674
  # The machine-index input: rendered primary docid + repo-relative
568
675
  # path (clients prefix their own baseurl onto file).
569
676
  def machine_record(item)
570
- [item["id"], item["yaml_path"]]
677
+ [item["id"], item["yaml_path"],
678
+ Array(item["docids"]).filter_map { |d| d["id"] if d.is_a?(Hash) }]
571
679
  end
572
680
 
573
681
  def write_machine_index_manifest(total)
@@ -659,6 +767,7 @@ module Relaton
659
767
  detail << detail_record(doc) if emit_detail?
660
768
  machine&.add(*machine_record(doc))
661
769
  end
770
+ machine&.add_alias_rows(committed_alias_rows)
662
771
  summary.flush
663
772
  detail.flush
664
773
  @machine_index = machine
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relaton-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
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-27 00:00:00.000000000 Z
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 3.0.0
47
+ version: 3.0.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 3.0.0
54
+ version: 3.0.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: relaton-render
57
57
  requirement: !ruby/object:Gem::Requirement