pubid 2.0.8 → 2.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8013a826102b555c7067ce39bb01b1b3a4d43a6ec09ba6092ae4fe80ce364c8e
4
- data.tar.gz: 9e7ebbfa50e70911201bc5800438ed661c1d03116c85e7f96f164bfc4089b615
3
+ metadata.gz: 014b932f4ce8d6a4da07b23e611467dc460afc19aa73f9be3af191cab1007a02
4
+ data.tar.gz: 284a4f85f2adcdabe735a20c5f427bf15a242c8b660c66d2ebde54f1938a2665
5
5
  SHA512:
6
- metadata.gz: f3d0739c523091e44a03e9c4796696a046dbbad30b6681cc4d19e69df785fff10a8bfb83912bff0d3215def503a1d28d48e1f1ca5f76335424cf9f7782c672bd
7
- data.tar.gz: ae1b998769f90c7ea9463cd11c66f0374bf4598d2c7a425bb2f615c5acf592652f2a98fba9bf3b8c1db5c10d4f7b24952c516ca994de58f2f8c55b17db42919b
6
+ metadata.gz: bdecc7d28c6b0bc0c2550d9a643e48324f5f40c8feb3faf3e931c6b2928f573f1c07e01db60d30c016fab9e60bca035e4cf9f4b064c9a47a973b1aa5e500cea3
7
+ data.tar.gz: 112284d94ca1994025773f8de8ba04840e3560fc339a955c07241e1c0def5bae291f7285aef723ee370669cc8682d7ef88a1d1afd21a37d1c2cb2e890bc965eb
data/README.adoc CHANGED
@@ -93,7 +93,8 @@ id.to_urn # => "urn:iso:std:iso:9001:amd:2020:v1"
93
93
  `#to_urn` follows the registered grammar of the organization, which can
94
94
  drop values (RFC 5141 has no year). `#to_pubid_urn` is pubid's own URN: it
95
95
  carries every value, always round-trips through `Pubid.parse_urn`, and is
96
- safe to use as an HTTP path. ISO, IEEE, ANSI, ITU, NIST and ASTM support it now; the other flavors follow
96
+ safe to use as an HTTP path. ISO, IEEE, ANSI, ITU, NIST, ASTM, API, ASHRAE,
97
+ ASME, JCGM, OIML, ETSI, GOST and PLATEAU support it now; the other flavors follow
97
98
  (link:docs/URN-SCHEMES.adoc[URN schemes]).
98
99
 
99
100
  [source,ruby]
data/data/parg/sae.json CHANGED
@@ -1 +1 @@
1
- {"bindings":null,"version":"0.1.0","grammar":"PubidSae","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"SAE"}},{"Named":{"name":"publisher","atom":0}},{"Entity":{"atom":3}},{"Str":{"pattern":" "}},{"Entity":{"atom":12}},{"Str":{"pattern":"AMS"}},{"Str":{"pattern":"AIR"}},{"Str":{"pattern":"ARP"}},{"Str":{"pattern":"AS"}},{"Str":{"pattern":"J"}},{"Str":{"pattern":"MA"}},{"Alternative":{"atoms":[5,6,7,8,9,10]}},{"Named":{"name":"type","atom":11}},{"Entity":{"atom":3}},{"Repetition":{"atom":13,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":29}},{"Entity":{"atom":17}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Repetition":{"atom":16,"min":1,"max":1,"tag":"Repetition"}},{"Entity":{"atom":20}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":19,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[18,21]}},{"Entity":{"atom":20}},{"Repetition":{"atom":23,"min":1,"max":null,"tag":"Repetition"}},{"Entity":{"atom":17}},{"Repetition":{"atom":25,"min":0,"max":1,"tag":"Repetition"}},{"Sequence":{"atoms":[24,26]}},{"Alternative":{"atoms":[22,27]}},{"Named":{"name":"number","atom":28}},{"Entity":{"atom":31}},{"Str":{"pattern":":"}},{"Named":{"name":"colon","atom":30}},{"Entity":{"atom":36}},{"Entity":{"atom":20}},{"Repetition":{"atom":34,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":35}},{"Sequence":{"atoms":[32,33]}},{"Repetition":{"atom":37,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[1,2,4,14,15,38]}}],"root":39},"bindings":[{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"publisher","path":"publisher","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":[]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"SAE J1939","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE AMS2430","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE ARP4754A:2010","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}}],"docs":{},"source":"# Port of lib/pubid/sae/parser.rb: SAE <AMS|AIR|ARP|AS|J|MA> <number>[:year]\n\ngrammar PubidSae version \"0.1.0\" edition 2 {\n doc_type = (\"AMS\" / \"AIR\" / \"ARP\" / \"AS\" / \"J\" / \"MA\") as type\n number = ((1*1letter 1*digit) / (1*digit *1letter)) as number\n year = (4digit) as year\n identifier = \"SAE\" as publisher space doc_type [space] number [(colon year)]\n letter = %x41-5A\n digit = %x30-39\n space = \" \"\n colon = \":\"\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n type -> type (string)\n number -> number (string)\n year -> year (string)\n publisher -> publisher (string)\n }\n\n\n test {\n accept \"SAE J1939\"\n accept \"SAE AMS2430\"\n accept \"SAE ARP4754A:2010\"\n reject \"nonsense\"\n }\n}\n","source_hash":"2d3a161caeb9c059ce2600050b823aea373712c16cfa662e5f82166c6f7ee01a","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.114","edition":2,"checksum":"sha256:cdcc10602aa864cbccccd23ab6baaeed8e7edc4e4a8075c6c7a8f90324d7b5e1"}
1
+ {"bindings":null,"version":"0.2.0","grammar":"PubidSae","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"SAE"}},{"Named":{"name":"publisher","atom":0}},{"Entity":{"atom":3}},{"Str":{"pattern":" "}},{"Entity":{"atom":86}},{"Str":{"pattern":"AMS-QQ-A"}},{"Str":{"pattern":"AMS-QQ-C"}},{"Str":{"pattern":"AMS-QQ-N"}},{"Str":{"pattern":"AMS-QQ-P"}},{"Str":{"pattern":"AMS-QQ-S"}},{"Str":{"pattern":"AMS-QQ-W"}},{"Str":{"pattern":"AMS-WW-T"}},{"Str":{"pattern":"GEIA-STD"}},{"Str":{"pattern":"AMS-DTL"}},{"Str":{"pattern":"AMS-PRF"}},{"Str":{"pattern":"AMS-STD"}},{"Str":{"pattern":"EIA-STD"}},{"Str":{"pattern":"GEIA-HB"}},{"Str":{"pattern":"GEIA-OP"}},{"Str":{"pattern":"GEIA-TB"}},{"Str":{"pattern":"GEIASTD"}},{"Str":{"pattern":"AMSDTL"}},{"Str":{"pattern":"AMSPRF"}},{"Str":{"pattern":"AMSQQA"}},{"Str":{"pattern":"AMSQQC"}},{"Str":{"pattern":"AMSQQN"}},{"Str":{"pattern":"AMSQQP"}},{"Str":{"pattern":"AMSQQS"}},{"Str":{"pattern":"AMSQQW"}},{"Str":{"pattern":"AMSSTD"}},{"Str":{"pattern":"AMSWWT"}},{"Str":{"pattern":"EIA-IS"}},{"Str":{"pattern":"EIASTD"}},{"Str":{"pattern":"GEIAHB"}},{"Str":{"pattern":"GEIAOP"}},{"Str":{"pattern":"GEIATB"}},{"Str":{"pattern":"AMS-A"}},{"Str":{"pattern":"AMS-B"}},{"Str":{"pattern":"AMS-C"}},{"Str":{"pattern":"AMS-D"}},{"Str":{"pattern":"AMS-F"}},{"Str":{"pattern":"AMS-G"}},{"Str":{"pattern":"AMS-H"}},{"Str":{"pattern":"AMS-I"}},{"Str":{"pattern":"AMS-L"}},{"Str":{"pattern":"AMS-M"}},{"Str":{"pattern":"AMS-P"}},{"Str":{"pattern":"AMS-R"}},{"Str":{"pattern":"AMS-S"}},{"Str":{"pattern":"AMS-T"}},{"Str":{"pattern":"AMS-W"}},{"Str":{"pattern":"EIAIS"}},{"Str":{"pattern":"USCAR"}},{"Str":{"pattern":"AMSA"}},{"Str":{"pattern":"AMSB"}},{"Str":{"pattern":"AMSC"}},{"Str":{"pattern":"AMSD"}},{"Str":{"pattern":"AMSF"}},{"Str":{"pattern":"AMSG"}},{"Str":{"pattern":"AMSH"}},{"Str":{"pattern":"AMSI"}},{"Str":{"pattern":"AMSL"}},{"Str":{"pattern":"AMSM"}},{"Str":{"pattern":"AMSP"}},{"Str":{"pattern":"AMSR"}},{"Str":{"pattern":"AMSS"}},{"Str":{"pattern":"AMST"}},{"Str":{"pattern":"AMSW"}},{"Str":{"pattern":"GEIA"}},{"Str":{"pattern":"AIR"}},{"Str":{"pattern":"AMS"}},{"Str":{"pattern":"ARD"}},{"Str":{"pattern":"ARP"}},{"Str":{"pattern":"CMB"}},{"Str":{"pattern":"CRP"}},{"Str":{"pattern":"EIA"}},{"Str":{"pattern":"MAM"}},{"Str":{"pattern":"MAP"}},{"Str":{"pattern":"SSB"}},{"Str":{"pattern":"TSB"}},{"Str":{"pattern":"AS"}},{"Str":{"pattern":"JA"}},{"Str":{"pattern":"MA"}},{"Str":{"pattern":"MS"}},{"Str":{"pattern":"J"}},{"Alternative":{"atoms":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84]}},{"Named":{"name":"type","atom":85}},{"Entity":{"atom":90}},{"Entity":{"atom":3}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[88,89]}},{"Named":{"name":"type_sep","atom":87}},{"Repetition":{"atom":91,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":97}},{"Entity":{"atom":95}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":94,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":96}},{"Entity":{"atom":107}},{"Str":{"pattern":"-"}},{"Str":{"pattern":"_"}},{"Alternative":{"atoms":[99,100]}},{"Entity":{"atom":95}},{"Repetition":{"atom":102,"min":1,"max":3,"tag":"Repetition"}},{"Named":{"name":"dash","atom":103}},{"Entity":{"atom":95}},{"Lookahead":{"atom":105,"positive":false}},{"Sequence":{"atoms":[101,104,106]}},{"Repetition":{"atom":98,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":113}},{"Entity":{"atom":111}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Repetition":{"atom":110,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"tail","atom":112}},{"Repetition":{"atom":109,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":130}},{"Str":{"pattern":"/"}},{"Entity":{"atom":111}},{"Repetition":{"atom":117,"min":0,"max":null,"tag":"Repetition"}},{"Entity":{"atom":95}},{"Repetition":{"atom":119,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[118,120]}},{"Entity":{"atom":111}},{"Repetition":{"atom":122,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[121,123]}},{"Named":{"name":"part","atom":124}},{"Entity":{"atom":111}},{"Repetition":{"atom":126,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"part_tail","atom":127}},{"Repetition":{"atom":128,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[116,125,129]}},{"Repetition":{"atom":115,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":137}},{"Str":{"pattern":"_A"}},{"Entity":{"atom":95}},{"Repetition":{"atom":134,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"amendment","atom":135}},{"Sequence":{"atoms":[133,136]}},{"Repetition":{"atom":132,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":153}},{"Str":{"pattern":":"}},{"Entity":{"atom":95}},{"Repetition":{"atom":141,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":142}},{"Sequence":{"atoms":[140,143]}},{"Str":{"pattern":"_"}},{"Entity":{"atom":95}},{"Repetition":{"atom":146,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":147}},{"Entity":{"atom":95}},{"Repetition":{"atom":149,"min":2,"max":2,"tag":"Repetition"}},{"Named":{"name":"month","atom":150}},{"Sequence":{"atoms":[145,148,151]}},{"Alternative":{"atoms":[144,152]}},{"Repetition":{"atom":139,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[1,2,4,92,93,108,114,131,138,154]}}],"root":155},"bindings":[{"capture":"publisher","path":"publisher","type":"string","card":null,"preprocess":null},{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"dash","path":"dash","type":"string","card":null,"preprocess":null},{"capture":"tail","path":"tail","type":"string","card":null,"preprocess":null},{"capture":"part","path":"part","type":"string","card":null,"preprocess":null},{"capture":"part_tail","path":"part_tail","type":"string","card":null,"preprocess":null},{"capture":"amendment","path":"amendment","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"month","path":"month","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":[]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"SAE J1939","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE J 3016","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE AMS2430","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE ARP4754A:2010","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE AMS-QQ-A-250/4B","expect":{}},{"entry":"identifier","kind":"accept","input":"SAE AMSQQA250/4B","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"SAE J3016_2021","expect":{}},{"entry":"identifier","kind":"reject","input":"SAE XYZ1","expect":{}},{"entry":"identifier","kind":"example","input":"SAE J3016_202104","expect":{"type":"J","number":"3016","year":"2021","month":"04"}},{"entry":"identifier","kind":"example","input":"SAE AMS4121J","expect":{"type":"AMS","number":"4121","tail":"J"}},{"entry":"identifier","kind":"example","input":"SAE AS22520/45A","expect":{"type":"AS","number":"22520","part":"45","part_tail":"A"}},{"entry":"identifier","kind":"example","input":"SAE AMS5622/H1025","expect":{"type":"AMS","number":"5622","part":"H1025"}},{"entry":"identifier","kind":"example","input":"SAE J2945/AS","expect":{"type":"J","number":"2945","part":"AS"}},{"entry":"identifier","kind":"example","input":"SAE J1939-71:2020","expect":{"type":"J","number":"1939","dash":"71","year":"2020"}},{"entry":"identifier","kind":"example","input":"SAE EIA649_1A","expect":{"type":"EIA","number":"649","dash":"1","tail":"A"}},{"entry":"identifier","kind":"example","input":"SAE AS5259/7A_A1","expect":{"type":"AS","number":"5259","part":"7","part_tail":"A","amendment":"1"}},{"entry":"identifier","kind":"example","input":"SAE AMSSTD595","expect":{"type":"AMSSTD","number":"595"}},{"entry":"identifier","kind":"example","input":"SAE GEIA-STD-0001","expect":{"type":"GEIA-STD","number":"0001"}}],"docs":{},"source":"# SAE identifiers, in SAE's printed form and in the hyphen-free form of its\n# Crossref DOI suffixes (10.4271/<code>):\n#\n# SAE <type>[ |-]<number>[-<dash>][<tail>][/<part>[<part_tail>]][_A<n>][:yyyy|_yyyymm]\n#\n# The grammar only segments. What a letter tail means (a revision, the\n# metric M of a J report, a digital-annex DA) and which spelling of the type\n# is printed is decided by pubid's lib/pubid/sae/builder.rb, which owns the\n# type table (Pubid::Sae::Components::Type::TYPES). The doc_type\n# alternatives are that table's keys plus their hyphen-free spellings,\n# longest first: PEG commits to the first alternative that matches, so\n# \"AMS\" must come after every \"AMS...\" family.\n\ngrammar PubidSae version \"0.2.0\" edition 2 {\n doc_type = (\"AMS-QQ-A\" / \"AMS-QQ-C\" / \"AMS-QQ-N\" / \"AMS-QQ-P\" / \"AMS-QQ-S\" / \"AMS-QQ-W\" / \"AMS-WW-T\" / \"GEIA-STD\" / \"AMS-DTL\" / \"AMS-PRF\" / \"AMS-STD\" / \"EIA-STD\" / \"GEIA-HB\" / \"GEIA-OP\" / \"GEIA-TB\" / \"GEIASTD\" / \"AMSDTL\" / \"AMSPRF\" / \"AMSQQA\" / \"AMSQQC\" / \"AMSQQN\" / \"AMSQQP\" / \"AMSQQS\" / \"AMSQQW\" / \"AMSSTD\" / \"AMSWWT\" / \"EIA-IS\" / \"EIASTD\" / \"GEIAHB\" / \"GEIAOP\" / \"GEIATB\" / \"AMS-A\" / \"AMS-B\" / \"AMS-C\" / \"AMS-D\" / \"AMS-F\" / \"AMS-G\" / \"AMS-H\" / \"AMS-I\" / \"AMS-L\" / \"AMS-M\" / \"AMS-P\" / \"AMS-R\" / \"AMS-S\" / \"AMS-T\" / \"AMS-W\" / \"EIAIS\" / \"USCAR\" / \"AMSA\" / \"AMSB\" / \"AMSC\" / \"AMSD\" / \"AMSF\" / \"AMSG\" / \"AMSH\" / \"AMSI\" / \"AMSL\" / \"AMSM\" / \"AMSP\" / \"AMSR\" / \"AMSS\" / \"AMST\" / \"AMSW\" / \"GEIA\" / \"AIR\" / \"AMS\" / \"ARD\" / \"ARP\" / \"CMB\" / \"CRP\" / \"EIA\" / \"MAM\" / \"MAP\" / \"SSB\" / \"TSB\" / \"AS\" / \"JA\" / \"MA\" / \"MS\" / \"J\") as type\n type_sep = space / \"-\"\n number = (1*digit) as number\n # A dash number: J1939-71, EIA649-2, and the DOI's EIA649_1. At most three\n # digits and no digit after, so \"_202104\" stays a date.\n dash = (\"-\" / \"_\") (1*3digit) as dash !digit\n tail = (1*letter) as tail\n part = \"/\" ((*letter 1*digit) / 1*letter) as part [(1*letter) as part_tail]\n amendment = \"_A\" (1*digit) as amendment\n date = (\":\" (4digit) as year) / (\"_\" (4digit) as year (2digit) as month)\n identifier = \"SAE\" as publisher space doc_type [type_sep] number [dash] [tail] [part] [amendment] [date]\n letter = %x41-5A\n digit = %x30-39\n space = \" \"\n entry identifier: identifier\n\n bindings identifier {\n publisher -> publisher (string)\n type -> type (string)\n number -> number (string)\n dash -> dash (string)\n tail -> tail (string)\n part -> part (string)\n part_tail -> part_tail (string)\n amendment -> amendment (string)\n year -> year (string)\n month -> month (string)\n }\n\n test {\n accept \"SAE J1939\"\n accept \"SAE J 3016\"\n accept \"SAE AMS2430\"\n accept \"SAE ARP4754A:2010\"\n accept \"SAE AMS-QQ-A-250/4B\"\n accept \"SAE AMSQQA250/4B\"\n reject \"nonsense\"\n reject \"SAE J3016_2021\"\n reject \"SAE XYZ1\"\n example \"SAE J3016_202104\" { type: \"J\", number: \"3016\", year: \"2021\", month: \"04\" }\n example \"SAE AMS4121J\" { type: \"AMS\", number: \"4121\", tail: \"J\" }\n example \"SAE AS22520/45A\" { type: \"AS\", number: \"22520\", part: \"45\", part_tail: \"A\" }\n example \"SAE AMS5622/H1025\" { type: \"AMS\", number: \"5622\", part: \"H1025\" }\n example \"SAE J2945/AS\" { type: \"J\", number: \"2945\", part: \"AS\" }\n example \"SAE J1939-71:2020\" { type: \"J\", number: \"1939\", dash: \"71\", year: \"2020\" }\n example \"SAE EIA649_1A\" { type: \"EIA\", number: \"649\", dash: \"1\", tail: \"A\" }\n example \"SAE AS5259/7A_A1\" { type: \"AS\", number: \"5259\", part: \"7\", part_tail: \"A\", amendment: \"1\" }\n example \"SAE AMSSTD595\" { type: \"AMSSTD\", number: \"595\" }\n example \"SAE GEIA-STD-0001\" { type: \"GEIA-STD\", number: \"0001\" }\n }\n}\n","source_hash":"204b7183faedf1026e76016e92d3e9904d81930ed04d7a9d54849ac78f1ad6e7","unicode_tables":"onigmo (Ruby 3.4.10)","compiler_version":"1.3.129","edition":2,"semantics_hash":"sha256:58d95b3087380a9ddb6598898095eac06e5cf639bc63d699c0db8d51c88c15f0","checksum":"sha256:ea9c116c71abfbf523abb94e20bede94a0b9613621dd2d4cae1990aad6f3e774"}
@@ -12,6 +12,8 @@ module Pubid
12
12
  atom "part", "part"
13
13
  atom "section", "section"
14
14
  atom "subsection", "subsection"
15
+ # typeless standards date themselves ("API 650:2020")
16
+ atom "year", "year"
15
17
 
16
18
  type "pubid:api:base", "base"
17
19
  type "pubid:api:bulletin", "bulletin"
@@ -57,3 +57,20 @@ keeps `#matches?(other, ignore:)`.
57
57
  decision, on the reading that a published norm should carry a published
58
58
  stage by default. It is fixed at the matching layer instead: no grammar
59
59
  change, no index re-crawl.
60
+
61
+ ## `locate_type` and `CenelecHarmonizationDocument`
62
+
63
+ `CenCenelec.locate_type` returns the **first** class in `identifier_types`
64
+ whose `type[:key]` matches, and `identifier_types` reads
65
+ `Identifiers.constants`, whose order Ruby does not guarantee.
66
+ `CenelecHarmonizationDocument` shared the key `:hd` with
67
+ `HarmonizationDocument`, and its autoload was misspelled
68
+ (`CENelecHarmonizationDocument`), so it joined the list only when something
69
+ else had loaded its file before the list was memoized. Then `HD 123:2020`
70
+ could build the CENELEC class: 6 CEN examples failed in one spec order only,
71
+ exposed by the pubid-URN GOST wave, not caused by it. The autoload is fixed
72
+ and the class has its own key `:chd` (the token `docs/URN-SCHEMES.adoc`
73
+ gives it); `spec/pubid/cen_cenelec/type_key_spec.rb` loads the class and
74
+ rebuilds the list, so it does not depend on order. **Every type key in a
75
+ flavor must be unique**; ANSI has the same shape (`Standard` and
76
+ `AmericanNationalStandard` both declare `ans`) and is not checked here.
@@ -19,8 +19,11 @@ module Pubid
19
19
  ),
20
20
  ].freeze
21
21
 
22
+ # Its own key: CenCenelec.locate_type returns the first class with
23
+ # a matching key, and HarmonizationDocument owns :hd ("HD 123:2020").
24
+ # A shared key made the class choice depend on load order.
22
25
  def self.type
23
- { key: :hd, title: "CENELEC Harmonization Document", short: "HD" }
26
+ { key: :chd, title: "CENELEC Harmonization Document", short: "HD" }
24
27
  end
25
28
  end
26
29
  end
@@ -7,7 +7,7 @@ module Pubid
7
7
  "#{__dir__}/identifiers/adopted_european_norm"
8
8
  autoload :Amendment, "#{__dir__}/identifiers/amendment"
9
9
  autoload :Base, "#{__dir__}/identifiers/base"
10
- autoload :CENelecHarmonizationDocument,
10
+ autoload :CenelecHarmonizationDocument,
11
11
  "#{__dir__}/identifiers/cenelec_harmonization_document"
12
12
  autoload :CenReport, "#{__dir__}/identifiers/cen_report"
13
13
  autoload :CenWorkshopAgreement,
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module CenCenelec
5
+ # The pubid-scheme URN of CEN/CENELEC — PARTIAL (issue #656;
6
+ # docs/URN-SCHEMES.adoc).
7
+ #
8
+ # Only the types a GOST adoption can name are declared: an EN, an
9
+ # adopted EN ("EN ISO 12100:2010", no number of its own), its amendment
10
+ # and corrigendum, a CEN TS or TR, and the consolidated
11
+ # "EN 14988:2017+A1:2020". The atoms keep the doc's canonical order; an
12
+ # absent atom writes nothing, so the CEN wave can add types and atoms
13
+ # without changing these URNs. Until then cen_cenelec stays pending in
14
+ # the contract spec.
15
+ PubidUrnSchema = Pubid::PubidUrn.define("cencenelec") do
16
+ atom "publisher", "pub"
17
+ atom "copublishers", "co", kind: :list
18
+ atom "part", "part"
19
+ atom "year", "year"
20
+ atom "month", "month"
21
+ atom "version", "v"
22
+ atom "stage", "stage"
23
+ atom "adopted", "adopted", kind: :id
24
+ atom "identifiers", "ids", kind: :ids
25
+
26
+ type "pubid:cencenelec:adopted-european-norm", "aen", core: []
27
+ type "pubid:cencenelec:amendment", "amd"
28
+ type "pubid:cencenelec:consolidated-identifier", "consol", core: []
29
+ type "pubid:cencenelec:corrigendum", "cor"
30
+ type "pubid:cencenelec:european-norm", "en"
31
+ type "pubid:cencenelec:technical-report", "tr"
32
+ type "pubid:cencenelec:technical-specification", "ts"
33
+ end
34
+ end
35
+ end
@@ -19,6 +19,7 @@ module Pubid
19
19
  autoload :Identifier, "#{__dir__}/cen_cenelec/identifier"
20
20
  autoload :Identifiers, "#{__dir__}/cen_cenelec/identifiers"
21
21
  autoload :Parser, "#{__dir__}/cen_cenelec/parser"
22
+ autoload :PubidUrnSchema, "#{__dir__}/cen_cenelec/pubid_urn_schema"
22
23
  autoload :Renderer, "#{__dir__}/cen_cenelec/renderer"
23
24
  autoload :SingleIdentifier, "#{__dir__}/cen_cenelec/single_identifier"
24
25
  autoload :UrnGenerator, "#{__dir__}/cen_cenelec/urn_generator"
@@ -32,3 +32,12 @@ keeps `#matches?(other, ignore:)`.
32
32
  - `SUBSET_PAIRS["etsi"]` in `spec/pubid/subset_match_spec.rb` moved from
33
33
  `ETSI EN 300 175` to `ETSI EN 300 175-1`: the reference now states its
34
34
  part, and the version and the date are what it omits.
35
+
36
+ ## pubid-scheme URN (issue #656)
37
+
38
+ `lib/pubid/etsi/pubid_urn_schema.rb`; rulings in `docs/URN-SCHEMES.adoc`
39
+ (`[[urn-etsi]]`). A supplement's `number` is an Integer ordinal, so its core
40
+ is `:int` (`core: [["number", :int]]`), and the codec refuses a String there.
41
+ `minor` and `day` are placed although no pass fixture sets them, because
42
+ `EtsiStandard`'s `key_value` block maps them: a schema must place every key
43
+ the mapping can write, not only the keys the corpus shows.
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Etsi
5
+ # The pubid-scheme URN of ETSI (issue #656; docs/URN-SCHEMES.adoc).
6
+ #
7
+ # ETSI EN 300 175-1 V2.9.1 (2022-01)
8
+ # urn:pubid:etsi:es:300~20175:type-EN:part-1:year-2022:month-01:v-2.9.1
9
+ #
10
+ # ETSI ETR 108/A1 ed.1 (1995-08)
11
+ # urn:pubid:etsi:es:108:type-ETR:year-1995:month-08:v-1:isedition-true:
12
+ # amd:1
13
+ #
14
+ # The number is the core of every type. A supplement's number is its
15
+ # Integer ordinal, so it is an :int core; the document it supplements
16
+ # comes first, along +base+. The official urn:etsi: form (RFC 8515)
17
+ # stays on #to_urn.
18
+ PubidUrnSchema = Pubid::PubidUrn.define("etsi") do
19
+ atom "type", "type"
20
+ atom "parts", "part", kind: :list
21
+ atom "minor", "minor"
22
+ atom "year", "year"
23
+ atom "month", "month"
24
+ atom "day", "day"
25
+ atom "version", "v"
26
+ atom "is_edition", "isedition", kind: :bool
27
+
28
+ supplement = [["number", :int]]
29
+ type "pubid:etsi:amendment", "amd", core: supplement
30
+ type "pubid:etsi:corrigendum", "cor", core: supplement
31
+ type "pubid:etsi:etsi-standard", "es"
32
+ type "pubid:etsi:identifier", "id"
33
+ type "pubid:etsi:supplement-identifier", "si", core: supplement
34
+ end
35
+ end
36
+ end
data/lib/pubid/etsi.rb CHANGED
@@ -17,6 +17,7 @@ module Pubid
17
17
  autoload :Identifier, "#{__dir__}/etsi/identifiers/base"
18
18
  autoload :Identifiers, "#{__dir__}/etsi/identifiers"
19
19
  autoload :Parser, "#{__dir__}/etsi/parser"
20
+ autoload :PubidUrnSchema, "#{__dir__}/etsi/pubid_urn_schema"
20
21
  autoload :Renderer, "#{__dir__}/etsi/renderer"
21
22
  autoload :UrnGenerator, "#{__dir__}/etsi/urn_generator"
22
23
  autoload :UrnParser, "#{__dir__}/etsi/urn_parser"
@@ -62,3 +62,15 @@ a deterministic reproduction of the race (not a reliance on catching the rare
62
62
  full-suite failure): it temporarily `Registry.unregister(:bsi)`s the usual
63
63
  race winner and asserts routing still resolves to `Pubid::Iso` correctly —
64
64
  proving the fix is prefix routing, not a lucky alphabetical ordering.
65
+
66
+ ## pubid-scheme URN (issue #656)
67
+
68
+ `lib/pubid/gost/pubid_urn_schema.rb`; rulings in `docs/URN-SCHEMES.adoc`
69
+ (`[[urn-gost]]`). The adopted documents nest with their own flavor's schema,
70
+ so this wave added a partial `lib/pubid/cen_cenelec/pubid_urn_schema.rb`.
71
+ **Open gap:** `ForeignReference#raw` is not mapped in `key_value`, so
72
+ `to_hash` drops the text of an adopted document of an unknown flavor
73
+ (`(OECD 460:2017)`): two such adoptions share one hash and one URN, and
74
+ `from_hash` loses the parenthesis. Mapping `raw` changes `to_hash`, and the
75
+ `pubid-testsuite` GOST cases store the hash without it, so the fix must land
76
+ together with a testsuite update. The schema's `raw-` atom is ready for it.
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Gost
5
+ # The pubid-scheme URN of GOST (issue #656; docs/URN-SCHEMES.adoc).
6
+ #
7
+ # ГОСТ ISO 6848 -> urn:pubid:gost:is:6848:co-ISO
8
+ #
9
+ # ГОСТ Р 58904-2020/ISO/TR 25901-1:2016
10
+ # urn:pubid:gost:ns:58904:year-2020:ia:58904:year-2020:
11
+ # adopted-(iso:tr:25901:part-1:year-2016)
12
+ #
13
+ # A harmonized or identical adoption wraps the GOST standard in +base+
14
+ # (written first) and repeats its number and year, as #to_hash does.
15
+ # The adopted documents are nested with their own flavor's schema
16
+ # (cencenelec, iec, iso, ieee, astm).
17
+ PubidUrnSchema = Pubid::PubidUrn.define("gost") do
18
+ atom "publisher", "pub"
19
+ atom "copublisher", "co"
20
+ atom "year", "year"
21
+ atom "subtype", "subtype"
22
+ atom "raw", "raw"
23
+ atom "adopted_identifiers", "adoptedidentifiers", kind: :ids
24
+ atom "adopted", "adopted", kind: :id
25
+
26
+ type "pubid:gost:foreign-reference", "fr"
27
+ type "pubid:gost:harmonized", "h"
28
+ type "pubid:gost:identical-adoption", "ia"
29
+ type "pubid:gost:identifier", "id"
30
+ type "pubid:gost:interstate-standard", "is"
31
+ type "pubid:gost:national-standard", "ns"
32
+ end
33
+ end
34
+ end
data/lib/pubid/gost.rb CHANGED
@@ -14,6 +14,7 @@ module Pubid
14
14
  autoload :Identifier, "#{__dir__}/gost/identifier"
15
15
  autoload :Identifiers, "#{__dir__}/gost/identifiers"
16
16
  autoload :Parser, "#{__dir__}/gost/parser"
17
+ autoload :PubidUrnSchema, "#{__dir__}/gost/pubid_urn_schema"
17
18
  autoload :Renderer, "#{__dir__}/gost/renderer"
18
19
  autoload :UrnGenerator, "#{__dir__}/gost/urn_generator"
19
20
  autoload :UrnParser, "#{__dir__}/gost/urn_parser"
@@ -1468,7 +1468,13 @@ module Pubid
1468
1468
  @hash ||= compute_hash
1469
1469
  end
1470
1470
 
1471
- def eql?(other)
1471
+ # The second parameter is lutaml's: ComparableModel#== (an alias of its
1472
+ # #eql?(other, compared_objects = {})) recurses with two arguments when
1473
+ # an attribute holds a model of the SAME class, such as the standard
1474
+ # an IEEE "Revision of" id revises. A one-parameter override made #==
1475
+ # raise ArgumentError there. The cycle guard is lutaml's; this method
1476
+ # keeps its own semantics and does not need it.
1477
+ def eql?(other, _compared_objects = nil)
1472
1478
  return false unless other.is_a?(self.class)
1473
1479
 
1474
1480
  hash == other.hash && self == other
@@ -5,10 +5,12 @@ module Pubid
5
5
  # The pubid-scheme URN of IEC — PARTIAL (issue #656;
6
6
  # docs/URN-SCHEMES.adoc).
7
7
  #
8
- # Only +pubid:iec:international-standard+ is declared, because IEEE
9
- # identifiers nest it (IEC/IEEE co-publications). IEC's own wave declares
10
- # the rest of the flavor; until then IEC stays pending in the contract
11
- # spec. The official RFC 5141-style urn:iec:std: form stays on #to_urn.
8
+ # Only the types other flavors nest are declared: the International
9
+ # Standard (IEC/IEEE co-publications, GOST adoptions), and the TS, TR,
10
+ # amendment and consolidated identifier a GOST adoption can name
11
+ # ("IEC 60079-0:2017+AMD1:2020"). IEC's own wave declares the rest of the
12
+ # flavor; until then IEC stays pending in the contract spec. The official
13
+ # RFC 5141-style urn:iec:std: form stays on #to_urn.
12
14
  PubidUrnSchema = Pubid::PubidUrn.define("iec") do
13
15
  atom "publisher", "pub"
14
16
  atom "copublishers", "co", kind: :list
@@ -23,10 +25,15 @@ module Pubid
23
25
  atom "stage_iteration", "iter"
24
26
  atom "database", "database"
25
27
  atom "languages.*.code", "lang"
28
+ atom "identifiers", "ids", kind: :ids
26
29
 
27
30
  facet "languages.*.original_code", "langsrc"
28
31
 
32
+ type "pubid:iec:amendment", "amd"
33
+ type "pubid:iec:consolidated-identifier", "consol", core: []
29
34
  type "pubid:iec:international-standard", "is"
35
+ type "pubid:iec:technical-report", "tr"
36
+ type "pubid:iec:technical-specification", "ts"
30
37
  end
31
38
  end
32
39
  end
@@ -0,0 +1,13 @@
1
+ # PLATEAU flavor notes
2
+
3
+ Read this before you change `lib/pubid/plateau/` or `spec/pubid/plateau/`. The
4
+ root `CLAUDE.md` keeps the cross-flavor contract that every flavor obeys.
5
+
6
+ ## pubid-scheme URN (issue #656)
7
+
8
+ `lib/pubid/plateau/pubid_urn_schema.rb`; rulings in `docs/URN-SCHEMES.adoc`
9
+ (`[[urn-plateau]]`). PLATEAU is the one flavor whose `number` stays an
10
+ Integer, so its core is `:int` (`core: [["number", :int]]`): `#00` is the core
11
+ `0`, and `00` in a URN is not canonical. The Annex type has no `number`; it
12
+ has no core, its document comes first along `base`, and its `letter` is an
13
+ atom. No input makes an Annex today, so the spec builds one by hand.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Plateau
5
+ # The pubid-scheme URN of PLATEAU (issue #656; docs/URN-SCHEMES.adoc).
6
+ #
7
+ # PLATEAU Handbook #00 第1.0版 -> urn:pubid:plateau:hb:0:ed-1.0
8
+ #
9
+ # PLATEAU keeps +number+ as an Integer (the one flavor the String retype
10
+ # left out), so it is an :int core. An Annex has no number of its own:
11
+ # its document comes first, along +base+, and its letter is an atom.
12
+ PubidUrnSchema = Pubid::PubidUrn.define("plateau") do
13
+ atom "publisher", "pub"
14
+ atom "edition", "ed"
15
+ atom "annex", "annex", kind: :int
16
+
17
+ number = [["number", :int]]
18
+ type "pubid:plateau:annex", "annex", core: [],
19
+ atoms: [%w[letter letter]]
20
+ type "pubid:plateau:handbook", "hb", core: number
21
+ type "pubid:plateau:identifier", "id", core: number
22
+ type "pubid:plateau:technical-report", "tr", core: number
23
+ end
24
+ end
25
+ end
data/lib/pubid/plateau.rb CHANGED
@@ -13,6 +13,7 @@ module Pubid
13
13
  autoload :Identifier, "#{__dir__}/plateau/identifiers/base"
14
14
  autoload :Identifiers, "#{__dir__}/plateau/identifiers"
15
15
  autoload :Parser, "#{__dir__}/plateau/parser"
16
+ autoload :PubidUrnSchema, "#{__dir__}/plateau/pubid_urn_schema"
16
17
  autoload :Renderer, "#{__dir__}/plateau/renderer"
17
18
  autoload :SupplementIdentifier, "#{__dir__}/plateau/supplement_identifier"
18
19
  autoload :UrnGenerator, "#{__dir__}/plateau/urn_generator"
@@ -62,18 +62,24 @@ module Pubid
62
62
  end
63
63
 
64
64
  def decode_core(tokens, index, node, schema)
65
- schema.core.each do |parts|
65
+ schema.core.each_with_index do |parts, i|
66
66
  break if index >= tokens.size
67
67
 
68
68
  token = tokens[index]
69
69
  break if PubidUrn.structural?(token)
70
70
 
71
- Paths.put(node, parts, Escape.decode(token)) unless token == NULL
71
+ unless token == NULL
72
+ Paths.put(node, parts, core_value(token, schema.core_kinds[i]))
73
+ end
72
74
  index += 1
73
75
  end
74
76
  index
75
77
  end
76
78
 
79
+ def core_value(token, kind)
80
+ kind == :int ? integer(token) : Escape.decode(token)
81
+ end
82
+
77
83
  def decode_atoms(tokens, index, node, schema)
78
84
  last = -1
79
85
  while index < tokens.size &&
@@ -69,11 +69,23 @@ module Pubid
69
69
  def core_segments(node, schema)
70
70
  values = schema.core.map { |parts| Paths.take(node, parts) }
71
71
  values.pop while !values.empty? && values.last.nil?
72
- values.map do |value|
73
- value.nil? ? NULL : core_value(value, schema)
72
+ values.each_with_index.map do |value, i|
73
+ next NULL if value.nil?
74
+ next core_integer(value, schema, i) if schema.core_kinds[i] == :int
75
+
76
+ core_value(value, schema)
74
77
  end
75
78
  end
76
79
 
80
+ # A decimal never has the shape of structure, so it needs no escape.
81
+ def core_integer(value, schema, index)
82
+ return value.to_s if value.is_a?(Integer)
83
+
84
+ raise Errors::InvalidInputError,
85
+ "#{schema.type} #{schema.core[index].join('.')}: " \
86
+ "#{value.inspect} is not int"
87
+ end
88
+
77
89
  # A core value that has the shape of structure (PubidUrn::STRUCTURE)
78
90
  # gets its first character escaped.
79
91
  def core_value(value, schema)
@@ -48,13 +48,15 @@ module Pubid
48
48
 
49
49
  # The URN shape of one concrete identifier type.
50
50
  class TypeSchema
51
- attr_reader :flavor, :type, :token, :core, :atoms, :facets
51
+ attr_reader :flavor, :type, :token, :core, :core_kinds, :atoms, :facets
52
52
 
53
+ # +core+ is a list of [path, kind] pairs; kind is :string or :int.
53
54
  def initialize(flavor:, type:, token:, core:, atoms:, facets:)
54
55
  @flavor = flavor
55
56
  @type = type
56
57
  @token = token
57
- @core = core.map { |path| path.split(".") }.freeze
58
+ @core = core.map { |path, _| path.split(".") }.freeze
59
+ @core_kinds = core.map { |_, kind| kind }.freeze
58
60
  @atoms = atoms.freeze
59
61
  @facets = facets.freeze
60
62
  @atom_index = atoms.each_with_index.to_h { |atom, i| [atom.prefix, i] }
@@ -86,6 +88,7 @@ module Pubid
86
88
  NAME = /\A[a-z][a-z0-9]*\z/
87
89
  KINDS = %i[string list bool bools int id ids].freeze
88
90
  FACET_KINDS = %i[string list bool bools].freeze
91
+ CORE_KINDS = %i[string int].freeze
89
92
  # "f" opens the facet group; "type" is the type-metadata URN
90
93
  # (Pubid::IdentifierMetadata, "urn:pubid:<flavor>:type:<key>").
91
94
  RESERVED_TOKENS = %w[f type].freeze
@@ -110,7 +113,9 @@ module Pubid
110
113
 
111
114
  # @param type [String] the +_type+ the class serializes
112
115
  # @param token [String] the short type token of the URN
113
- # @param core [Array<String>] the positional core paths
116
+ # @param core [Array<String, Array>] the positional core paths; an
117
+ # entry is a path (a String value) or [path, kind], kind :string or
118
+ # :int
114
119
  # @param atoms [Array<Array>] extra atoms: [path, prefix, kind]
115
120
  # @param facets [Array<Array>] extra facets: [path, name, kind]
116
121
  def type(type, token, core: %w[number], atoms: [], facets: [])
@@ -126,7 +131,8 @@ module Pubid
126
131
  check_unique!(all_facets.map(&:name), "facet name", type)
127
132
 
128
133
  schema = TypeSchema.new(flavor: flavor, type: type, token: token,
129
- core: core, atoms: all_atoms,
134
+ core: core.map { |spec| build_core(*spec) },
135
+ atoms: all_atoms,
130
136
  facets: all_facets)
131
137
  @types[type] = schema
132
138
  @tokens[token] = schema
@@ -142,6 +148,14 @@ module Pubid
142
148
 
143
149
  private
144
150
 
151
+ def build_core(path, kind = :string)
152
+ unless CORE_KINDS.include?(kind)
153
+ raise ArgumentError, "unknown core kind #{kind.inspect}"
154
+ end
155
+
156
+ [path, kind]
157
+ end
158
+
145
159
  def build_atom(path, prefix, kind = nil)
146
160
  check_name!(prefix, "atom prefix")
147
161
  Atom.new(path, prefix, check_kind!(path, kind, KINDS, "atom"))
@@ -106,6 +106,46 @@ module Pubid
106
106
  string.is_a?(String) && string.b[0, PREFIX.length].downcase == PREFIX
107
107
  end
108
108
 
109
+ # The slug form (relaton#441): the URN wearing HTTP-path clothes.
110
+ # Each ":" segment becomes a "/" segment, after the "urn:pubid:"
111
+ # prefix is cut — "urn:pubid:iso:is:9001:year-2015" is
112
+ # "iso/is/9001/year-2015". The contract's path-safety gate
113
+ # guarantees no segment carries a "/", so the transform is
114
+ # bijective: {from_slug} rebuilds the exact URN.
115
+ #
116
+ # @param source [Identifier, String] an identifier or its
117
+ # pubid-scheme URN
118
+ # @return [String] the slug, with no leading or trailing "/"
119
+ # @raise [Errors::InvalidInputError] when a String is not a
120
+ # canonical pubid-scheme URN
121
+ def to_slug(source)
122
+ urn = source.respond_to?(:to_pubid_urn) ? source.to_pubid_urn : source
123
+ parse(urn) unless source.respond_to?(:to_pubid_urn)
124
+ urn.delete_prefix(PREFIX).tr(":", "/")
125
+ end
126
+
127
+ # The slug parse side: "/"-joined segments rebuild the URN and
128
+ # parse through the canonical gate, so a slug of a landed flavor
129
+ # round-trips by +to_hash+ exactly like its URN.
130
+ #
131
+ # @param string [String] the slug
132
+ # @return [Identifier]
133
+ # @raise [Errors::InvalidInputError] when +string+ is not a String
134
+ # or is longer than MAX_LENGTH
135
+ # @raise [Errors::UrnParseError] for a malformed, unknown or
136
+ # non-canonical slug
137
+ def from_slug(string)
138
+ unless string.is_a?(String)
139
+ raise Errors::InvalidInputError, Pubid::INPUT_NOT_A_STRING_MESSAGE
140
+ end
141
+ raise Errors::InvalidInputError,
142
+ "slug exceeds #{MAX_LENGTH} characters" if string.length > MAX_LENGTH
143
+ raise Errors::UrnParseError, "slug has empty segments" if
144
+ string.split("/", -1).any?(&:empty?)
145
+
146
+ parse("#{PREFIX}#{string.tr('/', ':')}")
147
+ end
148
+
109
149
  # @return [FlavorSchema, nil]
110
150
  def flavor_schema(flavor)
111
151
  return @schemas[flavor] if @schemas.key?(flavor)
@@ -0,0 +1,78 @@
1
+ # SAE flavor notes
2
+
3
+ ## Source of the codes: SAE's Crossref deposits
4
+
5
+ relaton indexes every SAE standard from Crossref (prefix `10.4271`, type
6
+ `standard`). The DOI suffix is SAE's own edition code, lower-case and without
7
+ hyphens: `10.4271/j3016_202104` is `SAE J3016_202104`, `10.4271/amsqqa250_4b`
8
+ is `SAE AMS-QQ-A-250/4B`. relaton upper-cases the suffix and prefixes `SAE `,
9
+ so pubid must read the hyphen-free spelling as well as the printed one.
10
+
11
+ Measured on the full deposit list (2026-11-04, 47,692 DOIs): **46,726
12
+ parse**, and every parsed code round-trips through `to_s`, `from_hash` and
13
+ the URN, with **0 `to_hash` collisions**. The 966 that do not parse are:
14
+
15
+ - **791 CP\*** (`CPGM`, `CPFD`, `CPKW`, …): collected-paper products, not
16
+ standards. Out of scope by decision.
17
+ - **175 one-offs**, none more than 32 of a kind: `SUP1` supplements
18
+ (`AS4468SUP1A`, `ARP4737SUP1`), language tails (`AS9101F_PTBR`,
19
+ `IA9137_DEDE`), `J2534-2/10_5_00`, `_4Q2012`, type-less `SAE6857`,
20
+ `_MMYYYY` dates (`CS1939_042012`, `J2012DA_012013` — the builder's
21
+ month check refuses the J ones, which would otherwise read as year 120,
22
+ month 13), `ISO/SAE21434`, `IEEE/EIA12207`, and ~30 families of 1–8
23
+ documents whose printed form is unknown (`HEB`, `EQB`, `TAHB`, `AMSLP`,
24
+ `AMSHHG`, …). Add a family by adding it to `Components::Type::TYPES` and
25
+ re-baking the grammar (below).
26
+
27
+ ## Model
28
+
29
+ | attribute | example | `===` |
30
+ |---|---|---|
31
+ | `type.abbr` (printed form) | `AMS`, `AMS-QQ-A`, `GEIA-STD` | stated |
32
+ | `number` | `4121`, `0001`, `1939DA`, `174M`, `1939-71` | stated |
33
+ | `part` | `2` of `J2337/2`, `H1025` of `AMS5622/H1025` | strict |
34
+ | `revision` | `J` of `AMS4121J`, `6` of `USCAR2-6` | wildcard |
35
+ | `amendment` | `1` of `AS5259/7A_A1` | strict |
36
+ | `date` (`year`, `month`) | `J3016_202104` | month is a wildcard |
37
+
38
+ The grammar only segments the code; `builder.rb` decides what each segment
39
+ means, because the meaning depends on the family:
40
+
41
+ - **A one-letter tail is the revision** (`AMS4121J`, `AS22520/45A`), so every
42
+ revision of a document shares one `number` bucket and `SAE AMS4121 ===`
43
+ each of them. relaton then picks the highest revision.
44
+ - **Except the `M` of a J or JA report**, which marks the metric document
45
+ (`J174M`) and stays in `number`. AMS revisions do reach `M` (`AMS2400M`),
46
+ so the exception is per family (`Type::METRIC_SUFFIX`).
47
+ - **A longer tail is another document** (`J1939DA` digital annex,
48
+ `J2735SET`) and stays in `number`, in its own bucket.
49
+ - **A dash number is part of `number`** (`J1939-71`, `EIA649-2`; the DOI
50
+ writes some of them `_1`: `EIA649_1A` → `EIA-649-1A`). For USCAR the dash
51
+ is the revision (`USCAR2-6` is USCAR-2 Revision 6, `Type::DASH_REVISION`).
52
+ - **`part` and `amendment` are `subset_strict`**: `SAE J2337` does not match
53
+ `SAE J2337/2`, and `SAE AS5259/7A` does not match `SAE AS5259/7A_A1`.
54
+
55
+ Rendering follows SAE: no space after the type (`SAE J3016`, the old
56
+ `SAE J 3016` still parses), a hyphen after a family that kept a military or
57
+ industry series hyphen (`Type::TYPES` holds the separator), `_YYYYMM` when
58
+ the month is known and `:YYYY` otherwise. The amendment tail `_A1` has no
59
+ known printed form and keeps the DOI's.
60
+
61
+ ## The grammar is baked here from a hand-off source
62
+
63
+ `data/parg/sae.json` was compiled with the pinned parsanol from the source
64
+ recorded in hand-off `pubid__pubid-grammar__sae-crossref-coverage` (the same
65
+ text sits in the artifact's `source` field). Until pubid-grammar lands it,
66
+ `pubid-grammar/grammars/sae.parg` is the OLD grammar, so `parsanol parg
67
+ check` against it reports stale. To change the grammar, extract the source
68
+ (`jq -r .source data/parg/sae.json`), edit, and re-bake:
69
+
70
+ ```bash
71
+ bundle exec parsanol parg compile sae.parg -o data/parg/sae.json
72
+ bundle exec parsanol parg test data/parg/sae.json
73
+ ```
74
+
75
+ The `doc_type` alternatives are the `Type::TYPES` keys plus their
76
+ hyphen-free spellings, longest first (PEG commits to the first match, so
77
+ `AMS` comes after every `AMS…`). `spec/pubid/sae/type_table_spec.rb` fails
78
+ when the two lists drift or an alternative shadows a longer one.
@@ -2,31 +2,109 @@
2
2
 
3
3
  module Pubid
4
4
  module Sae
5
+ # Builds a Pubid::Sae::Identifier from the grammar's bound captures.
6
+ #
7
+ # The grammar only segments the code (data/parg/sae.json):
8
+ # +type number [dash] [tail] [/part [part_tail]] [_A amendment] [date]+.
9
+ # The meaning of each segment is decided here, because it depends on the
10
+ # family:
11
+ #
12
+ # * +type+ is stored in its printed form (+AMSQQA+ -> +AMS-QQ-A+).
13
+ # * A one-letter +tail+ or +part_tail+ is the revision (+AMS4121J+,
14
+ # +AS22520/45A+), except the +M+ of a J or JA report, which marks the
15
+ # metric document (+J174M+) and stays in the number.
16
+ # * A longer tail names another document (+J1939DA+, the digital annex;
17
+ # +J2735SET+) and stays in the number, so it keeps its own index bucket.
18
+ # * A +dash+ is a USCAR revision (+USCAR2-6+); in every other family it
19
+ # is part of the number (+J1939-71+, +EIA649-2+).
5
20
  class Builder < Pubid::Builder::Base
6
21
  def self.build(parsed_data)
7
22
  new.build(parsed_data)
8
23
  end
9
24
 
25
+ def build(data)
26
+ data = flatten_array(data) if data.is_a?(Array)
27
+ data = data.transform_keys(&:to_sym)
28
+ Identifier.new(**attributes_for(data))
29
+ end
30
+
10
31
  private
11
32
 
12
33
  def default_identifier_class
13
34
  Identifier
14
35
  end
15
36
 
16
- def cast(type, value)
17
- case type
18
- when :publisher
19
- "SAE"
20
- when :type
21
- Components::Type.new(abbr: value.to_s)
22
- when :number
23
- value.to_s
24
- when :year
25
- { date: Components::Date.new(year: value.to_i) }
37
+ def attributes_for(data)
38
+ abbr = Components::Type.canonical(data[:type])
39
+ attrs = { type: Components::Type.new(abbr: abbr),
40
+ number: data[:number].to_s }
41
+ apply_dash(attrs, abbr, data[:dash])
42
+ apply_tail(attrs, abbr, data[:tail], part: data[:part])
43
+ apply_part(attrs, data[:part], data[:part_tail])
44
+ attrs[:amendment] = data[:amendment].to_s if data[:amendment]
45
+ attrs[:date] = date_for(data) if data[:year]
46
+ attrs
47
+ end
48
+
49
+ def apply_dash(attrs, abbr, dash)
50
+ return unless dash
51
+
52
+ if Components::Type::DASH_REVISION.include?(abbr)
53
+ attrs[:revision] = dash.to_s
54
+ else
55
+ attrs[:number] += "-#{dash}"
56
+ end
57
+ end
58
+
59
+ # A one-letter tail before a part (AMS4121J/2) or after a USCAR dash
60
+ # revision (USCAR2-6A) has no place in the model: no SAE code has that
61
+ # shape, and storing it in `number` would split the index bucket and
62
+ # print a string that does not parse back. Such input is refused.
63
+ def apply_tail(attrs, abbr, tail, part:)
64
+ return unless tail
65
+
66
+ tail = tail.to_s
67
+ if !revision_letter?(abbr, tail)
68
+ attrs[:number] += tail
69
+ elsif part || attrs[:revision]
70
+ refuse("a revision letter #{tail} before a part or after a " \
71
+ "dash revision")
72
+ else
73
+ attrs[:revision] = tail
74
+ end
75
+ end
76
+
77
+ def apply_part(attrs, part, part_tail)
78
+ return unless part
79
+
80
+ attrs[:part] = part.to_s
81
+ return unless part_tail
82
+
83
+ if part_tail.to_s.length == 1
84
+ attrs[:revision] = part_tail.to_s
26
85
  else
27
- value
86
+ attrs[:part] += part_tail.to_s
28
87
  end
29
88
  end
89
+
90
+ def revision_letter?(abbr, tail)
91
+ return false unless tail.length == 1
92
+
93
+ !(tail == "M" && Components::Type::METRIC_SUFFIX.include?(abbr))
94
+ end
95
+
96
+ def date_for(data)
97
+ month = data[:month]&.to_i
98
+ refuse("month #{data[:month]}") if month && !(1..12).cover?(month)
99
+
100
+ Components::Date.new(year: data[:year].to_i, month: month)
101
+ end
102
+
103
+ def refuse(what)
104
+ raise Pubid::Errors::ParseError.new(
105
+ "SAE identifier with #{what} is not supported", nil, flavor: "sae"
106
+ )
107
+ end
30
108
  end
31
109
  end
32
110
  end
@@ -5,12 +5,18 @@ require "lutaml/model"
5
5
  module Pubid
6
6
  module Sae
7
7
  module Components
8
- # Date component for SAE standards
9
- # SAE uses year only (e.g., 2024, 2022)
8
+ # The edition date of an SAE document.
9
+ #
10
+ # SAE dates a J report by year and month: its edition code is
11
+ # +J3016_202104+, the April 2021 edition. A reference may state the
12
+ # year alone (+SAE J3016:2021+); the month is then nil, and nil is a
13
+ # wildcard under +===+, so the year-only reference matches every
14
+ # edition of that year.
10
15
  class Date < Lutaml::Model::Serializable
11
16
  include ::Pubid::SubsetMatch
12
17
 
13
18
  attribute :year, :integer
19
+ attribute :month, :integer
14
20
 
15
21
  def present?
16
22
  !year.nil?
@@ -20,8 +26,11 @@ module Pubid
20
26
  render
21
27
  end
22
28
 
29
+ # +202104+ with a month (SAE's edition code), +2021+ without.
23
30
  def render(context: nil)
24
- year.to_s
31
+ return year.to_s unless month
32
+
33
+ format("%<year>04d%<month>02d", year: year, month: month)
25
34
  end
26
35
  end
27
36
  end
@@ -5,13 +5,65 @@ require "lutaml/model"
5
5
  module Pubid
6
6
  module Sae
7
7
  module Components
8
- # Type component for SAE document types
9
- # AMS, AIR, ARP, AS, MA
8
+ # The SAE document family: the letters before the number.
9
+ #
10
+ # +abbr+ holds the printed form (+AMS-QQ-A+). SAE's DOI suffixes drop
11
+ # the hyphens (+10.4271/amsqqa250_4b+), so the parser reads both
12
+ # spellings and the builder stores the printed one (see
13
+ # +.canonical+).
10
14
  class Type < Lutaml::Model::Serializable
11
15
  include ::Pubid::SubsetMatch
12
16
 
17
+ # Printed form => the separator SAE prints between it and the number.
18
+ # A family transferred from a military or industry series keeps that
19
+ # series' hyphen (+AMS-QQ-A-250+, +GEIA-STD-0001+, +EIA-649+); the
20
+ # SAE-native families print the number glued (+AMS4121J+, +J3016+).
21
+ # The grammar's +doc_type+ alternatives list exactly these keys plus
22
+ # their hyphen-free spellings; +spec/pubid/sae/type_table_spec.rb+
23
+ # keeps the two in step.
24
+ TYPES = {
25
+ "AMS" => "", "AS" => "", "J" => "", "ARP" => "", "AIR" => "",
26
+ "MA" => "", "MAM" => "", "MAP" => "", "JA" => "", "MS" => "",
27
+ "ARD" => "", "TSB" => "", "CMB" => "", "SSB" => "",
28
+ "CRP" => "-", "USCAR" => "-",
29
+ "EIA" => "-", "EIA-IS" => "-", "EIA-STD" => "-",
30
+ "GEIA" => "-", "GEIA-STD" => "-", "GEIA-HB" => "-", "GEIA-OP" => "-",
31
+ "GEIA-TB" => "-",
32
+ "AMS-STD" => "-", "AMS-DTL" => "-", "AMS-WW-T" => "-",
33
+ "AMS-QQ-A" => "-", "AMS-QQ-C" => "-", "AMS-QQ-N" => "-",
34
+ "AMS-QQ-P" => "-", "AMS-QQ-S" => "-", "AMS-QQ-W" => "-",
35
+ "AMS-PRF" => "-",
36
+ "AMS-A" => "-", "AMS-B" => "-", "AMS-C" => "-", "AMS-D" => "-",
37
+ "AMS-F" => "-",
38
+ "AMS-G" => "-", "AMS-H" => "-", "AMS-I" => "-", "AMS-L" => "-",
39
+ "AMS-M" => "-", "AMS-P" => "-", "AMS-R" => "-", "AMS-S" => "-",
40
+ "AMS-T" => "-", "AMS-W" => "-"
41
+ }.freeze
42
+
43
+ # Hyphen-free spelling => printed form.
44
+ CANONICAL = TYPES.keys.to_h { |abbr| [abbr.delete("-"), abbr] }.freeze
45
+
46
+ # The families whose revision is a dash number (+USCAR2-6+ is
47
+ # USCAR-2 Revision 6), not a letter.
48
+ DASH_REVISION = %w[USCAR].freeze
49
+
50
+ # The families whose trailing +M+ marks the metric edition of a
51
+ # document (+J174M+), not a revision.
52
+ METRIC_SUFFIX = %w[J JA].freeze
53
+
13
54
  attribute :abbr, :string
14
55
 
56
+ # @param spelling [String] +AMSQQA+, +AMS-QQ-A+ or +ams-qq-a+
57
+ # @return [String, nil] the printed form, nil for an unknown family
58
+ def self.canonical(spelling)
59
+ CANONICAL[spelling.to_s.upcase.delete("-")]
60
+ end
61
+
62
+ # The separator printed between this family and its number.
63
+ def separator
64
+ TYPES.fetch(abbr.to_s, "")
65
+ end
66
+
15
67
  def to_s
16
68
  render
17
69
  end
@@ -4,7 +4,7 @@ module Pubid
4
4
  module Sae
5
5
  # Base SAE Identifier. Canonical name Pubid::Sae::Identifier. SAE has a single identifier class that IS the
6
6
  # document type, so this class is both the base and the only concrete type.
7
- # Handles all SAE document types (AMS, AIR, ARP, AS, MA).
7
+ # Handles every SAE document family (Components::Type::TYPES).
8
8
  class Identifier < ::Pubid::Identifier
9
9
  def self.parse(input)
10
10
  unless input.is_a?(String)
@@ -26,8 +26,19 @@ module Pubid
26
26
  # a bare alias of the shared component and never carried more than
27
27
  # `value`, so this is a representation change only.
28
28
  attribute :number, :string
29
+ # The revision letter (AMS4121J -> "J"), or USCAR's revision number
30
+ # (USCAR2-6 -> "6"). Kept out of `number`, so every revision of a
31
+ # document shares one index bucket and a reference without a revision
32
+ # matches them all under `===`.
33
+ attribute :revision, :string
34
+ # The amendment of a revision: AS5259/7A_A1 -> "1".
35
+ attribute :amendment, :string
29
36
  attribute :date, Sae::Components::Date
30
37
 
38
+ # A part (J2337/2) and an amendment (_A1) are other documents: a
39
+ # reference without one names the document that has none.
40
+ subset_strict :part, :amendment
41
+
31
42
  def self.type
32
43
  { key: :base, web: :standard, title: "Standard", short: "SAE" }
33
44
  end
@@ -44,6 +55,48 @@ module Pubid
44
55
  def mr_type
45
56
  type&.abbr&.to_s&.downcase
46
57
  end
58
+
59
+ # The code after the type and its separator, without the date:
60
+ # "4121J", "22520/45A", "5259/7A_A1", "2-6" (USCAR-2 Revision 6).
61
+ # The renderer prints it after the type; the URN carries it as one
62
+ # segment.
63
+ def designation
64
+ dash = dash_revision?
65
+ code = number.to_s
66
+ code += "-#{revision}" if revision && dash
67
+ code += "/#{part}" if part
68
+ code += revision.to_s if revision && !dash
69
+ code += "_A#{amendment}" if amendment
70
+ code
71
+ end
72
+
73
+ # A revision after a part (AS22520/45A) is the part document's
74
+ # revision, so dropping the part drops it too. Without this,
75
+ # `exclude(:part)` would turn it into revision A of the base document
76
+ # (AS22520A) and `matches?(other, ignore: [:part])` would pair the two.
77
+ def exclude(*args)
78
+ args += [:revision] if part && args.include?(:part)
79
+ super
80
+ end
81
+
82
+ # A numbered revision of a USCAR family (USCAR-2-6). A letter revision
83
+ # (USCAR3A) prints glued, as in every other family.
84
+ def dash_revision?
85
+ Sae::Components::Type::DASH_REVISION.include?(type&.abbr.to_s) &&
86
+ revision.to_s.match?(/\A\d+\z/)
87
+ end
88
+
89
+ # Each segment carries a letter, so a dash number (J1939-71 ->
90
+ # "1939-71") never collides with a part (J1939/71 -> "1939-p71").
91
+ def mr_number_with_part
92
+ return nil unless number
93
+
94
+ segments = [number]
95
+ segments << "p#{part}" if part
96
+ segments << "r#{revision}" if revision
97
+ segments << "a#{amendment}" if amendment
98
+ segments.join("-").downcase
99
+ end
47
100
  end
48
101
 
49
102
  end
@@ -2,34 +2,31 @@
2
2
 
3
3
  module Pubid
4
4
  module Sae
5
- # Human-readable renderer for SAE identifiers.
5
+ # Human-readable renderer for SAE identifiers, in the form SAE prints:
6
6
  #
7
- # Produces strings like:
8
- # "SAE AS5553"
9
- # "SAE ARP4754A"
10
- # "SAE AMS2475:2023"
7
+ # "SAE J3016" "SAE AMS4121J"
8
+ # "SAE J3016_202104" "SAE ARP4754A:2010"
9
+ # "SAE AS22520/45A" "SAE AMS-QQ-A-250/4B"
10
+ # "SAE AS5259/7A_A1" "SAE USCAR-2-6"
11
11
  #
12
- # The renderer is registered as the +:human+ format in the SAE format
13
- # registry and invoked via +render(format: :human)+.
12
+ # A dated edition with a month renders SAE's native edition code
13
+ # (+_YYYYMM+); a year alone renders +:YYYY+.
14
14
  class Renderer < ::Pubid::Renderers::Base
15
15
  def render(context: nil, **_opts)
16
16
  id = @id
17
+ type = id.type
18
+ code = type ? "#{type.abbr}#{type.separator}" : ""
19
+ result = [id.publisher, code + id.designation].compact.join(" ")
20
+ result + date_suffix(id, context)
21
+ end
17
22
 
18
- parts = []
19
-
20
- # Publisher and type
21
- parts << id.publisher if id.publisher
22
- parts << id.type.to_s if id.type
23
-
24
- # Number
25
- parts << id.number.to_s if id.number
26
-
27
- result = parts.join(" ")
23
+ private
28
24
 
29
- # Date
30
- result += ":#{id.date.render(context:)}" if id.date
25
+ def date_suffix(id, context)
26
+ return "" unless id.date&.year
31
27
 
32
- result
28
+ separator = id.date.month ? "_" : ":"
29
+ "#{separator}#{id.date.render(context:)}"
33
30
  end
34
31
  end
35
32
  end
@@ -2,6 +2,11 @@
2
2
 
3
3
  module Pubid
4
4
  module Sae
5
+ # urn:sae:{type|std}:{designation}[:{yyyy|yyyymm}]
6
+ #
7
+ # The designation is the printed code after the type (Identifier#
8
+ # designation): "4121J", "22520/45A", "5259/7A_A1". The date segment is
9
+ # the year, or SAE's year-and-month edition date.
5
10
  class UrnGenerator < Pubid::UrnGenerator::Base
6
11
  def urn_type
7
12
  return "std" unless identifier.type
@@ -12,7 +17,7 @@ module Pubid
12
17
  def urn_number
13
18
  return nil unless identifier.number
14
19
 
15
- identifier.number.to_s
20
+ identifier.designation
16
21
  end
17
22
 
18
23
  def generate
@@ -5,7 +5,7 @@ module Pubid
5
5
  # Parses SAE URNs back into identifiers.
6
6
  #
7
7
  # UrnGenerator emits:
8
- # urn:sae:{type|std}:{number}:{year}
8
+ # urn:sae:{type|std}:{designation}[:{yyyy|yyyymm}]
9
9
  # where slot[1] is overwritten with the publisher string when a publisher
10
10
  # is set. The common single-publisher case keeps slot[1] = "sae".
11
11
  #
@@ -25,10 +25,15 @@ module Pubid
25
25
 
26
26
  private
27
27
 
28
+ # The designation follows the type with no separator; the grammar
29
+ # reads a glued number for every family. A six-digit date segment is
30
+ # SAE's year-and-month edition date.
28
31
  def build_text(type_token, number, year)
29
32
  head = type_token == "std" ? "" : type_token.upcase
30
- text = "SAE #{head}#{number}"
31
- text += ":#{year}" if year
33
+ # The designation's letters are upper-case in every SAE code; a URN
34
+ # that a consumer lower-cased (urn:sae:ams:4121j) still resolves.
35
+ text = "SAE #{head}#{number.to_s.upcase}"
36
+ text += "#{year.to_s.length == 6 ? '_' : ':'}#{year}" if year
32
37
  text
33
38
  end
34
39
  end
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.8"
4
+ VERSION = "2.0.9"
5
5
  end
data/lib/pubid.rb CHANGED
@@ -300,6 +300,26 @@ module Pubid
300
300
  urn_parser.parse(string)
301
301
  end
302
302
 
303
+ # The slug form (relaton#441): the pubid-scheme URN of +source+ in
304
+ # HTTP-path clothes — "/"-joined segments after the "urn:pubid:"
305
+ # prefix ("iso/is/9001/year-2015"). Bijective with
306
+ # {PubidUrn.from_slug}.
307
+ #
308
+ # @param source [Identifier, String] an identifier or its pubid-scheme URN
309
+ # @return [String]
310
+ def self.to_slug(source)
311
+ PubidUrn.to_slug(source)
312
+ end
313
+
314
+ # Parse a slug (see {PubidUrn.from_slug}).
315
+ #
316
+ # @param string [String] the slug
317
+ # @return [Identifier]
318
+ def self.from_slug(string)
319
+ eager_load_flavors!
320
+ PubidUrn.from_slug(string)
321
+ end
322
+
303
323
  # Parse a pubid-scheme URN, and only that: an official URN such as
304
324
  # "urn:iso:std:…" raises. For a consumer that takes a slug and must not
305
325
  # accept a lossy official URN by mistake (relaton/relaton#441).
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.0.9
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-11-04 00:00:00.000000000 Z
11
+ date: 2026-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -361,6 +361,7 @@ files:
361
361
  - lib/pubid/cen_cenelec/identifiers/technical_report.rb
362
362
  - lib/pubid/cen_cenelec/identifiers/technical_specification.rb
363
363
  - lib/pubid/cen_cenelec/parser.rb
364
+ - lib/pubid/cen_cenelec/pubid_urn_schema.rb
364
365
  - lib/pubid/cen_cenelec/renderer.rb
365
366
  - lib/pubid/cen_cenelec/single_identifier.rb
366
367
  - lib/pubid/cen_cenelec/urn_generator.rb
@@ -479,6 +480,7 @@ files:
479
480
  - lib/pubid/etsi/identifiers/etsi_standard.rb
480
481
  - lib/pubid/etsi/identifiers/supplement_identifier.rb
481
482
  - lib/pubid/etsi/parser.rb
483
+ - lib/pubid/etsi/pubid_urn_schema.rb
482
484
  - lib/pubid/etsi/renderer.rb
483
485
  - lib/pubid/etsi/urn_generator.rb
484
486
  - lib/pubid/etsi/urn_parser.rb
@@ -521,6 +523,7 @@ files:
521
523
  - lib/pubid/gost/identifiers/interstate_standard.rb
522
524
  - lib/pubid/gost/identifiers/national_standard.rb
523
525
  - lib/pubid/gost/parser.rb
526
+ - lib/pubid/gost/pubid_urn_schema.rb
524
527
  - lib/pubid/gost/renderer.rb
525
528
  - lib/pubid/gost/urn_generator.rb
526
529
  - lib/pubid/gost/urn_parser.rb
@@ -943,6 +946,7 @@ files:
943
946
  - lib/pubid/parsers/urn.rb
944
947
  - lib/pubid/pg.rb
945
948
  - lib/pubid/plateau.rb
949
+ - lib/pubid/plateau/CLAUDE.md
946
950
  - lib/pubid/plateau/builder.rb
947
951
  - lib/pubid/plateau/identifier.rb
948
952
  - lib/pubid/plateau/identifiers.rb
@@ -951,6 +955,7 @@ files:
951
955
  - lib/pubid/plateau/identifiers/handbook.rb
952
956
  - lib/pubid/plateau/identifiers/technical_report.rb
953
957
  - lib/pubid/plateau/parser.rb
958
+ - lib/pubid/plateau/pubid_urn_schema.rb
954
959
  - lib/pubid/plateau/renderer.rb
955
960
  - lib/pubid/plateau/supplement_identifier.rb
956
961
  - lib/pubid/plateau/urn_generator.rb
@@ -982,6 +987,7 @@ files:
982
987
  - lib/pubid/rendering/stage.rb
983
988
  - lib/pubid/rendering/supplement.rb
984
989
  - lib/pubid/sae.rb
990
+ - lib/pubid/sae/CLAUDE.md
985
991
  - lib/pubid/sae/builder.rb
986
992
  - lib/pubid/sae/components.rb
987
993
  - lib/pubid/sae/components/code.rb