pubid 2.0.0.pre.alpha.17 → 2.0.0.pre.alpha.19

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.
@@ -95,3 +95,5 @@ The lesson BSI paid for: a wrapper's hand-written `#number` delegation makes the
95
95
  - **`all_parts_edition_keys` missed `revision`, `reaffirmed` AND `edition_month` — three gaps, found in two passes.** `Identifier.all_parts_edition_keys` defaults to `%i[date year edition version]`. A first pass fixed IEEE's `revision` attribute (the numbered/lettered draft revision id — "2" in "P802.16Rev2"), a separate discriminator from `edition`/`year` that this list never covered, so `"IEEE P802.16Rev2".to_all_parts === "IEEE P802.16Rev3"` was `false` — two revisions of the same draft failed to collapse. Code review of that same change then found it had stopped one attribute too early: `reaffirmed` (the "(R2010)" year, read by `renderer.rb` and `urn_generator.rb`) meant two reaffirmations of the same standard — `"IEEE Std 218-1956 (R1980) …"` vs `"… (R1990) …"` — also failed to collapse; and `edition_month`, the month half of the `"Edition N YYYY-MM"` rendering (`renderer.rb`'s `"Edition #{edition} #{year}-#{edition_month}"`), survived even though its sibling `year` is already in the default list, so `"IEEE Std 802.11 Edition 3.0 2015-03"` didn't match `"… 2015-06"`. All three fixed together: `Pubid::Ieee::Identifier.all_parts_edition_keys` is `super + %i[revision reaffirmed edition_month]`. Not on the original hand-off's audit list (which didn't cover IEEE); found during code review of the OGC/3GPP/NIST fix for the same class of bug. **Lesson**: finding one missed attribute on a flavor does not mean the audit of that flavor is finished — check every date/edition/version/revision/reaffirmation-shaped attribute against the renderer and builder, not just the one the bug report named (see the root `CLAUDE.md` bullet). Locked by `spec/pubid/all_parts_edition_keys_audit_spec.rb`.
96
96
 
97
97
  - **The project designator format, and the bare-P parse that dropped it (pubid#18).** IEEE's own draft documents are cited by their short designator — "P1201/D0.3", "P10000/D1.2" — and metanorma-ieee had to post-process the full rendering to produce it. Two edits. **(1) `format: :project`** registers `Pubid::Ieee::ProjectRenderer` in the IEEE format registry: the code (with parts), a PREPENDED "P" when the identifier is a project but the code does not already carry it, and "/D<draft>"; no publisher, no type word, no date. "Project" is detected in all three places a marker can live depending on the building path — `code_obj.prefix` (the bare spelling), the "P" type code, or a `typed_stage` with `project_status` (D1-D6). **(2) The bare-P data loss**: `ieee_p_identifier` consumed its leading "P" with a bare `str("P")`, so "P1201/D0.3" parsed WITHOUT its P anywhere (the number was "1201", prefix nil) — the issue's complaint verbatim. The rule now lets `number` capture the P (the #318 pattern), so `code.prefix` survives and the designator has something to read. The DEFAULT rendering is unchanged — the corpus keeps the P in every unapproved-draft canonical, and #318's idempotence work locked that; the issue's suggestion that the default drop the P predates the corpus and is not adopted. A published (non-project) id renders its bare number under `:project` — the format is a draft-designator view.
98
+
99
+ - **Joint stage-first forms are lead ISO, and the ISO face is lossless (pubid#477)**: a stage word printed in the ISO position (`IEEE FCD 15026.3:2010`, `IEEE/IEC CD2 P62582-2017-05`) parses as printed (#469): `Builder#build_joint_development` sets `lead_party "ISO"` whatever the publisher order, so `canonical_format` is `:iso`. PR #472 had made the lead the first printed publisher and rendered an IEEE-first form in the `D=` face — but the grammar artifact cannot parse most of those renders (`D=FCD`, `D=CD.2`, a non-P number, a month), and `D=CD-2020` parses into a *different* id (`ieee_draft`, no `iso_stage`), so 19 published `relaton-data-ieee` rows stopped matching themselves. The `D=` face now prints only for an id that carries a `D=` designator, or on an explicit `to_s(format: :ieee)`. `JointDevelopment#to_iso_format` prints `iso_stage` as stored (`DIS2`, `CD4` — #472's bare `CD` lost the iteration) and the date in the spelling that parses back (`#iso_date_suffix`: `:2018`, `-2018-05`, `, April 2019`). **The test is `parse(to_s) == id`, not a stable `to_s`**: a lossy render reaches a fixed point and round-trips its *hash* (`iso_stage_spellings_spec` held both while dropping `CD4`→`CD`), yet the reparse is a different identifier. Measured over the 118 joint stage rows of `relaton-data-ieee`: `to_s` stable 100 → 118, `==` 51 → 106; the other 12 carry an `ieee_draft` or an edition that the ISO face still drops. **PARG separator-in-capture, again (#471)**: the draft `revision` capture is `".12"`, and `Draft#to_s` adds its own dot, so `handle_draft` strips the leading dot (`D2022.10.12` rendered `D2022.10..12`). Check every capture a renderer re-joins with a separator. Locked by `spec/pubid/ieee/joint_stage_roundtrip_spec.rb` (stored index hashes → `parse(to_s) == row`).
@@ -861,26 +861,14 @@ module Pubid
861
861
 
862
862
  # Detect lead party based on pattern
863
863
  if parsed[:iso_stage]
864
- # ISO stage word present. The lead party is the PRINTED first
865
- # publisher (pubid#469: the arrangement is the organization's
866
- # perspective, shown by the print) — "IEEE/ISO/IEC CD P42010"
867
- # is IEEE-led even though the stage word is ISO's.
868
- attributes[:lead_party] = attributes[:publishers]&.first || "ISO"
864
+ # ISO stage word present: the reference is printed in the ISO
865
+ # position ("IEEE FCD 15026.3:2010"), so it parses as printed
866
+ # (pubid#469) - lead ISO, ISO face, whatever the publisher
867
+ # order. The printed first publisher is not the arrangement, and
868
+ # the published relaton-data-ieee rows carry lead ISO (pubid#477).
869
+ attributes[:lead_party] = "ISO"
869
870
  attributes[:iso_stage] = extract_value(parsed[:iso_stage])
870
871
 
871
- # A digit-suffixed stage word ("CD4", "DIS2") from a stage-FIRST
872
- # spelling prints bare in the ISO face (every
873
- # iso_stage_spellings_spec expectation: the iteration is draft
874
- # machinery, not identity). The embedded/stage-last form keeps
875
- # its "CD3" whole — its trailing token IS the identity the
876
- # embedded corpus keys on (embedded_iso_stage_spec) — and
877
- # serialized rows keep theirs verbatim (#477).
878
- # the marker is a zero-length capture: presence is the signal
879
- embedded = !parsed[:stage_embedded].nil?
880
- if !embedded && attributes[:iso_stage]&.match?(/\A[A-Z]+\d+\z/)
881
- attributes[:iso_stage] = attributes[:iso_stage][/\A[A-Z]+/]
882
- end
883
-
884
872
  # Create typed_stage for ISO stage
885
873
  stage_abbr = attributes[:iso_stage]
886
874
  if stage_abbr
@@ -1484,7 +1472,12 @@ module Pubid
1484
1472
  version = version.sub(/\A-/, "") if version
1485
1473
  end
1486
1474
 
1487
- revision = extract_value(draft_data[:revision]) if draft_data[:revision]
1475
+ # The PARG capture carries its separator (".12", the #471
1476
+ # convention); Draft#to_s prints the dot itself, so keep only
1477
+ # the digits or "D2022.10.12" renders "D2022.10..12" (pubid#477).
1478
+ if draft_data[:revision]
1479
+ revision = extract_value(draft_data[:revision])&.delete_prefix(".")
1480
+ end
1488
1481
 
1489
1482
  # The compound both-systems form (docs/IEEE-DRAFT-STAGES.md
1490
1483
  # §1.3): the stage half of "=DDIS.3".
@@ -69,6 +69,10 @@ module Pubid
69
69
  # Set lead_party if not provided - default to first publisher
70
70
  if args[:lead_party]
71
71
  self.lead_party = args[:lead_party]
72
+ elsif iso_stage
73
+ # A stage-first reference is lead ISO, as the builder sets it
74
+ # (pubid#477) — a hand-built id must agree with a parsed one.
75
+ self.lead_party = "ISO"
72
76
  elsif publishers && !publishers.empty?
73
77
  # Lead party defaults to first publisher if not explicitly set
74
78
  # Builder should override this with detected lead party
@@ -79,20 +83,10 @@ module Pubid
79
83
  # Canonical format based on lead party
80
84
  # @return [Symbol] :ieee or :iso
81
85
  def canonical_format
82
- # A PARSED stage-tracked D= designator is IEEE draft notation —
83
- # its canonical face is the designator spelling (§1.3 spelling 7:
84
- # "JOINT PNUMBER/D=<STAGE>[:year]"). Rows that carry an ISO stage
85
- # word (from_hash, or stage-led parses) keep the stage-word face
86
- # no matter what the render derives: their published spelling is
87
- # "IEEE FCD 15026.3:2010", not a designator (#477).
88
- if ieee_draft.to_s.start_with?("D=") && iso_stage.to_s.empty?
89
- return :ieee
90
- end
91
- # A stage-word row (parsed stage-led spelling, or a serialized
92
- # row carrying iso_stage) prints the stage-word face whatever
93
- # the lead party — the stage word is the printed identity
94
- # ("IEEE FCD 15026.3:2010", #477).
95
- return :iso unless iso_stage.to_s.empty?
86
+ # A stage-tracked D= designator is IEEE draft notation — its
87
+ # canonical face is the designator spelling (§1.3 spelling 7:
88
+ # "JOINT PNUMBER/D=<STAGE>[:year]"), whatever the lead party.
89
+ return :ieee if ieee_draft.to_s.start_with?("D=")
96
90
 
97
91
  case lead_party
98
92
  when "IEEE", "AIEE"
@@ -147,23 +141,14 @@ module Pubid
147
141
 
148
142
  # The stage word is the ISO format's own position convention —
149
143
  # it prints whenever the identifier carries one, regardless of
150
- # lead party (the format face decides, not the arrangement). A
151
- # stage-tracked D= designator decomposes into its stage word
152
- # and date ("D=CD.2-2020" → "CD2", 2020); the iteration glues
153
- # onto the word only for the stages whose printed ISO spellings
154
- # carry it (CD2, DIS2 — the grammar's multi-digit families);
155
- # the P project stage is IEEE convention and never prints here.
156
- # The stage word prints verbatim: parsed digit-suffixed
157
- # spellings ("CD4") are normalized to the bare word with the
158
- # iteration carried separately at build time, while serialized
159
- # rows keep the full "DIS2" — both must round-trip (#477).
160
- stage_word = iso_stage
161
- stage_word = nil if stage_word.to_s.empty?
162
- # A serialized row's stage iteration is identity: re-attach it
163
- # to the printed word ("DIS2 24748.4:2015", #477). The parse
164
- # path never sets stage_iteration for digit-suffixed spellings.
165
- iter = stage_iteration.respond_to?(:number) ? stage_iteration.number : stage_iteration
166
- stage_word = "#{stage_word}#{iter}" if stage_word && iter.to_s.match?(/\A\d+\z/)
144
+ # lead party (the format face decides, not the arrangement).
145
+ # The stage prints as stored, iteration included ("DIS2",
146
+ # "CD4"): the face is lossless, so parse(to_s) == self
147
+ # (pubid#477). A stage-tracked D= designator decomposes into its
148
+ # stage word and date ("D=CD-2020" → "CD", 2020); the P project
149
+ # stage is IEEE convention and never prints here.
150
+ stage_word = iso_stage.to_s
151
+ stage_word = nil if stage_word.empty?
167
152
  draft_year = nil
168
153
  if stage_word.nil? &&
169
154
  ieee_draft.to_s.match(/\AD=([A-Z]+)(?:\.(\d+[a-z]?))?(?:[-:](\d{4}))?\z/)
@@ -179,24 +164,33 @@ module Pubid
179
164
  code_str += mark unless code_str.empty?
180
165
  parts << code_str if code_str && !code_str.empty?
181
166
 
182
- # Join with space and add year with colon (the ISO position
183
- # convention; a decomposed D= date rides here as the year).
184
- result = parts.join(" ")
185
- result += ":#{year || draft_year}" if year || draft_year
167
+ # Join with space and add the date (a decomposed D= date rides
168
+ # here as the year).
169
+ result = parts.join(" ") + iso_date_suffix(year || draft_year)
186
170
  # Only the language/edition marker ("(E)", "(E/F)") prints; a
187
171
  # trailing relationship narrative is metadata, not identity.
188
172
  if parenthetical_content&.match?(%r{\A[A-Z](?:\s*[/&]\s*[A-Z])*\z})
189
173
  result += " (#{parenthetical_content})"
190
174
  end
191
175
 
192
- # The revision narrative prints after the code: published joint
193
- # rows carry it as identity spelling ("…:2012(E) Revision of
194
- # IEEE Std C37.082-1982", #477).
195
- result += " #{relationships.map(&:to_s).join(' ')}" if relationships && !relationships.empty?
196
-
197
176
  result
198
177
  end
199
178
 
179
+ # The ISO-face date, in the spelling that parses back to the same
180
+ # year and month: the colon year (":2018"); a numeric month glued
181
+ # with dashes ("-2018-05"); a text month as ", February 2015".
182
+ def iso_date_suffix(date_year)
183
+ return "" unless date_year
184
+ return ":#{date_year}" unless month
185
+
186
+ if month.match?(/\A\d+\z/)
187
+ # The grammar reads only a two-digit month ("05", never "5").
188
+ "-#{date_year}-#{month.rjust(2, '0')}"
189
+ else
190
+ ", #{month} #{date_year}"
191
+ end
192
+ end
193
+
200
194
  # Convert to IEEE format representation
201
195
  # ISO/IEC/IEEE P26511/D8-2018
202
196
  # @return [String] IEEE format string
@@ -215,8 +209,9 @@ module Pubid
215
209
 
216
210
  # Add IEEE draft notation if available (e.g., /D8). An ISO stage
217
211
  # word renders in IEEE's stage-tracked position as the ordinal-less
218
- # stage draft (docs/IEEE-DRAFT-STAGES.md §1.3, spelling 7):
219
- # "IEEE/ISO/IEC CD P42010:2020" → "IEEE/ISO/IEC P42010/D=CD-2020".
212
+ # stage draft (docs/IEEE-DRAFT-STAGES.md §1.3, spelling 7) — only
213
+ # on an explicit to_s(format: :ieee): a stage-first reference is
214
+ # lead ISO, so its canonical face is the ISO one (pubid#477).
220
215
  # The iso_stage branch is checked FIRST — the typed_stage registry
221
216
  # lookup for a stage word answers a draft-equivalent ordinal ("D2"),
222
217
  # which is not the canonical stage-tracked spelling.
@@ -230,9 +225,11 @@ module Pubid
230
225
  elsif ieee_draft
231
226
  code_str += "/#{ieee_draft}"
232
227
  elsif iso_stage
233
- # A stage word never converts to a designator on the IEEE
234
- # face: published stage rows print the stage-word face
235
- # ("IEEE FCD 15026.3:2010", #477).
228
+ # The ordinal-less stage draft: an iteration glued onto the
229
+ # printed word ("CD2") renders in the doctrine's ".iter" slot
230
+ # ("D=CD.2" — docs/IEEE-DRAFT-STAGES.md §1.3).
231
+ stage = iso_stage.match(/\A([A-Z]+?)(\d+)\z/)
232
+ code_str += stage ? "/D=#{stage[1]}.#{stage[2]}" : "/D=#{iso_stage}"
236
233
  elsif typed_stage&.ieee_draft_equivalent
237
234
  code_str += "/#{typed_stage.ieee_draft_equivalent}"
238
235
  end
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.0.pre.alpha.17"
4
+ VERSION = "2.0.0.pre.alpha.19"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.alpha.17
4
+ version: 2.0.0.pre.alpha.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 1.3.57
47
+ version: 1.3.62
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: 1.3.57
54
+ version: 1.3.62
55
55
  description: PubID is a Ruby library for parsing and generating international publication
56
56
  identifiers (PubIDs). It provides a simple and flexible API for working with PubIDs,
57
57
  allowing developers to easily create, manipulate, and validate PubIDs in their applications.