pubid 2.0.0.pre.alpha.28 → 2.0.0.pre.alpha.29

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.
@@ -511,8 +511,15 @@ module Pubid
511
511
  # @param parsed_hash [Hash] parsed data with base and supplement info
512
512
  # @return [Identifiers::Corrigendum] corrigendum identifier
513
513
  def build_corrigendum_supplement(parsed_hash)
514
+ # The `corrigendum_identifier` rule's :base subtree binds as a
515
+ # top-level sequence (an Array of capture hashes — the same shape
516
+ # #build's dispatch merges at line 24) whenever the base carries a
517
+ # multi-capture narrative tail; fold it before reconstruction or
518
+ # reconstruct_base_string's Hash indexing crashes with TypeError.
519
+ base_data = parsed_hash[:base]
520
+ base_data = merge_parsed_array(base_data) if base_data.is_a?(Array)
514
521
  # Recursively parse the base identifier reconstructed from the subtree.
515
- base = Identifier.parse(reconstruct_base_string(parsed_hash[:base]))
522
+ base = Identifier.parse(reconstruct_base_string(base_data))
516
523
 
517
524
  # Extract corrigendum attributes (parse-tree keys are cor_*; the
518
525
  # identifier stores them as the uniform `number`/`year`).
@@ -873,6 +880,10 @@ module Pubid
873
880
  end
874
881
  attributes[:parenthetical_content] ||= extract_value(parsed[:edition_marker]) if parsed[:edition_marker]
875
882
 
883
+ # A joint ISO-print redline is a distinct document of the same
884
+ # standard — same flag semantics as the generic path (pubid#215).
885
+ attributes[:redline] = true if parsed[:redline]
886
+
876
887
  # Extract edition, from relaton's "/E-<n>" suffix. The grammar's /E
877
888
  # alternative captures the bare ordinal (nil-residue hand-off item 1).
878
889
  if parsed[:edition]
@@ -1589,9 +1600,29 @@ module Pubid
1589
1600
  # Redline
1590
1601
  attributes[:redline] = true if parsed[:redline]
1591
1602
 
1603
+ mark_glued_month(attributes)
1592
1604
  attributes
1593
1605
  end
1594
1606
 
1607
+ # Glued identity year-month provenance (pubid#517): when the input's
1608
+ # tail is the glued "-YYYY-MM" and the identity carries no draft or
1609
+ # revision suffix, the month belongs to the code's date clause
1610
+ # ("ANSI/IEEE 21-1976-11", "IEEE Std PC2-2016-01") and the renderer
1611
+ # reproduces the glued face. The same month/year attribute shape is
1612
+ # produced by trailing print dates (", 05 2014" — whose numeric form
1613
+ # is its own documented law) and by dash-promoted draft tails
1614
+ # ("/R-x-2011-04"), both of which keep the comma face: the input
1615
+ # shape, checked here at the single choke point every assignment
1616
+ # path converges to, is what distinguishes them (the AIEE
1617
+ # date_separator precedent).
1618
+ def mark_glued_month(attributes)
1619
+ return unless attributes[:month] && attributes[:year] &&
1620
+ attributes[:draft].nil? && attributes[:revision].nil? &&
1621
+ original_input.to_s.match?(/-\d{4}-\d{2}\z/)
1622
+
1623
+ attributes[:month_glued] = true
1624
+ end
1625
+
1595
1626
  # Determine stage abbreviation for TYPED_STAGE lookup
1596
1627
  # @param type_value [String] the type value (e.g., "Std", "P")
1597
1628
  # @param draft_status_value [String] the draft status (e.g., "Unapproved")
@@ -38,6 +38,12 @@ module Pubid
38
38
  attribute :revision, :string
39
39
  attribute :edition, :string # Edition 1.0
40
40
  attribute :month, :string
41
+ # Provenance flag (pubid#517): the month arrived glued to the identity
42
+ # year on the code ("ANSI/IEEE 21-1976-11", grammar
43
+ # date_with_month_numeric) rather than as a trailing ", 11 1976"
44
+ # print date. Serialization round-trips it so to_s reproduces the
45
+ # glued face.
46
+ attribute :month_glued, :boolean, default: -> { false }
41
47
  attribute :day, :string
42
48
  attribute :redline, :boolean, default: -> { false }
43
49
  attribute :amendments, Identifier, collection: true # Amendment identifiers
@@ -235,6 +241,7 @@ module Pubid
235
241
  Identifiers::Nesc::Draft,
236
242
  Identifiers::Nesc::Edition,
237
243
  Identifiers::Nesc::Handbook,
244
+ Identifiers::Nesc::Preprint,
238
245
  Identifiers::Nesc::Redline,
239
246
  Identifiers::Nesc::Standard,
240
247
  ]
@@ -177,6 +177,9 @@ module Pubid
177
177
  if parenthetical_content&.match?(%r{\A[A-Z](?:\s*[/&]\s*[A-Z])*\z})
178
178
  result += " (#{parenthetical_content})"
179
179
  end
180
+ # The redline marker is identity — the joint ISO print spells it
181
+ # with the space-dash form ("15289:2015(E) - Redline", pubid#215).
182
+ result += " - Redline" if redline
180
183
 
181
184
  result
182
185
  end
@@ -250,6 +253,7 @@ module Pubid
250
253
  result = parts.join(" ")
251
254
  result += "-#{year}" if year && !@designator_carries_year &&
252
255
  !ieee_draft.to_s.end_with?("-#{year}")
256
+ result += " - Redline" if redline
253
257
 
254
258
  result
255
259
  end
@@ -79,7 +79,7 @@ module Pubid
79
79
  #
80
80
  # @return [Boolean]
81
81
  def draft?
82
- is_a?(Nesc::Draft)
82
+ is_a?(Nesc::Draft) || is_a?(Nesc::Preprint)
83
83
  end
84
84
 
85
85
  # Rendering for year-first NESC identifiers (the C2-code standard form
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ieee
5
+ module Identifiers
6
+ module Nesc
7
+ # Preprint NESC identifier — the draft stage preceding a C2 edition.
8
+ # "Preprint" is a draft stage (the NESC's own terminology for its
9
+ # pre-edition drafts), not a variant marker: the document IS a draft
10
+ # of the C2 code, distinct from the published edition.
11
+ #
12
+ # @example
13
+ # nesc = Pubid::Ieee.parse("IEEE Std C2.2012.Preprint")
14
+ # nesc.to_s # => "IEEE Std C2.2012.Preprint"
15
+ class Preprint < Base
16
+ include Pubid::Ieee::Identifiers::CodeNumber
17
+
18
+ def self.polymorphic_name
19
+ "pubid:ieee:nesc-preprint"
20
+ end
21
+
22
+ def draft?
23
+ true
24
+ end
25
+
26
+ # Render preprint identifier. The canonical spelling is dotted
27
+ # (C2.2012.Preprint); the dash spelling is a non-normalized alias.
28
+ #
29
+ # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
30
+ # @return [String] IEEE Std C2.YYYY.Preprint
31
+ def to_s(trademark: false, **opts)
32
+ body = ["C2", year, "Preprint"].compact.join(".")
33
+ result = ["IEEE Std", body].join(" ")
34
+ result += trademark_symbol if trademark
35
+ annotate_plain_render(result, **opts)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -8,6 +8,7 @@ module Pubid
8
8
  autoload :Draft, "#{__dir__}/nesc/draft"
9
9
  autoload :Edition, "#{__dir__}/nesc/edition"
10
10
  autoload :Handbook, "#{__dir__}/nesc/handbook"
11
+ autoload :Preprint, "#{__dir__}/nesc/preprint"
11
12
  autoload :Redline, "#{__dir__}/nesc/redline"
12
13
  autoload :Standard, "#{__dir__}/nesc/standard"
13
14
  end
@@ -88,6 +88,11 @@ module Pubid
88
88
  # @param parsed_hash [Hash] Parsed attributes
89
89
  # @return [Class] Identifier class to instantiate
90
90
  def determine_identifier_class(parsed_hash)
91
+ # Preprint — a draft STAGE of the C2 code (checked before the
92
+ # Standard route below: the c2-preprint tree carries :code "C2"
93
+ # too, and the verbose proposal forms carry none).
94
+ return Identifiers::Nesc::Preprint if parsed_hash[:preprint]
95
+
91
96
  # Draft identifiers
92
97
  return Identifiers::Nesc::Draft if parsed_hash[:draft]
93
98
 
@@ -735,10 +735,19 @@ module Pubid
735
735
  (slash >> str("Amd") >> (dot | space).maybe >>
736
736
  digits.as(:amd_number) >>
737
737
  ((str(":") | dash) >> year_digits.as(:amd_year)).maybe).maybe >>
738
+ # Optional corrigendum tail, the ISO print's dotted spelling
739
+ # (pubid#215): "8802-1AC:2018/Cor.1:2020(E)". The shared
740
+ # corrigendum rule accepts the slash/dot/colon separators; the
741
+ # flat tree routes through build_flat_corrigendum.
742
+ corrigendum.maybe >>
738
743
  # Optional edition, from relaton's "/E-<n>" suffix normalized to
739
744
  # "Edition <n>.0[ YYYY]" (nil-residue hand-off item 1).
740
745
  edition.maybe >>
741
746
  revision_suffix.maybe >>
747
+ # A redline is a distinct document of the joint ISO print
748
+ # (pubid#215): "15289:2015(E) - Redline". Same flag semantics as
749
+ # the generic rules - the builder sets redline: true.
750
+ redline.maybe >>
742
751
  # Published joint docs print a language marker, after either the
743
752
  # year or the amendment tail: "9945:2009(E)", "...Amd.1:2014(E)".
744
753
  # The crawl sometimes spaces it ("…(E), January 2017" family,
@@ -1465,10 +1474,6 @@ module Pubid
1465
1474
  cleaned = "IEEE P#{cleaned}"
1466
1475
  end
1467
1476
 
1468
- # Part D: Suffix Normalization
1469
- # 5. "/Preprint" -> remove (data quality - not standard suffix)
1470
- cleaned = cleaned.gsub(/\/Preprint\b/, "")
1471
-
1472
1477
  # Part E: Relationship Text Normalization
1473
1478
  # 6. "Proposed Revision of" -> "Revision of"
1474
1479
  cleaned = cleaned.gsub("Proposed Revision of", "Revision of")
@@ -187,9 +187,17 @@ module Pubid
187
187
  # no draft at all, or a draft that carries its OWN date (the
188
188
  # draft+corrigendum family: "PC37.09-1999/D7.2, Nov 2006" -
189
189
  # the 1999 is the base's year, the Nov 2006 the draft's).
190
- year_on_code = id.year && !id.edition && !id.month &&
190
+ # A numeric identity month is the SHORT format: it keeps the
191
+ # year on the code and appends itself dash-joined, whatever the
192
+ # input spelling ("ANSI/IEEE 21-1976-11" and "IEEE P15026.1,
193
+ # 05 2014" both render glued - pubid#517). The comma face is
194
+ # the LONG format, a month name ("..., May 2014").
195
+ glued_month = id.month.to_s.match?(/\A\d{1,2}\z/)
196
+ year_on_code = id.year && !id.edition &&
197
+ (!id.month || glued_month) &&
191
198
  (!id.draft_obj || id.draft_obj&.year)
192
199
  result += "-#{id.year}" if year_on_code
200
+ result += "-#{id.month}" if glued_month
193
201
 
194
202
  # Append the numbered/lettered revision inline, in IEEE's native
195
203
  # position — right after the code number and before the draft
@@ -263,7 +271,8 @@ module Pubid
263
271
  # An exactly-IEC/IEEE co-published reference drops the comma date
264
272
  # entirely (same catalogue-metadata rule as the draft date above):
265
273
  # "IEC/IEEE P60076-16, May 2016" prints as "IEC/IEEE 60076-16".
266
- if id.month && !(id.publisher == "IEC" && id.copublisher == ["IEEE"])
274
+ if id.month && !glued_month &&
275
+ !(id.publisher == "IEC" && id.copublisher == ["IEEE"])
267
276
  result += ", #{id.month}"
268
277
  result += " #{id.day}" if id.day
269
278
  if id.year && !id.edition
@@ -40,6 +40,13 @@ module Pubid
40
40
  parts << "int"
41
41
  end
42
42
 
43
+ # A NESC preprint is a draft-stage document distinct from the
44
+ # published edition it precedes — without this component it would
45
+ # share the edition's URN (urn:ieee:ieee:C2:2012).
46
+ if identifier.is_a?(Identifiers::Nesc::Preprint)
47
+ parts << "preprint"
48
+ end
49
+
43
50
  # Conformance is always a ConformanceIdentifier wrapper (special_type
44
51
  # "conformance"), never inline on a base standard — the base no longer
45
52
  # carries a conf_number attribute, so there is no inline conf part.
@@ -138,7 +138,11 @@ module Pubid
138
138
  # Two-letter suffixes: Ur (Unclassified Revised), Ua (Unclassified Amended), Ub-Uj (series variants)
139
139
  # Single letter: any letter not followed by excluded keywords
140
140
  rule(:number_suffix) do
141
- (str("U") >> lower_letter) | (match("[a-zA-Z]") >> (
141
+ (str("U") >> lower_letter) |
142
+ # An "r" directly followed by a digit is the revision marker (pubid#170),
143
+ # not a suffix — leave it for the second_number/edition rules.
144
+ # The guard sits before the letter so it tests the suffix start itself.
145
+ ((str("r") >> digit).absent? >> match("[a-zA-Z]") >> (
142
146
  # Match suffixes
143
147
  str("ec") |
144
148
  str("ndex") |
@@ -265,7 +269,9 @@ module Pubid
265
269
  (digits >> str("r") >> digits >> match("[a-zA-Z]")) |
266
270
  # NEW: Revision pattern with year (e.g., "126r2013")
267
271
  # This handles SP revision format where revision is attached to second_number
268
- (digits.as(:number_only) >> str("r") >> digits.as(:edition_id)) |
272
+ # Dotted minor accepted atomically (pubid#170): "53r4.1" binds id "4.1"
273
+ (digits.as(:number_only) >> str("r") >>
274
+ (digits >> (str(".") >> digits).maybe).as(:edition_id)) |
269
275
  # CRPL range with underscore (e.g., "2_3-1A")
270
276
  (digits >> str("_") >> digits >> dash >> digits >> upper_letter.maybe) |
271
277
  # Letter followed by dash and digits (e.g., "m-5")
@@ -283,7 +289,9 @@ module Pubid
283
289
  # For patterns like NIST SP 800-27ra where revision 'ra' is attached directly to number
284
290
  (str("r") >> match("[a-zA-Z]")).as(:revision_letter_suffix) |
285
291
  # NEW: Simple revision pattern r followed by digits (e.g., "r1", "r2") for trailing revision
286
- (str("r") >> digits.as(:edition_id)).as(:revision_simple) |
292
+ # Dotted minor accepted atomically (pubid#170): "r4.1" binds id "4.1"
293
+ (str("r") >>
294
+ (digits >> (str(".") >> digits).maybe).as(:edition_id)).as(:revision_simple) |
287
295
  # Special patterns like "NCNR", "PERMIS", "BFRL"
288
296
  str("NCNR") | str("PERMIS") | str("BFRL") |
289
297
  # Just capital letters (e.g., "A", "B", "C") - standalone
@@ -303,15 +311,22 @@ module Pubid
303
311
  # Enhanced: Support space-separated format from preprocessing (r1 separated from number)
304
312
  rule(:edition) do
305
313
  # Edition with "e" prefix: e2, e3, e2021 (1-4 digits for ID)
306
- (space.maybe >> str("e") >> digits.as(:edition_id)).as(:edition_e) |
314
+ # Dotted minor accepted atomically (pubid#170): "e2006.1" binds id "2006.1"
315
+ (space.maybe >> str("e") >>
316
+ (digits >> (str(".") >> digits).maybe).as(:edition_id)).as(:edition_e) |
307
317
  # Revision with "r" prefix and SPACE, with letter: r 5A (preserve format)
308
- (space >> str("r") >> digits.as(:edition_id) >> match("[a-zA-Z]").as(:edition_letter)).as(:edition_r_with_space_letter) |
318
+ # Dotted minor accepted atomically (pubid#170)
319
+ (space >> str("r") >> (digits >> (str(".") >> digits).maybe).as(:edition_id) >>
320
+ match("[a-zA-Z]").as(:edition_letter)).as(:edition_r_with_space_letter) |
309
321
  # Revision with "r" prefix and SPACE: r 5 (preserve format)
310
- (space >> str("r") >> digits.as(:edition_id)).as(:edition_r_with_space) |
322
+ (space >> str("r") >>
323
+ (digits >> (str(".") >> digits).maybe).as(:edition_id)).as(:edition_r_with_space) |
311
324
  # Revision with "r" prefix NO space, with letter: r5A (compact format)
312
- (str("r") >> digits.as(:edition_id) >> match("[a-zA-Z]").as(:edition_letter)).as(:edition_r_no_space_letter) |
325
+ (str("r") >> (digits >> (str(".") >> digits).maybe).as(:edition_id) >>
326
+ match("[a-zA-Z]").as(:edition_letter)).as(:edition_r_no_space_letter) |
313
327
  # Revision with "r" prefix NO space: r5 (compact format)
314
- (str("r") >> digits.as(:edition_id)).as(:edition_r_no_space) |
328
+ (str("r") >>
329
+ (digits >> (str(".") >> digits).maybe).as(:edition_id)).as(:edition_r_no_space) |
315
330
  # Revision with "rev" prefix (verbose): rev2013, rev 2013
316
331
  (space.maybe >> str("rev") >> space.maybe >> digits.as(:edition_id)).as(:edition_rev) |
317
332
  # Historical with "-" prefix: -2, -3 (ONLY if followed by non-digit or end)
@@ -76,6 +76,7 @@ module Pubid
76
76
  normalize_supplement_and_part!
77
77
  normalize_version_notation!
78
78
  normalize_edition_year_suffix!
79
+ normalize_glued_dotted_edition!
79
80
  normalize_revision_with_letter!
80
81
  normalize_version_dotted_spaces!
81
82
  normalize_update_markers!
@@ -264,10 +265,24 @@ module Pubid
264
265
  end
265
266
 
266
267
  # Edition year suffix shorthand: "2006ed." → "e2006".
268
+ # A dotted minor after the dot stays attached (pubid#170): "2006ed.1" → "e2006.1",
269
+ # so the edition id binds atomically instead of corrupting into "e20061".
267
270
  def normalize_edition_year_suffix!
271
+ @cleaned = @cleaned.gsub(/(\d{4})ed\.(\d+)/, 'e\1.\2')
268
272
  @cleaned = @cleaned.gsub(/(\d{4})ed\./, 'e\1')
269
273
  end
270
274
 
275
+ # A glued dotted e-edition needs the space the spaced form has (pubid#170):
276
+ # "800-53e2.1" → "800-53 e2.1", so the edition rule binds "2.1" atomically.
277
+ # The minor is 1-2 digits — a 4-digit tail is the additional-text year of
278
+ # forms like "NBS CIRC 11e2.1915", which must keep its existing tree.
279
+ # Only dotted e-editions are unglued; the dot-less glued form "800-53e2"
280
+ # keeps its number-bound tree. Mirrors the "v1.1" unglue in
281
+ # normalize_draft_and_volume!.
282
+ def normalize_glued_dotted_edition!
283
+ @cleaned = @cleaned.gsub(/(\d)e(\d+\.\d{1,2})(?!\d)/, '\1 e\2')
284
+ end
285
+
271
286
  # Revision attached to a number with optional letter suffix. When
272
287
  # a letter suffix is present, keep them together for the
273
288
  # second_number grammar rule; otherwise insert a space before
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.28"
4
+ VERSION = "2.0.0.pre.alpha.29"
5
5
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.alpha.28
4
+ version: 2.0.0.pre.alpha.29
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-08 00:00:00.000000000 Z
11
+ date: 2026-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parslet
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -640,6 +640,7 @@ files:
640
640
  - lib/pubid/ieee/identifiers/nesc/draft.rb
641
641
  - lib/pubid/ieee/identifiers/nesc/edition.rb
642
642
  - lib/pubid/ieee/identifiers/nesc/handbook.rb
643
+ - lib/pubid/ieee/identifiers/nesc/preprint.rb
643
644
  - lib/pubid/ieee/identifiers/nesc/redline.rb
644
645
  - lib/pubid/ieee/identifiers/nesc/standard.rb
645
646
  - lib/pubid/ieee/identifiers/parenthetical_identifier.rb