pubid 2.0.22 → 2.0.24

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/data/parg/calconnect_pfs.json +1 -0
  3. data/data/parg/cen_pfs.json +1 -0
  4. data/data/parg/doi_pfs.json +1 -0
  5. data/data/parg/etsi_pfs.json +1 -0
  6. data/data/parg/iec.json +1 -1
  7. data/data/parg/nist.json +1 -1
  8. data/lib/pubid/calconnect_pfs/builder.rb +35 -0
  9. data/lib/pubid/calconnect_pfs/identifier.rb +84 -0
  10. data/lib/pubid/calconnect_pfs/identifiers/standard.rb +31 -0
  11. data/lib/pubid/calconnect_pfs/identifiers.rb +7 -0
  12. data/lib/pubid/calconnect_pfs/parser.rb +10 -0
  13. data/lib/pubid/calconnect_pfs/pubid_urn_schema.rb +17 -0
  14. data/lib/pubid/calconnect_pfs/renderer.rb +10 -0
  15. data/lib/pubid/calconnect_pfs/urn_generator.rb +30 -0
  16. data/lib/pubid/calconnect_pfs.rb +60 -0
  17. data/lib/pubid/cen_pfs/builder.rb +33 -0
  18. data/lib/pubid/cen_pfs/identifier.rb +64 -0
  19. data/lib/pubid/cen_pfs/identifiers/european_norm.rb +31 -0
  20. data/lib/pubid/cen_pfs/identifiers.rb +7 -0
  21. data/lib/pubid/cen_pfs/parser.rb +10 -0
  22. data/lib/pubid/cen_pfs/pubid_urn_schema.rb +15 -0
  23. data/lib/pubid/cen_pfs/renderer.rb +10 -0
  24. data/lib/pubid/cen_pfs/urn_generator.rb +12 -0
  25. data/lib/pubid/cen_pfs.rb +71 -0
  26. data/lib/pubid/doi_pfs/CLAUDE.md +72 -0
  27. data/lib/pubid/doi_pfs/builder.rb +18 -0
  28. data/lib/pubid/doi_pfs/identifier.rb +43 -0
  29. data/lib/pubid/doi_pfs/identifiers/resource.rb +31 -0
  30. data/lib/pubid/doi_pfs/identifiers.rb +7 -0
  31. data/lib/pubid/doi_pfs/parser.rb +10 -0
  32. data/lib/pubid/doi_pfs/pubid_urn_schema.rb +15 -0
  33. data/lib/pubid/doi_pfs/renderer.rb +10 -0
  34. data/lib/pubid/doi_pfs/urn_generator.rb +12 -0
  35. data/lib/pubid/doi_pfs.rb +60 -0
  36. data/lib/pubid/etsi_pfs/CLAUDE.md +95 -0
  37. data/lib/pubid/etsi_pfs/builder.rb +25 -0
  38. data/lib/pubid/etsi_pfs/identifier.rb +50 -0
  39. data/lib/pubid/etsi_pfs/identifiers/standard.rb +22 -0
  40. data/lib/pubid/etsi_pfs/identifiers.rb +7 -0
  41. data/lib/pubid/etsi_pfs/parser.rb +10 -0
  42. data/lib/pubid/etsi_pfs/pubid_urn_schema.rb +19 -0
  43. data/lib/pubid/etsi_pfs/renderer.rb +10 -0
  44. data/lib/pubid/etsi_pfs/urn_generator.rb +12 -0
  45. data/lib/pubid/etsi_pfs.rb +59 -0
  46. data/lib/pubid/itu/CLAUDE.md +25 -14
  47. data/lib/pubid/itu/builder.rb +7 -5
  48. data/lib/pubid/itu/identifiers/special_publication.rb +20 -17
  49. data/lib/pubid/itu/urn_generator.rb +3 -4
  50. data/lib/pubid/nist/CLAUDE.md +36 -0
  51. data/lib/pubid/nist/builder.rb +10 -0
  52. data/lib/pubid/nist/identifiers/base.rb +10 -0
  53. data/lib/pubid/nist/identifiers/special_publication.rb +15 -0
  54. data/lib/pubid/nist/router.rb +11 -0
  55. data/lib/pubid/version.rb +1 -1
  56. data/lib/pubid.rb +17 -0
  57. metadata +44 -2
@@ -0,0 +1,35 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ class Builder
4
+ def self.build(parsed_data)
5
+ new.build(parsed_data)
6
+ end
7
+
8
+ def build(data)
9
+ Identifiers::Standard.new(
10
+ series: data[:series],
11
+ number: data[:number],
12
+ date: build_date(data),
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ # The publication date as the shared Components::Date, or
19
+ # nil when the reference omits the calendar fields (a
20
+ # partial reference matches by excluding the date).
21
+ def build_date(data)
22
+ values = [data[:year], data[:month], data[:day]]
23
+ return nil if values.compact.empty?
24
+
25
+ ::Pubid::Components::Date.new(
26
+ year: data[:year]&.to_s,
27
+ month: data[:month]&.to_s,
28
+ day: data[:day]&.to_s,
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ Pubid::CalconnectPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -0,0 +1,84 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ class Identifier < ::Pubid::Identifier
4
+ attribute :series, :string
5
+ attribute :number, :string
6
+ attribute :date, ::Pubid::Components::Date
7
+
8
+ CC_TYPE_MAP = {
9
+ "pubid:calconnect:standard" => "Pubid::CalconnectPfs::Identifiers::Standard",
10
+ }.freeze
11
+
12
+ key_value do
13
+ map "_type", to: :_type, polymorphic_map: CC_TYPE_MAP
14
+ map "series", to: :series
15
+ map "number", to: :number
16
+ map "year", with: { to: :year_to_kv, from: :year_from_kv }
17
+ map "month", with: { to: :month_to_kv, from: :month_from_kv }
18
+ map "day", with: { to: :day_to_kv, from: :day_from_kv }
19
+ end
20
+
21
+ # --- date serialized flat (the shared flat-scalar shape) ---
22
+ def year_to_kv(model, doc)
23
+ emit_date_part(doc, "year", model.date&.year)
24
+ end
25
+
26
+ def year_from_kv(model, value)
27
+ date_for(model).year = value.to_s
28
+ end
29
+
30
+ # --- date serialized flat (the shared flat-scalar shape) ---
31
+ def month_to_kv(model, doc)
32
+ emit_date_part(doc, "month", model.date&.month)
33
+ end
34
+
35
+ def month_from_kv(model, value)
36
+ date_for(model).month = value.to_s
37
+ end
38
+
39
+ # --- date serialized flat (the shared flat-scalar shape) ---
40
+ def day_to_kv(model, doc)
41
+ emit_date_part(doc, "day", model.date&.day)
42
+ end
43
+
44
+ def day_from_kv(model, value)
45
+ date_for(model).day = value.to_s
46
+ end
47
+
48
+ def emit_date_part(doc, key, val)
49
+ return if val.nil? || val.to_s.empty?
50
+
51
+ doc.add_child(Lutaml::KeyValue::DataModel::Element.new(key, val.to_s))
52
+ end
53
+
54
+ def date_for(model)
55
+ model.date ||= ::Pubid::Components::Date.new
56
+ end
57
+
58
+ PUBLISHER = "CC"
59
+
60
+ # The scheme's declared publisher token: the internal-form
61
+ # cut (to_s(with_publisher: false)) strips it from the
62
+ # rendered face.
63
+ def publisher_prefixes
64
+ [PUBLISHER]
65
+ end
66
+
67
+ def self.parse(identifier)
68
+ unless identifier.is_a?(String)
69
+ raise Pubid::Errors::InvalidInputError,
70
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
71
+ end
72
+
73
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
74
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
75
+ end
76
+
77
+ identifier = ::Pubid.utf8_input(identifier)
78
+
79
+ parsed = Pubid::Parg::Model.bound(:calconnect_pfs, identifier)
80
+ Builder.build(parsed)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ module Identifiers
4
+ # Concrete CalConnect Standard identifier type (PFS type `standard`).
5
+ class Standard < Identifier
6
+ TYPED_STAGES = [
7
+ Pubid::Components::TypedStage.new(
8
+ code: :pubcc,
9
+ stage_code: :published,
10
+ type_code: :cc,
11
+ abbr: ["CC"],
12
+ name: "CalConnect Standard",
13
+ harmonized_stages: [],
14
+ ),
15
+ ].freeze
16
+
17
+ def self.type
18
+ { key: :cc, web: :standard, title: "CalConnect Standard", short: "CC" }
19
+ end
20
+
21
+ # The scheme-canonical wire tag declared in the definition:
22
+ # to_hash stays byte-identical with the scheme's coded
23
+ # flavor, and this flavor's own from_hash routes the tag
24
+ # to this class.
25
+ def self.polymorphic_name
26
+ "pubid:calconnect:standard"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ module Identifiers
4
+ autoload :Standard, "#{__dir__}/identifiers/standard"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ # PFS-generated parser shell. Parsing runs through the baked
4
+ # PARG artifact (data/parg/calconnect_pfs.json) via Pubid::Parg::Model.bound;
5
+ # the grammar source lives in pubid-grammar
6
+ # (grammars/calconnect_pfs.parg, compiled from definitions/calconnect.pfs).
7
+ class Parser
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ # The pubid-scheme URN schema, registered under the compiled
4
+ # flavor key. The definition's declared namespace
5
+ # ("calconnect") is the scheme's canonical
6
+ # namespace; identifiers carry the scheme's wire tag, so their
7
+ # pubid URNs encode through the scheme's own schema.
8
+ PubidUrnSchema = Pubid::PubidUrn.define("calconnect_pfs") do
9
+ atom "series", "series"
10
+ atom "year", "year"
11
+ atom "month", "month"
12
+ atom "day", "day"
13
+ type "pubid:calconnect:identifier", "id", core: []
14
+ type "pubid:calconnect:standard", "std", core: ["number"]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ # Human renderer, generated from the canonical template.
4
+ class Renderer < ::Pubid::Renderers::Base
5
+ def render(**_opts)
6
+ "CC#{"/#{@id.series}" if @id.series} #{@id.number}#{":#{@id.date.year}#{"-#{@id.date.month}-#{@id.date.day}" if @id.date&.month && @id.date&.day}" if @id.date&.year}"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module Pubid
2
+ module CalconnectPfs
3
+ # The official URN generator: the scheme's declared namespace
4
+ # ("calconnect") and the ordered segments the
5
+ # definition's urn.official declares — an absent field leaves no
6
+ # segment, a date group joins its fields with "-".
7
+ class UrnGenerator < ::Pubid::UrnGenerator::Base
8
+ def urn_namespace
9
+ "calconnect"
10
+ end
11
+
12
+ def generate
13
+ parts = ["urn", urn_namespace]
14
+ parts.concat(official_segments)
15
+ parts.join(":")
16
+ end
17
+
18
+ private
19
+
20
+ def official_segments
21
+ segments = []
22
+ segments << @identifier.series if @identifier.series
23
+ segments << @identifier.number if @identifier.number
24
+ date = [@identifier.date&.year, @identifier.date&.month, @identifier.date&.day].compact
25
+ segments << date.join("-") unless date.empty?
26
+ segments
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # PFS-generated flavor — DO NOT EDIT BY HAND.
4
+ # Source: definitions/calconnect.pfs (pfs:1) sha256:2e864f0185a1a2f18a392f2f401ad3c9d65d1f32a9ec3f3f04f7dfca9b2226c4
5
+ # Compiler: pubid/grammar/pfs 0.1.0 (baked with parsanol 1.3.126)
6
+ # Regenerate: ruby scripts/pfs compile definitions/calconnect.pfs --as calconnect_pfs --ruby-out <dir>
7
+
8
+ require "pubid"
9
+
10
+ module Pubid
11
+ # CalConnect Standards flavor, compiled from its PFS definition
12
+ # (pubid/pubid#706). A compiled flavor is a peer of the coded
13
+ # flavors: same registry, same input guards, same error
14
+ # namespace, same URN contracts.
15
+ module CalconnectPfs
16
+ extend Pubid::PrefixesSupport
17
+
18
+ PREFIXES = ["CC"].freeze
19
+
20
+
21
+ autoload :Builder, "#{__dir__}/calconnect_pfs/builder"
22
+ autoload :Identifier, "#{__dir__}/calconnect_pfs/identifier"
23
+ autoload :Identifiers, "#{__dir__}/calconnect_pfs/identifiers"
24
+ autoload :Parser, "#{__dir__}/calconnect_pfs/parser"
25
+ autoload :Renderer, "#{__dir__}/calconnect_pfs/renderer"
26
+ autoload :PubidUrnSchema, "#{__dir__}/calconnect_pfs/pubid_urn_schema"
27
+ autoload :UrnGenerator, "#{__dir__}/calconnect_pfs/urn_generator"
28
+
29
+ def self.parse(identifier)
30
+ Identifier.parse(identifier)
31
+ end
32
+
33
+ Identifier.format_registry = FormatRegistry.new(parent: Identifier.format_registry)
34
+ Identifier.format_registry.register(:human, renderer: CalconnectPfs::Renderer)
35
+
36
+ def self.identifier_types
37
+ @identifier_types ||= Identifiers.constants
38
+ .filter_map { |c| begin; Identifiers.const_get(c); rescue NameError; nil; end }
39
+ .select { |c| c.is_a?(Class) && c.singleton_methods(false).include?(:type) }
40
+ .select { |c| c.type.is_a?(Hash) }
41
+ end
42
+
43
+ def self.all_typed_stages
44
+ @all_typed_stages ||= identifier_types.flat_map do |klass|
45
+ klass.const_defined?(:TYPED_STAGES) ? klass.const_get(:TYPED_STAGES) : []
46
+ end
47
+ end
48
+
49
+ def self.locate_type(code)
50
+ identifier_types.find { |t| t.type[:key].to_s == code.to_s }
51
+ end
52
+
53
+ def self.locate_stage(abbr)
54
+ abbr_str = abbr.to_s.upcase
55
+ all_typed_stages.find { |s| s.abbr.any? { |a| a.to_s.upcase == abbr_str } }
56
+ end
57
+ end
58
+ end
59
+
60
+ Pubid::Registry.register(:calconnect_pfs, Pubid::CalconnectPfs)
@@ -0,0 +1,33 @@
1
+ module Pubid
2
+ module CenPfs
3
+ class Builder
4
+ def self.build(parsed_data)
5
+ new.build(parsed_data)
6
+ end
7
+
8
+ def build(data)
9
+ Identifiers::EuropeanNorm.new(
10
+ number: data[:number],
11
+ part: data[:part],
12
+ date: build_date(data),
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ # The publication date as the shared Components::Date, or
19
+ # nil when the reference omits the calendar fields (a
20
+ # partial reference matches by excluding the date).
21
+ def build_date(data)
22
+ values = [data[:year]]
23
+ return nil if values.compact.empty?
24
+
25
+ ::Pubid::Components::Date.new(
26
+ year: data[:year]&.to_s,
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Pubid::CenPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -0,0 +1,64 @@
1
+ module Pubid
2
+ module CenPfs
3
+ class Identifier < ::Pubid::Identifier
4
+ attribute :number, :string
5
+ attribute :part, :string
6
+ attribute :date, ::Pubid::Components::Date
7
+
8
+ EN_TYPE_MAP = {
9
+ "pubid:cencenelec:european-norm" => "Pubid::CenPfs::Identifiers::EuropeanNorm",
10
+ }.freeze
11
+
12
+ key_value do
13
+ map "_type", to: :_type, polymorphic_map: EN_TYPE_MAP
14
+ map "number", to: :number
15
+ map "part", to: :part
16
+ map "year", with: { to: :year_to_kv, from: :year_from_kv }
17
+ end
18
+
19
+ # --- date serialized flat (the shared flat-scalar shape) ---
20
+ def year_to_kv(model, doc)
21
+ emit_date_part(doc, "year", model.date&.year)
22
+ end
23
+
24
+ def year_from_kv(model, value)
25
+ date_for(model).year = value.to_s
26
+ end
27
+
28
+ def emit_date_part(doc, key, val)
29
+ return if val.nil? || val.to_s.empty?
30
+
31
+ doc.add_child(Lutaml::KeyValue::DataModel::Element.new(key, val.to_s))
32
+ end
33
+
34
+ def date_for(model)
35
+ model.date ||= ::Pubid::Components::Date.new
36
+ end
37
+
38
+ PUBLISHER = "CEN"
39
+
40
+ # The scheme's declared publisher token: the internal-form
41
+ # cut (to_s(with_publisher: false)) strips it from the
42
+ # rendered face.
43
+ def publisher_prefixes
44
+ [PUBLISHER]
45
+ end
46
+
47
+ def self.parse(identifier)
48
+ unless identifier.is_a?(String)
49
+ raise Pubid::Errors::InvalidInputError,
50
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
51
+ end
52
+
53
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
54
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
55
+ end
56
+
57
+ identifier = ::Pubid.utf8_input(identifier)
58
+
59
+ parsed = Pubid::Parg::Model.bound(:cen_pfs, identifier)
60
+ Builder.build(parsed)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ module Pubid
2
+ module CenPfs
3
+ module Identifiers
4
+ # Concrete European Norm identifier type (PFS type `european_norm`).
5
+ class EuropeanNorm < Identifier
6
+ TYPED_STAGES = [
7
+ Pubid::Components::TypedStage.new(
8
+ code: :puben,
9
+ stage_code: :published,
10
+ type_code: :en,
11
+ abbr: ["EN"],
12
+ name: "European Norm",
13
+ harmonized_stages: [],
14
+ ),
15
+ ].freeze
16
+
17
+ def self.type
18
+ { key: :en, web: :"european-norm", title: "European Norm", short: "EN" }
19
+ end
20
+
21
+ # The scheme-canonical wire tag declared in the definition:
22
+ # to_hash stays byte-identical with the scheme's coded
23
+ # flavor, and this flavor's own from_hash routes the tag
24
+ # to this class.
25
+ def self.polymorphic_name
26
+ "pubid:cencenelec:european-norm"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module Pubid
2
+ module CenPfs
3
+ module Identifiers
4
+ autoload :EuropeanNorm, "#{__dir__}/identifiers/european_norm"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Pubid
2
+ module CenPfs
3
+ # PFS-generated parser shell. Parsing runs through the baked
4
+ # PARG artifact (data/parg/cen_pfs.json) via Pubid::Parg::Model.bound;
5
+ # the grammar source lives in pubid-grammar
6
+ # (grammars/cen_pfs.parg, compiled from definitions/cen.pfs).
7
+ class Parser
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Pubid
2
+ module CenPfs
3
+ # The pubid-scheme URN schema, registered under the compiled
4
+ # flavor key. The definition's declared namespace
5
+ # ("cen") is the scheme's canonical
6
+ # namespace; identifiers carry the scheme's wire tag, so their
7
+ # pubid URNs encode through the scheme's own schema.
8
+ PubidUrnSchema = Pubid::PubidUrn.define("cen_pfs") do
9
+ atom "part", "part"
10
+ atom "year", "year"
11
+ type "pubid:cencenelec:identifier", "id", core: []
12
+ type "pubid:cencenelec:european-norm", "en", core: ["number"]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module Pubid
2
+ module CenPfs
3
+ # Human renderer, generated from the canonical template.
4
+ class Renderer < ::Pubid::Renderers::Base
5
+ def render(**_opts)
6
+ "EN #{@id.number}#{"-#{@id.part}" if @id.part}#{":#{@id.date.year}" if @id.date&.year}"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Pubid
2
+ module CenPfs
3
+ # The official URN generator, pinned to the scheme's declared
4
+ # namespace ("cen") so to_urn stays
5
+ # scheme-canonical under the compiled flavor key.
6
+ class UrnGenerator < ::Pubid::UrnGenerator::Base
7
+ def urn_namespace
8
+ "cen"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # PFS-generated flavor — DO NOT EDIT BY HAND.
4
+ # Source: definitions/cen.pfs (pfs:1) sha256:bc1edd5874f8938b5f946416a2b052d9d9b9df3d99f6c20a3d88fcab9ae1d70c
5
+ # Compiler: pubid/grammar/pfs 0.1.0 (baked with parsanol 1.3.126)
6
+ # Regenerate: ruby scripts/pfs compile definitions/cen.pfs --as cen_pfs --ruby-out <dir>
7
+
8
+ require "pubid"
9
+
10
+ module Pubid
11
+ # CEN/CENELEC European Norms (module composition) flavor, compiled from its PFS definition
12
+ # (pubid/pubid#706). A compiled flavor is a peer of the coded
13
+ # flavors: same registry, same input guards, same error
14
+ # namespace, same URN contracts.
15
+ module CenPfs
16
+ extend Pubid::PrefixesSupport
17
+
18
+ PREFIXES = ["EN", "CEN/", "CLC/"].freeze
19
+ # The module composition the definition binds (issue #706 §4):
20
+ # each entry names the library module, the version it was bound
21
+ # at, and — where the module declares one — its IDENTITY
22
+ # composition (wrapper-with-adopted: the adopted document is
23
+ # the base). Carried so consumers and the wizard can
24
+ # introspect what the flavor is composed of.
25
+ MODULES = [
26
+ { name: "iso.inner", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
27
+ { name: "en.overlay", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
28
+ { name: "national.prefix", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
29
+ { name: "supplements.core", version: 1, identity: { composition: "wrapper-with-adopted", adopted_field: :base } },
30
+ ].freeze
31
+
32
+ autoload :Builder, "#{__dir__}/cen_pfs/builder"
33
+ autoload :Identifier, "#{__dir__}/cen_pfs/identifier"
34
+ autoload :Identifiers, "#{__dir__}/cen_pfs/identifiers"
35
+ autoload :Parser, "#{__dir__}/cen_pfs/parser"
36
+ autoload :Renderer, "#{__dir__}/cen_pfs/renderer"
37
+ autoload :PubidUrnSchema, "#{__dir__}/cen_pfs/pubid_urn_schema"
38
+ autoload :UrnGenerator, "#{__dir__}/cen_pfs/urn_generator"
39
+
40
+ def self.parse(identifier)
41
+ Identifier.parse(identifier)
42
+ end
43
+
44
+ Identifier.format_registry = FormatRegistry.new(parent: Identifier.format_registry)
45
+ Identifier.format_registry.register(:human, renderer: CenPfs::Renderer)
46
+
47
+ def self.identifier_types
48
+ @identifier_types ||= Identifiers.constants
49
+ .filter_map { |c| begin; Identifiers.const_get(c); rescue NameError; nil; end }
50
+ .select { |c| c.is_a?(Class) && c.singleton_methods(false).include?(:type) }
51
+ .select { |c| c.type.is_a?(Hash) }
52
+ end
53
+
54
+ def self.all_typed_stages
55
+ @all_typed_stages ||= identifier_types.flat_map do |klass|
56
+ klass.const_defined?(:TYPED_STAGES) ? klass.const_get(:TYPED_STAGES) : []
57
+ end
58
+ end
59
+
60
+ def self.locate_type(code)
61
+ identifier_types.find { |t| t.type[:key].to_s == code.to_s }
62
+ end
63
+
64
+ def self.locate_stage(abbr)
65
+ abbr_str = abbr.to_s.upcase
66
+ all_typed_stages.find { |s| s.abbr.any? { |a| a.to_s.upcase == abbr_str } }
67
+ end
68
+ end
69
+ end
70
+
71
+ Pubid::Registry.register(:cen_pfs, Pubid::CenPfs)
@@ -0,0 +1,72 @@
1
+ # DoiPfs — the PFS-compiled doi dev alias
2
+
3
+ This flavor is **generated**, not hand-written: every file here is compiler
4
+ output from `pubid-grammar`'s `definitions/doi.pfs` (pubid/pubid#706),
5
+ reproduce with
6
+
7
+ ```bash
8
+ cd ../pubid-grammar
9
+ ruby scripts/pfs compile definitions/doi.pfs --as doi_pfs \
10
+ --parsanol-version <CI pin> --ruby-out ../pubid/lib
11
+ cp artifacts/doi_pfs.json ../pubid/data/parg/doi_pfs.json
12
+ ```
13
+
14
+ DO NOT EDIT these files by hand — a hand edit is silently overwritten by
15
+ the next compile, and the grammar SSOT is the definition, not this module
16
+ (the `data/parg/doi_pfs.json` artifact's freshness envelope and the
17
+ definition's source sha pin the lineage).
18
+
19
+ ## The coexistence semantics (read before touching anything)
20
+
21
+ `doi_pfs` is the **dev-alias key** of the doi scheme: it registers beside
22
+ the coded `Pubid::Doi` so the compiled pipeline can be proven against the
23
+ coded flavor without cutover (cutover — replacing `doi` with the compiled
24
+ flavor — is a separate, later decision).
25
+
26
+ - **Wire tag**: the definition declares `tag: "pubid:doi:resource"` — the
27
+ SCHEME's canonical tag — so `to_hash` is byte-identical with the coded
28
+ flavor. `Resource.polymorphic_name` overrides the class-name-derived
29
+ tag for exactly this reason. Consequence: a GLOBAL `Pubid.from_hash` of
30
+ a doi_pfs hash resolves the coded class (one scheme, one `_type`, one
31
+ global resolution); the alias's own `DoiPfs::Identifier.from_hash`
32
+ routes the tag back to `DoiPfs::Identifiers::Resource` through its
33
+ `DOI_TYPE_MAP`.
34
+ - **URN namespace**: the definition declares `urn.namespace: doi`, so the
35
+ generated `UrnGenerator` pins `urn_namespace` to `"doi"` and `to_urn`
36
+ is byte-identical (`"urn:doi"`). The pubid-scheme schema registers
37
+ under the FLAVOR key (`PubidUrn.define("doi_pfs")`) — never re-register
38
+ under `"doi"`, that would overwrite the coded flavor's registration.
39
+ - **Routing**: `Pubid.parse` keeps routing `doi:`-faced strings to the
40
+ coded flavor (registry keys sort `"doi"` first; both flavors claim the
41
+ same prefixes). The alias is reached through `Pubid::DoiPfs.parse`.
42
+
43
+ ## The parity gate
44
+
45
+ `spec/pubid/doi_pfs/parity_spec.rb` is the spike's whole point: over the
46
+ full doi inventory (coded fixtures + corpus rows + aliases + constructed
47
+ edge cases), both flavors must agree byte-for-byte on `to_s`, `to_hash`,
48
+ `to_urn`, `to_mr_string`, `to_pubid_urn` and fail on the same inputs with
49
+ `Pubid::Errors::ParseError`. Drift is a defect in the definition or the
50
+ compiler — never relax an expectation here.
51
+
52
+ Non-obvious inventory facts, learned the hard way:
53
+
54
+ - The combined faces (`https://doi.org/doi:10.1000/182`) are rejected by
55
+ the CODED flavor too (the optional faces compose non-backtracking), so
56
+ they are negatives.
57
+ - `Pubid.parse("10.1000/182")` routes to **UN** on main, pre-existing:
58
+ the bare `10.` form is not a doi prefix-owner claim at a token
59
+ boundary (`10.` followed by a digit), and un's token run accepts it.
60
+ Only the scheme-faced form routes to Doi.
61
+ - The coded builder re-adds `"10."` to the registrar digits its grammar
62
+ captures; the doi.pfs pattern captures the whole `10.` face, so the
63
+ doi_pfs builder binds `prefix` directly. Model-level outputs are
64
+ identical; only the bound wire map differs (`prefix: "1000"` vs
65
+ `"10.1000"` in the artifact's corpus).
66
+
67
+ ## v1 boundaries
68
+
69
+ Single-type schemes only, `kind: string` fields only, no free-standing
70
+ `stages:`/`supplements:` sections, no semantic decode rules (the
71
+ DAM-family problem) — see `pubid-grammar`'s `definitions/README.md` for
72
+ the language reference and the fail-closed validation rules.
@@ -0,0 +1,18 @@
1
+ module Pubid
2
+ module DoiPfs
3
+ class Builder
4
+ def self.build(parsed_data)
5
+ new.build(parsed_data)
6
+ end
7
+
8
+ def build(data)
9
+ Identifiers::Resource.new(
10
+ prefix: data[:prefix],
11
+ suffix: data[:suffix],
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Pubid::DoiPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)