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.
- checksums.yaml +4 -4
- data/data/parg/calconnect_pfs.json +1 -0
- data/data/parg/cen_pfs.json +1 -0
- data/data/parg/doi_pfs.json +1 -0
- data/data/parg/etsi_pfs.json +1 -0
- data/data/parg/iec.json +1 -1
- data/data/parg/nist.json +1 -1
- data/lib/pubid/calconnect_pfs/builder.rb +35 -0
- data/lib/pubid/calconnect_pfs/identifier.rb +84 -0
- data/lib/pubid/calconnect_pfs/identifiers/standard.rb +31 -0
- data/lib/pubid/calconnect_pfs/identifiers.rb +7 -0
- data/lib/pubid/calconnect_pfs/parser.rb +10 -0
- data/lib/pubid/calconnect_pfs/pubid_urn_schema.rb +17 -0
- data/lib/pubid/calconnect_pfs/renderer.rb +10 -0
- data/lib/pubid/calconnect_pfs/urn_generator.rb +30 -0
- data/lib/pubid/calconnect_pfs.rb +60 -0
- data/lib/pubid/cen_pfs/builder.rb +33 -0
- data/lib/pubid/cen_pfs/identifier.rb +64 -0
- data/lib/pubid/cen_pfs/identifiers/european_norm.rb +31 -0
- data/lib/pubid/cen_pfs/identifiers.rb +7 -0
- data/lib/pubid/cen_pfs/parser.rb +10 -0
- data/lib/pubid/cen_pfs/pubid_urn_schema.rb +15 -0
- data/lib/pubid/cen_pfs/renderer.rb +10 -0
- data/lib/pubid/cen_pfs/urn_generator.rb +12 -0
- data/lib/pubid/cen_pfs.rb +71 -0
- data/lib/pubid/doi_pfs/CLAUDE.md +72 -0
- data/lib/pubid/doi_pfs/builder.rb +18 -0
- data/lib/pubid/doi_pfs/identifier.rb +43 -0
- data/lib/pubid/doi_pfs/identifiers/resource.rb +31 -0
- data/lib/pubid/doi_pfs/identifiers.rb +7 -0
- data/lib/pubid/doi_pfs/parser.rb +10 -0
- data/lib/pubid/doi_pfs/pubid_urn_schema.rb +15 -0
- data/lib/pubid/doi_pfs/renderer.rb +10 -0
- data/lib/pubid/doi_pfs/urn_generator.rb +12 -0
- data/lib/pubid/doi_pfs.rb +60 -0
- data/lib/pubid/etsi_pfs/CLAUDE.md +95 -0
- data/lib/pubid/etsi_pfs/builder.rb +25 -0
- data/lib/pubid/etsi_pfs/identifier.rb +50 -0
- data/lib/pubid/etsi_pfs/identifiers/standard.rb +22 -0
- data/lib/pubid/etsi_pfs/identifiers.rb +7 -0
- data/lib/pubid/etsi_pfs/parser.rb +10 -0
- data/lib/pubid/etsi_pfs/pubid_urn_schema.rb +19 -0
- data/lib/pubid/etsi_pfs/renderer.rb +10 -0
- data/lib/pubid/etsi_pfs/urn_generator.rb +12 -0
- data/lib/pubid/etsi_pfs.rb +59 -0
- data/lib/pubid/itu/CLAUDE.md +25 -14
- data/lib/pubid/itu/builder.rb +7 -5
- data/lib/pubid/itu/identifiers/special_publication.rb +20 -17
- data/lib/pubid/itu/urn_generator.rb +3 -4
- data/lib/pubid/nist/CLAUDE.md +36 -0
- data/lib/pubid/nist/builder.rb +10 -0
- data/lib/pubid/nist/identifiers/base.rb +10 -0
- data/lib/pubid/nist/identifiers/special_publication.rb +15 -0
- data/lib/pubid/nist/router.rb +11 -0
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid.rb +17 -0
- 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,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,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)
|