pubid 2.0.13 → 2.0.14
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/lib/pubid/ieee/identifiers/base.rb +99 -1
- data/lib/pubid/ieee/identifiers/joint_development.rb +17 -4
- data/lib/pubid/ieee/renderer.rb +10 -1
- data/lib/pubid/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a4c789f24390c6ea74355b629d16fe84521294e4aba851d074ecc0418196320
|
|
4
|
+
data.tar.gz: b80a6d4cc6167f7b6b53097dd5977fa754b27665be215fc2eb728d143d4e45fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3e37160d1874aa2d56a13689c627488e20788a33a61dddbf9a80e6c8c9c3283684f85c74c4dd71220a4a207e65af2d5e9e342284623a0a06a53ba2d1ba26005
|
|
7
|
+
data.tar.gz: dc678f6348adfb5be0b429297a3ba126df534ff531178e63fedfd2c7ba4c2c96a208af33b6f9dce35c7bf49fc29b14c21179ce4d9f43b5c42d839434b85dcfc1
|
|
@@ -237,16 +237,114 @@ module Pubid
|
|
|
237
237
|
# deserializes, so nested bases rebuild their component too. `draft` needs
|
|
238
238
|
# no expansion (Draft.parse accepts the slashless form).
|
|
239
239
|
def self.from_hash(data, options = {})
|
|
240
|
-
|
|
240
|
+
if data.is_a?(::Hash)
|
|
241
|
+
# The canonical comparison reads the wire AS STORED — the expand
|
|
242
|
+
# below rewrites `stage` into the typed_stage sub-hash that
|
|
243
|
+
# to_hash re-collapses, so the expanded form never equals a
|
|
244
|
+
# to_hash and would brand every canonical row legacy.
|
|
245
|
+
original_wire = Compaction.deep_dup(data)
|
|
246
|
+
# expand mutates its argument, so it works on another dup —
|
|
247
|
+
# original_wire must stay as the wire was stored.
|
|
248
|
+
data = Compaction.expand(Compaction.deep_dup(data))
|
|
249
|
+
end
|
|
241
250
|
identifier = super
|
|
242
251
|
# == compares the raw ivars: a rebuild whose lazy readers have not
|
|
243
252
|
# run carries @code_obj/@draft_obj nil while a parse has them set,
|
|
244
253
|
# so the objects compare unequal although their serialized hashes
|
|
245
254
|
# agree (pubid#596). Materialize the runtime components eagerly.
|
|
246
255
|
identifier.materialize_runtime_components
|
|
256
|
+
if original_wire
|
|
257
|
+
identifier = normalize_legacy_wire_shape(identifier, original_wire)
|
|
258
|
+
end
|
|
247
259
|
identifier
|
|
248
260
|
end
|
|
249
261
|
|
|
262
|
+
# The legacy crawls stored shapes the 1.3.x-era renderer printed that
|
|
263
|
+
# the current grammar reads differently (the stage-token drafts the D=
|
|
264
|
+
# designator face superseded — "DCD1" —, the project drafts stored as
|
|
265
|
+
# plain standards, the draft-less month dates the joint IEEE face now
|
|
266
|
+
# prints). A row whose deserialized canonical hash differs from the
|
|
267
|
+
# wire hash it came from is such a legacy shape: re-derive the
|
|
268
|
+
# identifier from its own render — the shape a parse of the printed
|
|
269
|
+
# form produces — so the deserialized row answers the corpus laws
|
|
270
|
+
# (render(parse(x)) == x, parse(render(x)) == x) like every
|
|
271
|
+
# parse-built one. The re-derived shape is accepted only when it is
|
|
272
|
+
# itself stable (parse∘render idempotent on string and hash), so the
|
|
273
|
+
# normalization never lands on a drifting spelling. A canonical wire
|
|
274
|
+
# hash — the current serializer's output, `to_hash == wire` — never
|
|
275
|
+
# heals, so `from_hash(to_hash(id)) == id` keeps holding for every
|
|
276
|
+
# parse-built identifier (the relaton-index round-trip invariant).
|
|
277
|
+
MAX_NORMALIZE_CYCLES = 3
|
|
278
|
+
|
|
279
|
+
def self.normalize_legacy_wire_shape(identifier, wire_hash)
|
|
280
|
+
# The base class dispatches this hash to the concrete class's
|
|
281
|
+
# from_hash AFTER expand rewrote `stage` into the typed_stage
|
|
282
|
+
# sub-hash, so the comparison collapses the wire back to the
|
|
283
|
+
# serialized shape to_hash emits (collapse is lossless and a no-op
|
|
284
|
+
# on an already-collapsed wire).
|
|
285
|
+
wire = canonical_wire_hash(wire_hash)
|
|
286
|
+
return identifier if identifier.to_hash == wire
|
|
287
|
+
|
|
288
|
+
original = identifier
|
|
289
|
+
MAX_NORMALIZE_CYCLES.times do
|
|
290
|
+
round = reparse(identifier)
|
|
291
|
+
return original unless round
|
|
292
|
+
|
|
293
|
+
same = round.to_s == identifier.to_s &&
|
|
294
|
+
round.to_hash == identifier.to_hash
|
|
295
|
+
return identifier if same
|
|
296
|
+
|
|
297
|
+
# Land only on a shape that is itself parse∘render-stable, so the
|
|
298
|
+
# healed identifier satisfies the corpus laws; an unstable round
|
|
299
|
+
# becomes the vantage of the next cycle, and an exhausted cycle
|
|
300
|
+
# budget keeps the legacy shape (never a drifting spelling).
|
|
301
|
+
return round if stable_shape?(round)
|
|
302
|
+
|
|
303
|
+
identifier = round
|
|
304
|
+
end
|
|
305
|
+
original
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# The wire as the canonicalizer emits it: key-normalized (string and
|
|
309
|
+
# symbol callers alike) and collapsed back from the expand rewrite.
|
|
310
|
+
def self.canonical_wire_hash(wire_hash)
|
|
311
|
+
normalized = normalize_wire_keys(Compaction.deep_dup(wire_hash))
|
|
312
|
+
Compaction.collapse(normalized)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# One parse of the identifier's own render, or nil when the render is
|
|
316
|
+
# not grammar-readable (the legacy shape is kept, as recorded).
|
|
317
|
+
def self.reparse(identifier)
|
|
318
|
+
Pubid::Ieee::Identifier.parse(identifier.to_s)
|
|
319
|
+
rescue StandardError
|
|
320
|
+
nil
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# A shape is landable when parse∘render is idempotent on it, string
|
|
324
|
+
# and hash — the Law 1a + Law 1b pair the corpus gate asserts.
|
|
325
|
+
def self.stable_shape?(round)
|
|
326
|
+
again = reparse(round)
|
|
327
|
+
!again.nil? && again.to_s == round.to_s &&
|
|
328
|
+
again.to_hash == round.to_hash
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Wire hashes arrive string-keyed (the published indexes) or
|
|
332
|
+
# symbol-keyed (direct spec calls); to_hash emits string keys, so the
|
|
333
|
+
# canonical comparison normalizes the wire keys the same way,
|
|
334
|
+
# recursing into nested bases.
|
|
335
|
+
def self.normalize_wire_keys(node)
|
|
336
|
+
case node
|
|
337
|
+
when ::Hash
|
|
338
|
+
node.each_with_object({}) do |(key, value), out|
|
|
339
|
+
out[key.to_s] = normalize_wire_keys(value)
|
|
340
|
+
end
|
|
341
|
+
when ::Array
|
|
342
|
+
node.map { |item| normalize_wire_keys(item) }
|
|
343
|
+
else
|
|
344
|
+
node
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
250
348
|
# Rebuild the runtime components from the wire strings. Safe on every
|
|
251
349
|
# construction path: the readers' ||= no-ops when already set, and the
|
|
252
350
|
# CodeNumber rebuild covers the split-column rows that carry no code
|
|
@@ -253,11 +253,24 @@ module Pubid
|
|
|
253
253
|
|
|
254
254
|
parts << code_str if code_str && !code_str.empty?
|
|
255
255
|
|
|
256
|
-
# Join with space and add
|
|
257
|
-
#
|
|
256
|
+
# Join with space and add the date — unless the D= designator face
|
|
257
|
+
# already carried the year. The month face follows the standing
|
|
258
|
+
# month rule: a text month is the comma long form (", February
|
|
259
|
+
# 2018", which re-parses through the joint date clause), a numeric
|
|
260
|
+
# month the glued short form ("-2018-05"); dropping the month
|
|
261
|
+
# published a different date ("P90003, February 2018 (E)" printed
|
|
262
|
+
# as "P90003-2018").
|
|
258
263
|
result = parts.join(" ")
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
if year && !@designator_carries_year &&
|
|
265
|
+
!ieee_draft.to_s.end_with?("-#{year}")
|
|
266
|
+
result += if month.to_s.match?(/\A\d+\z/)
|
|
267
|
+
"-#{year}-#{month.to_s.rjust(2, '0')}"
|
|
268
|
+
elsif month
|
|
269
|
+
", #{month} #{year}"
|
|
270
|
+
else
|
|
271
|
+
"-#{year}"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
261
274
|
# Lossless on the IEEE face too: the parenthetical rides after
|
|
262
275
|
# the year, before the redline marker.
|
|
263
276
|
result += " (#{parenthetical_content})" if parenthetical_content
|
data/lib/pubid/ieee/renderer.rb
CHANGED
|
@@ -146,7 +146,16 @@ module Pubid
|
|
|
146
146
|
should_render_type = id.publisher&.match?(/^(IEEE|AIEE)/) ||
|
|
147
147
|
id.is_a?(Identifiers::ProjectDraftIdentifier)
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
# An ISO/IEC stage-token draft (CD1, FDIS — the draft_obj carries the
|
|
150
|
+
# stage) is its own draft marker: the designator face it renders
|
|
151
|
+
# ("…/D=CD1", pubid#203) never takes the word Draft, so the type word
|
|
152
|
+
# is skipped whatever the publisher arrangement — the joint rows
|
|
153
|
+
# ("IEEE/IEC Draft P60076-57-129/D=FDIS-201707") would otherwise print
|
|
154
|
+
# Draft on one construction path and drop it on the re-parsed
|
|
155
|
+
# designation path, and the render would not re-parse to itself.
|
|
156
|
+
stage_draft = id.draft_obj&.iso_stage && true
|
|
157
|
+
|
|
158
|
+
if should_render_type && !stage_draft && !id.typed_stage&.project_status &&
|
|
150
159
|
((id.type && !id.type.to_s.strip.empty? && id.type != "P") ||
|
|
151
160
|
id.is_a?(Identifiers::ProjectDraftIdentifier)) &&
|
|
152
161
|
!id.draft_status.to_s.match?(/unapproved/i)
|
data/lib/pubid/version.rb
CHANGED