pubid 2.0.0.pre.alpha.3 → 2.0.0.pre.alpha.4

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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pubid/amca/identifier.rb +4 -23
  3. data/lib/pubid/amca/identifiers/base.rb +67 -30
  4. data/lib/pubid/amca/identifiers/interpretation.rb +0 -4
  5. data/lib/pubid/amca/identifiers/publication.rb +0 -4
  6. data/lib/pubid/amca/identifiers/standard.rb +0 -4
  7. data/lib/pubid/amca/renderer.rb +1 -1
  8. data/lib/pubid/amca/urn_generator.rb +2 -1
  9. data/lib/pubid/api/builder.rb +4 -0
  10. data/lib/pubid/api/identifier.rb +5 -1
  11. data/lib/pubid/api/identifiers/base.rb +2 -12
  12. data/lib/pubid/api/identifiers/mpms.rb +0 -4
  13. data/lib/pubid/api/identifiers/typeless_standard.rb +0 -3
  14. data/lib/pubid/api/single_identifier.rb +5 -8
  15. data/lib/pubid/ashrae/identifier.rb +7 -22
  16. data/lib/pubid/ashrae/identifiers/base.rb +28 -17
  17. data/lib/pubid/ashrae/parser.rb +4 -0
  18. data/lib/pubid/ashrae.rb +4 -0
  19. data/lib/pubid/asme/single_identifier.rb +3 -1
  20. data/lib/pubid/astm/single_identifier.rb +3 -1
  21. data/lib/pubid/bsi/identifier.rb +5 -31
  22. data/lib/pubid/bsi/identifiers/addendum_document.rb +0 -4
  23. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +0 -4
  24. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +0 -4
  25. data/lib/pubid/bsi/identifiers/aerospace_standard.rb +0 -3
  26. data/lib/pubid/bsi/identifiers/amendment.rb +0 -4
  27. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +0 -3
  28. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +2 -5
  29. data/lib/pubid/bsi/identifiers/committee_document.rb +0 -3
  30. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +2 -5
  31. data/lib/pubid/bsi/identifiers/corrigendum.rb +0 -4
  32. data/lib/pubid/bsi/identifiers/detailed_specification.rb +0 -3
  33. data/lib/pubid/bsi/identifiers/disc.rb +0 -3
  34. data/lib/pubid/bsi/identifiers/draft_document.rb +0 -3
  35. data/lib/pubid/bsi/identifiers/electronic_book.rb +0 -3
  36. data/lib/pubid/bsi/identifiers/expert_commentary.rb +0 -4
  37. data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +0 -3
  38. data/lib/pubid/bsi/identifiers/flex.rb +0 -3
  39. data/lib/pubid/bsi/identifiers/handbook.rb +0 -3
  40. data/lib/pubid/bsi/identifiers/index.rb +0 -3
  41. data/lib/pubid/bsi/identifiers/method.rb +0 -3
  42. data/lib/pubid/bsi/identifiers/national_annex.rb +0 -3
  43. data/lib/pubid/bsi/identifiers/practice_guide.rb +0 -3
  44. data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +0 -3
  45. data/lib/pubid/bsi/identifiers/published_document.rb +0 -3
  46. data/lib/pubid/bsi/identifiers/section.rb +0 -3
  47. data/lib/pubid/bsi/identifiers/set.rb +2 -5
  48. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +0 -3
  49. data/lib/pubid/bsi/identifiers/supplement_document.rb +0 -4
  50. data/lib/pubid/bsi/identifiers/supplementary_index.rb +0 -3
  51. data/lib/pubid/bsi/identifiers/technical_specification.rb +0 -3
  52. data/lib/pubid/bsi/identifiers/test_method.rb +0 -3
  53. data/lib/pubid/bsi/identifiers/value_added_publication.rb +0 -4
  54. data/lib/pubid/bsi/single_identifier.rb +32 -13
  55. data/lib/pubid/bsi.rb +8 -1
  56. data/lib/pubid/ccsds/identifier.rb +2 -14
  57. data/lib/pubid/cen_cenelec/identifier.rb +10 -8
  58. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +0 -4
  59. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +0 -4
  60. data/lib/pubid/cen_cenelec/identifiers/base.rb +2 -9
  61. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +0 -4
  62. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +0 -4
  63. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +0 -3
  64. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +0 -4
  65. data/lib/pubid/cen_cenelec/single_identifier.rb +4 -7
  66. data/lib/pubid/cen_cenelec.rb +4 -0
  67. data/lib/pubid/cie/identifier.rb +4 -0
  68. data/lib/pubid/cie/single_identifier.rb +4 -4
  69. data/lib/pubid/cie.rb +4 -0
  70. data/lib/pubid/csa/identifier.rb +10 -1
  71. data/lib/pubid/csa/identifiers/base.rb +0 -3
  72. data/lib/pubid/csa/identifiers/cec.rb +0 -4
  73. data/lib/pubid/csa/identifiers/series.rb +0 -4
  74. data/lib/pubid/csa/single_identifier.rb +1 -1
  75. data/lib/pubid/csa.rb +4 -0
  76. data/lib/pubid/etsi/components/code.rb +1 -7
  77. data/lib/pubid/etsi/components/version.rb +0 -5
  78. data/lib/pubid/etsi/identifier.rb +4 -12
  79. data/lib/pubid/etsi/identifiers/base.rb +31 -24
  80. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +0 -3
  81. data/lib/pubid/etsi.rb +3 -1
  82. data/lib/pubid/identifier.rb +90 -1
  83. data/lib/pubid/idf/identifier.rb +4 -0
  84. data/lib/pubid/idf.rb +4 -0
  85. data/lib/pubid/iec/identifier.rb +14 -14
  86. data/lib/pubid/iec/identifiers/base.rb +0 -4
  87. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +0 -4
  88. data/lib/pubid/iec/identifiers/fragment_identifier.rb +1 -5
  89. data/lib/pubid/iec/identifiers/sheet_identifier.rb +1 -5
  90. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -3
  91. data/lib/pubid/iec/identifiers/vap_identifier.rb +1 -5
  92. data/lib/pubid/iec/identifiers/working_document.rb +0 -4
  93. data/lib/pubid/iec.rb +4 -0
  94. data/lib/pubid/ieee/identifier.rb +5 -17
  95. data/lib/pubid/ieee/identifiers/adopted_standard.rb +0 -4
  96. data/lib/pubid/ieee/identifiers/base.rb +223 -201
  97. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +0 -4
  98. data/lib/pubid/ieee/identifiers/corrigendum.rb +0 -4
  99. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +0 -4
  100. data/lib/pubid/ieee/identifiers/dual_identifier.rb +0 -4
  101. data/lib/pubid/ieee/identifiers/dual_published.rb +0 -4
  102. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +0 -4
  103. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +0 -4
  104. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +0 -4
  105. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +0 -4
  106. data/lib/pubid/ieee/identifiers/redlined_standard.rb +0 -4
  107. data/lib/pubid/ieee/identifiers/si_standard.rb +0 -4
  108. data/lib/pubid/ieee.rb +4 -0
  109. data/lib/pubid/iho/identifier.rb +7 -25
  110. data/lib/pubid/iho/identifiers/base.rb +62 -68
  111. data/lib/pubid/iho.rb +3 -1
  112. data/lib/pubid/iso/identifier.rb +15 -14
  113. data/lib/pubid/iso/single_identifier.rb +0 -4
  114. data/lib/pubid/iso.rb +4 -0
  115. data/lib/pubid/itu/components/code.rb +1 -7
  116. data/lib/pubid/itu/components/sector.rb +15 -4
  117. data/lib/pubid/itu/components/series.rb +0 -4
  118. data/lib/pubid/itu/identifier.rb +4 -18
  119. data/lib/pubid/itu/identifiers/base.rb +124 -122
  120. data/lib/pubid/itu.rb +3 -1
  121. data/lib/pubid/jcgm/identifiers/amendment.rb +0 -4
  122. data/lib/pubid/jcgm/identifiers/gum_guide.rb +0 -4
  123. data/lib/pubid/jcgm/single_identifier.rb +0 -4
  124. data/lib/pubid/jis/identifier.rb +2 -14
  125. data/lib/pubid/nist/identifier.rb +8 -23
  126. data/lib/pubid/nist/identifiers/base.rb +553 -544
  127. data/lib/pubid/nist/preprocessor.rb +12 -0
  128. data/lib/pubid/nist.rb +4 -0
  129. data/lib/pubid/oiml/builder.rb +21 -3
  130. data/lib/pubid/oiml/components/code.rb +7 -0
  131. data/lib/pubid/oiml/identifier.rb +44 -0
  132. data/lib/pubid/oiml/identifiers/annex.rb +10 -1
  133. data/lib/pubid/oiml/identifiers/errata.rb +15 -0
  134. data/lib/pubid/oiml/identifiers.rb +1 -1
  135. data/lib/pubid/oiml/parser.rb +59 -12
  136. data/lib/pubid/oiml/renderer.rb +27 -17
  137. data/lib/pubid/oiml/single_identifier.rb +58 -0
  138. data/lib/pubid/oiml/supplement_identifier.rb +30 -1
  139. data/lib/pubid/plateau/identifier.rb +4 -21
  140. data/lib/pubid/plateau/identifiers/base.rb +45 -36
  141. data/lib/pubid/plateau/identifiers/handbook.rb +0 -4
  142. data/lib/pubid/plateau/identifiers/technical_report.rb +0 -4
  143. data/lib/pubid/plateau/supplement_identifier.rb +0 -4
  144. data/lib/pubid/sae/identifier.rb +4 -12
  145. data/lib/pubid/sae/identifiers/base.rb +26 -17
  146. data/lib/pubid/version.rb +1 -1
  147. data/lib/pubid.rb +18 -2
  148. data/lib/tasks/export.rake +6 -5
  149. metadata +3 -4
  150. data/lib/pubid/identifier_facade.rb +0 -114
  151. data/lib/pubid/oiml/identifiers/base.rb +0 -21
@@ -2,79 +2,73 @@
2
2
 
3
3
  module Pubid
4
4
  module Iho
5
- module Identifiers
6
- # Base class for all IHO identifiers.
7
- #
8
- # IHO identifiers have the form:
9
- # IHO {S|P|M|B|C}-{number}[ Ap. {appendix}][ Part {part}][ Annex {annex}][ Suppl {supplement}][ {version}]
10
- #
11
- # The leading IHO publisher prefix is optional on input but always
12
- # emitted on output.
13
- class Base < Pubid::Identifier
14
- # Identity for the Pubid::Iho::Identifier facade (see its comment).
15
- include Pubid::Iho::Identifier
16
-
17
- # `number` overrides the generic Components::Code attribute with a plain
18
- # string (same pattern as `part` below); it holds the IHO document
19
- # number (e.g. "100", "1/21"). Kept as the generic `number` so the
20
- # relaton index sorts/binary-searches on it natively via `id.number`.
21
- attribute :number, :string
22
- attribute :publisher, :string, default: "IHO"
23
- attribute :appendix, :string
24
- attribute :part, :string
25
- attribute :annex, :string
26
- attribute :supplement, :string
27
- attribute :version, :string
5
+ # Base class for all IHO identifiers. Canonical name Pubid::Iho::Identifier;
6
+ # every concrete IHO identifier (Identifiers::*) descends from it, and
7
+ # Identifiers::Base — aliased at the foot of this file — points back to it.
8
+ #
9
+ # IHO identifiers have the form:
10
+ # IHO {S|P|M|B|C}-{number}[ Ap. {appendix}][ Part {part}][ Annex {annex}][ Suppl {supplement}][ {version}]
11
+ #
12
+ # The leading IHO publisher prefix is optional on input but always
13
+ # emitted on output.
14
+ class Identifier < ::Pubid::Identifier
15
+ # Parse an IHO identifier string into an identifier object
16
+ # @param identifier [String] The IHO identifier string to parse
17
+ # @return [Pubid::Iho::Identifier] The appropriate identifier object
18
+ # @raise [Parslet::ParseFailed] If parsing fails
19
+ def self.parse(identifier)
20
+ parsed = Parser.parse(identifier)
21
+ Builder.build(parsed)
22
+ rescue Parslet::ParseFailed => e
23
+ raise "Failed to parse IHO identifier '#{identifier}': #{e.message}"
24
+ end
28
25
 
29
- # Polymorphic type map for lutaml::Model key_value serialization: maps
30
- # each subclass's polymorphic_name to its class name so `from_hash` can
31
- # re-instantiate the concrete type from `_type`.
32
- IHO_TYPE_MAP = {
33
- "pubid:iho:standard" => "Pubid::Iho::Identifiers::Standard",
34
- "pubid:iho:publication" => "Pubid::Iho::Identifiers::Publication",
35
- "pubid:iho:miscellaneous" => "Pubid::Iho::Identifiers::Miscellaneous",
36
- "pubid:iho:bibliographic" => "Pubid::Iho::Identifiers::Bibliographic",
37
- "pubid:iho:circular-letter" => "Pubid::Iho::Identifiers::CircularLetter",
38
- }.freeze
26
+ # `number` overrides the generic Components::Code attribute with a plain
27
+ # string (same pattern as `part` below); it holds the IHO document
28
+ # number (e.g. "100", "1/21"). Kept as the generic `number` so the
29
+ # relaton index sorts/binary-searches on it natively via `id.number`.
30
+ attribute :number, :string
31
+ attribute :publisher, :string, default: "IHO"
32
+ attribute :appendix, :string
33
+ attribute :part, :string
34
+ attribute :annex, :string
35
+ attribute :supplement, :string
36
+ attribute :version, :string
39
37
 
40
- # The base Pubid::Identifier no longer auto-maps attributes, so each
41
- # flavor's top class must declare its own key_value mapping. IHO
42
- # attributes are plain strings, so they map directly. `publisher` is
43
- # intentionally omitted: it is always the "IHO" default and is restored
44
- # on load.
45
- key_value do
46
- map "_type", to: :_type, polymorphic_map: IHO_TYPE_MAP
47
- map "number", to: :number
48
- map "appendix", to: :appendix
49
- map "part", to: :part
50
- map "annex", to: :annex
51
- map "supplement", to: :supplement
52
- map "version", to: :version
53
- end
38
+ # Polymorphic type map for lutaml::Model key_value serialization: maps
39
+ # each subclass's polymorphic_name to its class name so `from_hash` can
40
+ # re-instantiate the concrete type from `_type`.
41
+ IHO_TYPE_MAP = {
42
+ "pubid:iho:standard" => "Pubid::Iho::Identifiers::Standard",
43
+ "pubid:iho:publication" => "Pubid::Iho::Identifiers::Publication",
44
+ "pubid:iho:miscellaneous" => "Pubid::Iho::Identifiers::Miscellaneous",
45
+ "pubid:iho:bibliographic" => "Pubid::Iho::Identifiers::Bibliographic",
46
+ "pubid:iho:circular-letter" => "Pubid::Iho::Identifiers::CircularLetter",
47
+ }.freeze
54
48
 
55
- # Re-instantiate the concrete subclass from `_type`. lutaml's
56
- # polymorphic_map only validates `_type` on key_value deserialization;
57
- # without this routing `from_hash` returns a bare Base (no #type, which
58
- # breaks #to_s). Mirrors Pubid::Iso::Identifier.from_hash.
59
- def self.from_hash(data, options = {})
60
- klass_name = IHO_TYPE_MAP[data["_type"] || data[:_type]]
61
- if klass_name
62
- klass = Object.const_get(klass_name)
63
- return klass.from_hash(data, options) unless klass == self
64
- end
65
- super
66
- end
49
+ # The base Pubid::Identifier no longer auto-maps attributes, so each
50
+ # flavor's top class must declare its own key_value mapping. IHO
51
+ # attributes are plain strings, so they map directly. `publisher` is
52
+ # intentionally omitted: it is always the "IHO" default and is restored
53
+ # on load.
54
+ key_value do
55
+ map "_type", to: :_type, polymorphic_map: IHO_TYPE_MAP
56
+ map "number", to: :number
57
+ map "appendix", to: :appendix
58
+ map "part", to: :part
59
+ map "annex", to: :annex
60
+ map "supplement", to: :supplement
61
+ map "version", to: :version
62
+ end
67
63
 
68
- def self.parse(string)
69
- Iho::Identifier.parse(string)
70
- end
64
+ # from_hash is the shared polymorphic dispatch on Pubid::Identifier.
65
+ # IHO_TYPE_MAP remains as the key_value polymorphic_map.
66
+ end
71
67
 
72
- # Render the identifier as a string in canonical IHO form.
73
- # @return [String]
74
- def to_s
75
- render(format: :human)
76
- end
77
- end
68
+ module Identifiers
69
+ # Backward-compatible alias: IHO's base class used to be
70
+ # Pubid::Iho::Identifiers::Base. It is now Pubid::Iho::Identifier.
71
+ Base = Pubid::Iho::Identifier
78
72
  end
79
73
  end
80
74
  end
data/lib/pubid/iho.rb CHANGED
@@ -27,7 +27,9 @@ module Pubid
27
27
  @identifier_types ||= Identifiers.constants
28
28
  .filter_map { |c| begin; Identifiers.const_get(c); rescue NameError; nil; end }
29
29
  .select { |c| c.is_a?(Class) && c < Pubid::Identifier }
30
- .reject { |c| c.name&.split("::")&.last == "Base" }
30
+ # Exclude the flavor base itself by identity (Identifiers::Base is now an
31
+ # alias for Pubid::Iho::Identifier, so name-based matching misses it).
32
+ .reject { |c| c == Identifiers::Base }
31
33
  end
32
34
 
33
35
  # Build typed stage index from identifier types
@@ -85,6 +85,14 @@ module Pubid
85
85
  "pubid:iso:bundled-identifier" => "Pubid::Iso::BundledIdentifier",
86
86
  }.freeze
87
87
 
88
+ # BundledIdentifier lives outside Pubid::Iso::Identifiers but carries a
89
+ # polymorphic `_type`, so the shared polymorphic_type_map needs it
90
+ # explicitly (see Pubid::Identifier#additional_identifier_classes).
91
+ def self.additional_identifier_classes
92
+ [Pubid::Iso::BundledIdentifier]
93
+ end
94
+ private_class_method :additional_identifier_classes
95
+
88
96
  # Build type map from Pubid::Iso.identifier_types for validation
89
97
  def self.build_type_map
90
98
  Pubid::Iso.identifier_types.to_h do |klass|
@@ -259,6 +267,10 @@ module Pubid
259
267
  end
260
268
 
261
269
  def self.parse(string, format: :auto)
270
+ if string.length > Pubid::MAX_INPUT_LENGTH
271
+ raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
272
+ end
273
+
262
274
  format = Pubid::FormatDetector.detect(string) if format == :auto
263
275
 
264
276
  case format
@@ -277,20 +289,9 @@ module Pubid
277
289
  end
278
290
  end
279
291
 
280
- # lutaml's polymorphic key_value mapping reads `_type` only to validate;
281
- # it does not re-instantiate the concrete subclass on deserialization. So
282
- # `Identifier.from_hash(corrigendum_hash)` would return a bare Identifier
283
- # and drop `base_identifier`. Route by `_type` to the right subclass and
284
- # let its (inherited) from_hash do the real work, mirroring JIS.
285
- def self.from_hash(data, options = {})
286
- type = data["_type"] || data[:_type]
287
- klass_name = ISO_TYPE_MAP[type]
288
- if klass_name
289
- klass = Object.const_get(klass_name)
290
- return klass.from_hash(data, options) unless klass == self
291
- end
292
- super
293
- end
292
+ # from_hash is the shared polymorphic dispatch on Pubid::Identifier, which
293
+ # routes `_type` through polymorphic_type_map (ISO_TYPE_MAP's dynamic
294
+ # equivalent). ISO_TYPE_MAP remains as the key_value polymorphic_map.
294
295
  end
295
296
  end
296
297
  end
@@ -3,10 +3,6 @@
3
3
  module Pubid
4
4
  module Iso
5
5
  class SingleIdentifier < Identifier
6
- def to_s(**opts)
7
- render(format: :human, **opts)
8
- end
9
-
10
6
  private
11
7
 
12
8
  def build_rendering_context(_renderer, format:, with_edition: false,
data/lib/pubid/iso.rb CHANGED
@@ -25,6 +25,10 @@ module Pubid
25
25
  # @param format [Symbol] :auto, :human, :mr_string, or :urn
26
26
  # @return [Identifier] the parsed identifier
27
27
  def self.parse(identifier, format: :auto)
28
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
29
+ raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
30
+ end
31
+
28
32
  format = Pubid::FormatDetector.detect(identifier) if format == :auto
29
33
 
30
34
  case format
@@ -15,13 +15,7 @@ module Pubid
15
15
  class Code < Lutaml::Model::Serializable
16
16
  attribute :number, :string
17
17
  attribute :subseries, :string
18
- attribute :parts, :string, collection: true
19
-
20
- def initialize(number:, subseries: nil, parts: nil)
21
- @number = number
22
- @subseries = subseries
23
- @parts = parts || []
24
- end
18
+ attribute :parts, :string, collection: true, default: []
25
19
 
26
20
  def to_s
27
21
  result = number.to_s
@@ -12,12 +12,23 @@ module Pubid
12
12
 
13
13
  VALID_SECTORS = %w[R T D].freeze
14
14
 
15
- def initialize(sector:)
16
- unless VALID_SECTORS.include?(sector.to_s.upcase)
17
- raise ArgumentError, "Invalid sector: #{sector}. Must be R, T, or D"
15
+ # Accept either a positional Hash (lutaml-model's call style:
16
+ # `Sector.new({lutaml_register: :default})` then attribute assignment)
17
+ # or a literal `sector:` kwarg (`Sector.new(sector: "R")`). Both call
18
+ # paths funnel through the same validate-then-set flow so the attribute
19
+ # stays lutaml-tracked and round-trips through to_hash/from_hash.
20
+ def initialize(attrs = {}, options = {})
21
+ super
22
+ sector_val = attrs[:sector] || attrs["sector"]
23
+ return if sector_val.nil?
24
+
25
+ normalized = sector_val.to_s.upcase
26
+ unless VALID_SECTORS.include?(normalized)
27
+ raise ArgumentError,
28
+ "Invalid sector: #{sector_val}. Must be R, T, or D"
18
29
  end
19
30
 
20
- @sector = sector.to_s.upcase
31
+ self.sector = normalized
21
32
  end
22
33
 
23
34
  def to_s
@@ -10,10 +10,6 @@ module Pubid
10
10
  class Series < Lutaml::Model::Serializable
11
11
  attribute :series, :string # e.g., BO, V, X, R, SG1, OB
12
12
 
13
- def initialize(series:)
14
- @series = series
15
- end
16
-
17
13
  def to_s
18
14
  series
19
15
  end
@@ -1,20 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Pubid
4
- module Itu
5
- # NOTE: intentionally does NOT `extend Pubid::IdentifierFacade`. ITU's
6
- # `to_hash` currently raises (publisher stored as a String, not a Publisher
7
- # component), so enabling the facade's identity check would route ITU ids
8
- # through a consumer's `to_hash` (e.g. relaton-index#save) and crash.
9
- # Re-enable once ITU serialization round-trips cleanly. See
10
- # identifier_facade.rb.
11
- module Identifier
12
- def self.parse(identifier)
13
- parsed = Parser.parse(identifier)
14
- Builder.build(parsed)
15
- rescue Parslet::ParseFailed => e
16
- raise "Failed to parse ITU identifier '#{identifier}': #{e.message}"
17
- end
18
- end
19
- end
20
- end
3
+ # Pubid::Itu::Identifier is the ITU base class (a real Pubid::Identifier
4
+ # subclass); its body, `.parse`, and the Identifiers::Base back-compat alias
5
+ # live in identifiers/base.rb. This file just ensures it is loaded.
6
+ require_relative "identifiers/base"
@@ -2,147 +2,149 @@
2
2
 
3
3
  module Pubid
4
4
  module Itu
5
- module Identifiers
6
- # Base class for all ITU identifiers
7
- class Base < Pubid::Identifier
8
- # Long-form ITU single-letter language code map. The parser produces
9
- # single-letter codes (E/F/S/R/A/C); API callers (e.g. metanorma-itu)
10
- # pass long-form (en/fr/es/ru/ar/zh). Storage is normalized to the
11
- # single-letter form. Languages with no canonical letter (e.g. "de")
12
- # pass through unchanged and produce no trailing suffix.
13
- LANGUAGES = {
14
- "fr" => "F", "es" => "S", "ru" => "R",
15
- "ar" => "A", "zh" => "C", "en" => "E",
16
- "F" => "F", "S" => "S", "R" => "R",
17
- "A" => "A", "C" => "C", "E" => "E"
18
- }.freeze
19
-
20
- attribute :sector, Pubid::Itu::Components::Sector
21
- attribute :series, Pubid::Itu::Components::Series
22
- attribute :code, Pubid::Itu::Components::Code
23
- attribute :date, Pubid::Components::Date
24
- attribute :language, :string
25
-
26
- def initialize(**kwargs)
27
- if kwargs[:language]
28
- kwargs = kwargs.merge(language: normalize_language(kwargs[:language]))
29
- end
30
-
31
- super
32
-
33
- validate_ob_no_sector!
34
- end
5
+ # Base class for all ITU identifiers. Canonical name Pubid::Itu::Identifier
6
+ # (Identifiers::Base is a back-compat alias).
7
+ class Identifier < ::Pubid::Identifier
8
+ # Parse an ITU identifier string into an identifier object.
9
+ def self.parse(identifier)
10
+ parsed = Parser.parse(identifier)
11
+ Builder.build(parsed)
12
+ rescue Parslet::ParseFailed => e
13
+ raise "Failed to parse ITU identifier '#{identifier}': #{e.message}"
14
+ end
35
15
 
36
- # Generate URN for this identifier
37
- #
38
- # @return [String] URN representation
39
-
40
- # Override base_hash to handle ITU-specific attributes
41
- def base_hash
42
- hash = super
43
- # ITU Series has a 'series' attribute, not 'number'
44
- if hash[:series].is_a?(Hash) && series
45
- hash[:series] = series.series
46
- end
47
- # Add sector (ITU-specific, has a 'sector' attribute)
48
- hash[:sector] = sector.sector if sector
49
- hash
16
+ # Long-form ITU single-letter language code map. The parser produces
17
+ # single-letter codes (E/F/S/R/A/C); API callers (e.g. metanorma-itu)
18
+ # pass long-form (en/fr/es/ru/ar/zh). Storage is normalized to the
19
+ # single-letter form. Languages with no canonical letter (e.g. "de")
20
+ # pass through unchanged and produce no trailing suffix.
21
+ LANGUAGES = {
22
+ "fr" => "F", "es" => "S", "ru" => "R",
23
+ "ar" => "A", "zh" => "C", "en" => "E",
24
+ "F" => "F", "S" => "S", "R" => "R",
25
+ "A" => "A", "C" => "C", "E" => "E"
26
+ }.freeze
27
+
28
+ attribute :sector, Pubid::Itu::Components::Sector
29
+ attribute :series, Pubid::Itu::Components::Series
30
+ attribute :code, Pubid::Itu::Components::Code
31
+ attribute :date, Pubid::Components::Date
32
+ attribute :language, :string
33
+
34
+ def initialize(**kwargs)
35
+ if kwargs[:language]
36
+ kwargs = kwargs.merge(language: normalize_language(kwargs[:language]))
50
37
  end
51
38
 
52
- def publisher
53
- "ITU"
54
- end
39
+ super
55
40
 
56
- # Render identifier as a string.
57
- #
58
- # @param i18n_lang [Symbol, String] language for identifier text
59
- # translation (e.g. :fr renders "Annex to" as "Annexe au"). Distinct
60
- # from the identifier's `language` attribute, which is the document's
61
- # own language and produces the trailing suffix like "-F".
62
- # @param language [Symbol, String] deprecated alias for i18n_lang.
63
- # @param format [Symbol] :long for title-style rendering of supported
64
- # identifiers, otherwise the default short form.
65
- def to_s(**opts)
66
- opts = self.class.normalize_to_s_opts(opts)
67
- render_base(**opts) + render_language_suffix
68
- end
41
+ validate_ob_no_sector!
42
+ end
69
43
 
70
- def render_base(**_opts)
71
- result = "#{publisher}-#{sector}"
44
+ # Generate URN for this identifier
45
+ #
46
+ # @return [String] URN representation
47
+
48
+ # Stored as a plain string (always "ITU") so it round-trips through
49
+ # to_hash/from_hash. Was a `def publisher` method, which made lutaml
50
+ # serialize a String against the Components::Publisher attribute.
51
+ attribute :publisher, :string, default: -> { "ITU" }
52
+
53
+ # Render identifier as a string.
54
+ #
55
+ # @param i18n_lang [Symbol, String] language for identifier text
56
+ # translation (e.g. :fr renders "Annex to" as "Annexe au"). Distinct
57
+ # from the identifier's `language` attribute, which is the document's
58
+ # own language and produces the trailing suffix like "-F".
59
+ # @param language [Symbol, String] deprecated alias for i18n_lang.
60
+ # @param format [Symbol] :long for title-style rendering of supported
61
+ # identifiers, otherwise the default short form.
62
+ def to_s(**opts)
63
+ opts = self.class.normalize_to_s_opts(opts)
64
+ render_base(**opts) + render_language_suffix
65
+ end
72
66
 
73
- # Add series and code
74
- result += if series
75
- " #{series}.#{code}"
76
- else
77
- " #{code}"
78
- end
67
+ def render_base(**_opts)
68
+ result = "#{publisher}-#{sector}"
79
69
 
80
- # Add date if present
81
- if date
82
- result += if date.month
83
- " (#{date.month}/#{date.year})"
84
- else
85
- " (#{date.year})"
86
- end
87
- end
70
+ # Add series and code
71
+ result += if series
72
+ " #{series}.#{code}"
73
+ else
74
+ " #{code}"
75
+ end
88
76
 
89
- result
77
+ # Add date if present
78
+ if date
79
+ result += if date.month
80
+ " (#{date.month}/#{date.year})"
81
+ else
82
+ " (#{date.year})"
83
+ end
90
84
  end
91
85
 
92
- def render_language_suffix
93
- return "" unless language
94
- # Only render canonical single-letter ITU language codes
95
- # (e.g. "F", "S"). Languages with no mapping (e.g. "de") get
96
- # no suffix — matches v1 PR #38 render_language behavior.
97
- return "" unless LANGUAGES.value?(language)
86
+ result
87
+ end
88
+
89
+ def render_language_suffix
90
+ return "" unless language
91
+ # Only render canonical single-letter ITU language codes
92
+ # (e.g. "F", "S"). Languages with no mapping (e.g. "de") get
93
+ # no suffix — matches v1 PR #38 render_language behavior.
94
+ return "" unless LANGUAGES.value?(language)
98
95
 
99
- "-#{language}"
100
- end
96
+ "-#{language}"
97
+ end
101
98
 
102
- # Translate `language:` opt to `i18n_lang:` opt. v1 PR #38 introduced
103
- # `i18n_lang:` to disambiguate "rendering language" from the document
104
- # language attribute; `language:` remains a deprecated alias.
105
- def self.normalize_to_s_opts(opts)
106
- opts = opts.dup
107
- if opts.key?(:language) && !opts.key?(:i18n_lang)
108
- opts[:i18n_lang] = opts.delete(:language)
109
- else
110
- opts.delete(:language)
111
- end
112
- opts
99
+ # Translate `language:` opt to `i18n_lang:` opt. v1 PR #38 introduced
100
+ # `i18n_lang:` to disambiguate "rendering language" from the document
101
+ # language attribute; `language:` remains a deprecated alias.
102
+ def self.normalize_to_s_opts(opts)
103
+ opts = opts.dup
104
+ if opts.key?(:language) && !opts.key?(:i18n_lang)
105
+ opts[:i18n_lang] = opts.delete(:language)
106
+ else
107
+ opts.delete(:language)
113
108
  end
109
+ opts
110
+ end
114
111
 
115
- def ==(other)
116
- return false unless other.is_a?(Base)
112
+ def ==(other)
113
+ return false unless other.is_a?(Pubid::Itu::Identifier)
117
114
 
118
- sector == other.sector &&
119
- series == other.series &&
120
- code == other.code &&
121
- date == other.date &&
122
- language == other.language
123
- end
115
+ sector == other.sector &&
116
+ series == other.series &&
117
+ code == other.code &&
118
+ date == other.date &&
119
+ language == other.language
120
+ end
124
121
 
125
- private
122
+ private
126
123
 
127
- def normalize_language(value)
128
- str = value.to_s
129
- LANGUAGES[str] || str
130
- end
124
+ def normalize_language(value)
125
+ str = value.to_s
126
+ LANGUAGES[str] || str
127
+ end
131
128
 
132
- # OB (Operational Bulletin) is a cross-bureau ITU publication and
133
- # must not have a sector. Direct construction with both raises;
134
- # the parser silently drops sector for legacy strings like
135
- # "ITU-T OB.X" (handled in Builder).
136
- def validate_ob_no_sector!
137
- return unless series&.series == "OB"
138
- return if sector.nil?
139
- return if sector.is_a?(Components::Sector) && (sector.sector.nil? || sector.sector.to_s.empty?)
140
-
141
- raise ArgumentError,
142
- "OB (Operational Bulletin) is a cross-bureau ITU publication; " \
143
- "sector must not be set"
144
- end
129
+ # OB (Operational Bulletin) is a cross-bureau ITU publication and
130
+ # must not have a sector. Direct construction with both raises;
131
+ # the parser silently drops sector for legacy strings like
132
+ # "ITU-T OB.X" (handled in Builder).
133
+ def validate_ob_no_sector!
134
+ return unless series&.series == "OB"
135
+ return if sector.nil?
136
+ return if sector.is_a?(Components::Sector) && (sector.sector.nil? || sector.sector.to_s.empty?)
137
+
138
+ raise ArgumentError,
139
+ "OB (Operational Bulletin) is a cross-bureau ITU publication; " \
140
+ "sector must not be set"
145
141
  end
146
142
  end
143
+
144
+ module Identifiers
145
+ # Backward-compatible alias: ITU's base class used to be
146
+ # Pubid::Itu::Identifiers::Base. It is now Pubid::Itu::Identifier.
147
+ Base = Pubid::Itu::Identifier
148
+ end
147
149
  end
148
150
  end
data/lib/pubid/itu.rb CHANGED
@@ -27,7 +27,9 @@ module Pubid
27
27
  @identifier_types ||= Identifiers.constants
28
28
  .filter_map { |c| begin; Identifiers.const_get(c); rescue NameError; nil; end }
29
29
  .select { |c| c.is_a?(Class) && c < Pubid::Identifier }
30
- .reject { |c| c.name&.split("::")&.last == "Base" }
30
+ # Exclude the flavor base itself by identity (Identifiers::Base is now an
31
+ # alias for Pubid::Itu::Identifier, so name-based matching misses it).
32
+ .reject { |c| c == Identifiers::Base }
31
33
  end
32
34
 
33
35
  # Build typed stage index from identifier types
@@ -20,10 +20,6 @@ module Pubid
20
20
  def self.type
21
21
  { key: :amendment, title: "Amendment", short: "Amd" }
22
22
  end
23
-
24
- def to_s(**opts)
25
- render(format: :human, **opts)
26
- end
27
23
  end
28
24
  end
29
25
  end
@@ -22,10 +22,6 @@ module Pubid
22
22
  { key: :gum_guide, title: "GUM Guide", short: "GUM" }
23
23
  end
24
24
 
25
- def to_s(**opts)
26
- render(format: :human, **opts)
27
- end
28
-
29
25
  private
30
26
 
31
27
  def language_portion
@@ -35,10 +35,6 @@ module Pubid
35
35
  ")",
36
36
  ].join
37
37
  end
38
-
39
- def to_s
40
- render(format: :human)
41
- end
42
38
  end
43
39
  end
44
40
  end