pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.12

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 (461) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/components/date.rb +10 -4
  82. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  83. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  84. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -2
  85. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  86. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  87. data/lib/pubid/bsi/identifiers/handbook.rb +1 -3
  88. data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -2
  89. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  90. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  91. data/lib/pubid/bsi/parser.rb +1 -1
  92. data/lib/pubid/bsi/renderer.rb +63 -51
  93. data/lib/pubid/bsi/single_identifier.rb +27 -9
  94. data/lib/pubid/bsi/urn_generator.rb +11 -2
  95. data/lib/pubid/bsi.rb +7 -1
  96. data/lib/pubid/builder/base.rb +18 -8
  97. data/lib/pubid/bundled_identifier.rb +16 -6
  98. data/lib/pubid/calconnect/identifier.rb +9 -5
  99. data/lib/pubid/calconnect/parser.rb +1 -1
  100. data/lib/pubid/calconnect.rb +7 -1
  101. data/lib/pubid/ccsds/identifier.rb +13 -2
  102. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  103. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  104. data/lib/pubid/ccsds/parser.rb +1 -1
  105. data/lib/pubid/ccsds/single_identifier.rb +16 -12
  106. data/lib/pubid/ccsds.rb +1 -0
  107. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  108. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  109. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  110. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  111. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  112. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  115. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  116. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  117. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  118. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  119. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  120. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  123. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  124. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  125. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  126. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  127. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  128. data/lib/pubid/cen_cenelec.rb +28 -10
  129. data/lib/pubid/cie/builder.rb +10 -0
  130. data/lib/pubid/cie/identifier.rb +6 -1
  131. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  132. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  133. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  134. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  135. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  136. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  137. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  138. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  139. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  140. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  141. data/lib/pubid/cie/parser.rb +1 -1
  142. data/lib/pubid/cie.rb +7 -1
  143. data/lib/pubid/conformance/checks.rb +72 -0
  144. data/lib/pubid/conformance/corpus/case.rb +55 -0
  145. data/lib/pubid/conformance/corpus.rb +36 -0
  146. data/lib/pubid/conformance/generator.rb +212 -0
  147. data/lib/pubid/conformance/pending.rb +63 -0
  148. data/lib/pubid/conformance/runner.rb +153 -0
  149. data/lib/pubid/conformance.rb +55 -0
  150. data/lib/pubid/core.rb +2 -0
  151. data/lib/pubid/csa/builder.rb +47 -47
  152. data/lib/pubid/csa/composite_identifier.rb +14 -19
  153. data/lib/pubid/csa/identifier.rb +143 -81
  154. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  155. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  156. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  157. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  158. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  159. data/lib/pubid/csa/identifiers/package.rb +2 -2
  160. data/lib/pubid/csa/parser.rb +6 -2
  161. data/lib/pubid/csa/renderer.rb +13 -5
  162. data/lib/pubid/csa/single_identifier.rb +93 -4
  163. data/lib/pubid/csa/urn_generator.rb +9 -2
  164. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  165. data/lib/pubid/csa.rb +7 -1
  166. data/lib/pubid/doi/identifier.rb +6 -3
  167. data/lib/pubid/doi/parser.rb +1 -1
  168. data/lib/pubid/doi.rb +1 -0
  169. data/lib/pubid/easc/identifier.rb +0 -2
  170. data/lib/pubid/easc/parser.rb +10 -2
  171. data/lib/pubid/easc.rb +1 -0
  172. data/lib/pubid/ecma/builder.rb +23 -9
  173. data/lib/pubid/ecma/identifier.rb +81 -10
  174. data/lib/pubid/ecma/parser.rb +39 -4
  175. data/lib/pubid/ecma/renderer.rb +29 -8
  176. data/lib/pubid/ecma/urn_generator.rb +34 -13
  177. data/lib/pubid/ecma/urn_parser.rb +30 -14
  178. data/lib/pubid/ecma.rb +1 -0
  179. data/lib/pubid/errors.rb +80 -0
  180. data/lib/pubid/etsi/builder.rb +6 -1
  181. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  182. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  183. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  184. data/lib/pubid/etsi/parser.rb +1 -1
  185. data/lib/pubid/etsi.rb +1 -0
  186. data/lib/pubid/evs/builder.rb +26 -0
  187. data/lib/pubid/evs/identifier.rb +37 -0
  188. data/lib/pubid/evs/identifiers/national_adoption.rb +22 -0
  189. data/lib/pubid/evs/identifiers.rb +9 -0
  190. data/lib/pubid/evs/parser.rb +27 -0
  191. data/lib/pubid/evs/renderer.rb +30 -0
  192. data/lib/pubid/evs/urn_generator.rb +30 -0
  193. data/lib/pubid/evs/urn_parser.rb +80 -0
  194. data/lib/pubid/evs.rb +90 -0
  195. data/lib/pubid/export.rb +1 -0
  196. data/lib/pubid/format_detector.rb +1 -0
  197. data/lib/pubid/gb/builder.rb +14 -9
  198. data/lib/pubid/gb/identifier.rb +29 -7
  199. data/lib/pubid/gb/parser.rb +5 -1
  200. data/lib/pubid/gb/renderer.rb +4 -3
  201. data/lib/pubid/gb.rb +2 -0
  202. data/lib/pubid/gost/builder.rb +46 -9
  203. data/lib/pubid/gost/identifier.rb +0 -2
  204. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  205. data/lib/pubid/gost/parser.rb +10 -2
  206. data/lib/pubid/gost.rb +1 -0
  207. data/lib/pubid/iala/identifier.rb +9 -6
  208. data/lib/pubid/iala/parser.rb +10 -2
  209. data/lib/pubid/iala/urn_parser.rb +6 -2
  210. data/lib/pubid/iala.rb +1 -0
  211. data/lib/pubid/iana/builder.rb +3 -1
  212. data/lib/pubid/iana/identifier.rb +74 -6
  213. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  214. data/lib/pubid/iana/parser.rb +1 -1
  215. data/lib/pubid/iana/renderer.rb +3 -0
  216. data/lib/pubid/iana/urn_generator.rb +5 -1
  217. data/lib/pubid/iana.rb +1 -0
  218. data/lib/pubid/identifier.rb +605 -13
  219. data/lib/pubid/identifier_metadata.rb +1 -0
  220. data/lib/pubid/idf/builder.rb +3 -3
  221. data/lib/pubid/idf/identifier.rb +25 -9
  222. data/lib/pubid/idf/identifiers/amendment.rb +1 -1
  223. data/lib/pubid/idf/identifiers/corrigendum.rb +1 -1
  224. data/lib/pubid/idf/identifiers/international_standard.rb +1 -1
  225. data/lib/pubid/idf/identifiers/reviewed_method.rb +1 -1
  226. data/lib/pubid/idf/parser.rb +1 -1
  227. data/lib/pubid/idf/renderer.rb +4 -4
  228. data/lib/pubid/idf/single_identifier.rb +1 -1
  229. data/lib/pubid/idf/urn_generator.rb +1 -1
  230. data/lib/pubid/idf.rb +7 -1
  231. data/lib/pubid/iec/builder.rb +14 -7
  232. data/lib/pubid/iec/components.rb +0 -1
  233. data/lib/pubid/iec/identifier.rb +73 -83
  234. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  235. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  236. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  237. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  238. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  239. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  240. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  241. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  242. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  243. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  244. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  245. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  246. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  247. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  248. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  249. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  250. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  251. data/lib/pubid/iec/parser.rb +2 -18
  252. data/lib/pubid/iec/renderer.rb +13 -10
  253. data/lib/pubid/iec/single_identifier.rb +33 -10
  254. data/lib/pubid/iec/urn_generator.rb +227 -61
  255. data/lib/pubid/iec/urn_parser.rb +42 -4
  256. data/lib/pubid/iec.rb +1 -0
  257. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  258. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  259. data/lib/pubid/ieee/builder.rb +87 -3
  260. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  261. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  262. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  263. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  264. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  265. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  266. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  267. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  268. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  269. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  270. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  271. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  272. data/lib/pubid/ieee/identifiers/si_standard.rb +5 -2
  273. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  274. data/lib/pubid/ieee/ire/parser.rb +20 -2
  275. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  276. data/lib/pubid/ieee/parser.rb +208 -33
  277. data/lib/pubid/ieee/project_renderer.rb +46 -0
  278. data/lib/pubid/ieee/renderer.rb +11 -8
  279. data/lib/pubid/ieee/urn_generator.rb +32 -3
  280. data/lib/pubid/ieee.rb +9 -1
  281. data/lib/pubid/ieee_debug.rb +1 -0
  282. data/lib/pubid/ietf/builder.rb +24 -8
  283. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  284. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  285. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  286. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  287. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  288. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  289. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  290. data/lib/pubid/ietf/identifiers.rb +1 -0
  291. data/lib/pubid/ietf/parser.rb +27 -9
  292. data/lib/pubid/ietf/renderer.rb +44 -6
  293. data/lib/pubid/ietf/urn_generator.rb +20 -6
  294. data/lib/pubid/ietf/urn_parser.rb +10 -2
  295. data/lib/pubid/ietf.rb +12 -2
  296. data/lib/pubid/iho/identifiers/base.rb +10 -3
  297. data/lib/pubid/iho/parser.rb +1 -1
  298. data/lib/pubid/iho.rb +1 -0
  299. data/lib/pubid/isbn/identifier.rb +25 -7
  300. data/lib/pubid/isbn/parser.rb +1 -1
  301. data/lib/pubid/isbn.rb +1 -0
  302. data/lib/pubid/iso/builder.rb +10 -11
  303. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  304. data/lib/pubid/iso/combined_identifier.rb +5 -3
  305. data/lib/pubid/iso/components.rb +0 -1
  306. data/lib/pubid/iso/identifier.rb +23 -11
  307. data/lib/pubid/iso/identifiers/directives.rb +7 -4
  308. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  309. data/lib/pubid/iso/identifiers/tc_document.rb +31 -46
  310. data/lib/pubid/iso/normalizer.rb +1 -1
  311. data/lib/pubid/iso/parser.rb +1 -1
  312. data/lib/pubid/iso/urn_generator.rb +10 -10
  313. data/lib/pubid/iso.rb +12 -5
  314. data/lib/pubid/itu/builder.rb +10 -0
  315. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  316. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  317. data/lib/pubid/itu/identifiers/base.rb +29 -5
  318. data/lib/pubid/itu/identifiers/contribution.rb +31 -0
  319. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  320. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  321. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  322. data/lib/pubid/itu/identifiers.rb +1 -0
  323. data/lib/pubid/itu/parser.rb +20 -3
  324. data/lib/pubid/itu.rb +1 -0
  325. data/lib/pubid/jcgm/builder.rb +1 -1
  326. data/lib/pubid/jcgm/identifier.rb +10 -0
  327. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  328. data/lib/pubid/jcgm/parser.rb +10 -3
  329. data/lib/pubid/jcgm/renderer.rb +10 -3
  330. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  331. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  332. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  333. data/lib/pubid/jcgm.rb +10 -0
  334. data/lib/pubid/jis/identifier.rb +10 -3
  335. data/lib/pubid/jis/parser.rb +1 -1
  336. data/lib/pubid/jis.rb +1 -0
  337. data/lib/pubid/nist/builder.rb +12 -12
  338. data/lib/pubid/nist/components/stage.rb +2 -2
  339. data/lib/pubid/nist/configuration.rb +6 -8
  340. data/lib/pubid/nist/identifiers/base.rb +22 -5
  341. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  342. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  343. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  344. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  345. data/lib/pubid/nist/identifiers/crpl_report.rb +12 -7
  346. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +2 -2
  347. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  348. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  349. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +16 -8
  350. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  351. data/lib/pubid/nist/identifiers/report.rb +14 -6
  352. data/lib/pubid/nist/parser.rb +1 -1
  353. data/lib/pubid/nist/series/ir.rb +3 -7
  354. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  355. data/lib/pubid/nist.rb +7 -1
  356. data/lib/pubid/oasis/builder.rb +12 -10
  357. data/lib/pubid/oasis/identifier.rb +89 -11
  358. data/lib/pubid/oasis/parser.rb +1 -1
  359. data/lib/pubid/oasis.rb +7 -1
  360. data/lib/pubid/ogc/identifier.rb +9 -5
  361. data/lib/pubid/ogc/parser.rb +1 -1
  362. data/lib/pubid/ogc.rb +1 -0
  363. data/lib/pubid/oiml/builder.rb +7 -3
  364. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  365. data/lib/pubid/oiml/identifiers/bulletin.rb +40 -10
  366. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  367. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  368. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  369. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  370. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  371. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  372. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  373. data/lib/pubid/oiml/identifiers.rb +1 -0
  374. data/lib/pubid/oiml/parser.rb +8 -3
  375. data/lib/pubid/oiml/renderer.rb +3 -3
  376. data/lib/pubid/oiml/single_identifier.rb +26 -42
  377. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  378. data/lib/pubid/oiml/urn_generator.rb +4 -4
  379. data/lib/pubid/oiml.rb +7 -1
  380. data/lib/pubid/omg/builder.rb +1 -0
  381. data/lib/pubid/omg/identifier.rb +20 -3
  382. data/lib/pubid/omg/parser.rb +64 -11
  383. data/lib/pubid/omg/renderer.rb +13 -1
  384. data/lib/pubid/omg.rb +2 -1
  385. data/lib/pubid/parser/grammar.rb +74 -0
  386. data/lib/pubid/parser.rb +2 -0
  387. data/lib/pubid/parsers/mr_string.rb +19 -0
  388. data/lib/pubid/plateau/parser.rb +1 -1
  389. data/lib/pubid/plateau.rb +10 -0
  390. data/lib/pubid/prefixes_support.rb +1 -0
  391. data/lib/pubid/renderers/annotator.rb +233 -0
  392. data/lib/pubid/renderers/base.rb +13 -0
  393. data/lib/pubid/renderers/directives_renderer.rb +6 -4
  394. data/lib/pubid/renderers/human_readable.rb +3 -3
  395. data/lib/pubid/renderers/mr_string.rb +15 -9
  396. data/lib/pubid/renderers.rb +1 -0
  397. data/lib/pubid/rendering/numbering.rb +25 -7
  398. data/lib/pubid/rendering.rb +1 -0
  399. data/lib/pubid/sae/builder.rb +1 -1
  400. data/lib/pubid/sae/identifiers/base.rb +13 -3
  401. data/lib/pubid/sae/parser.rb +1 -1
  402. data/lib/pubid/sae/urn_generator.rb +1 -1
  403. data/lib/pubid/sae.rb +1 -0
  404. data/lib/pubid/schema/declaration.rb +33 -0
  405. data/lib/pubid/schema/error.rb +14 -0
  406. data/lib/pubid/schema/identifier_type.rb +23 -0
  407. data/lib/pubid/schema/loader.rb +117 -0
  408. data/lib/pubid/schema/typed_stage.rb +24 -0
  409. data/lib/pubid/schema.rb +25 -0
  410. data/lib/pubid/tgpp/builder.rb +1 -1
  411. data/lib/pubid/tgpp/identifier.rb +21 -5
  412. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  413. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  414. data/lib/pubid/tgpp/parser.rb +22 -6
  415. data/lib/pubid/tgpp/renderer.rb +21 -5
  416. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  417. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  418. data/lib/pubid/tgpp.rb +7 -1
  419. data/lib/pubid/type_resolver.rb +16 -1
  420. data/lib/pubid/un/identifier.rb +6 -3
  421. data/lib/pubid/un/parser.rb +1 -1
  422. data/lib/pubid/un.rb +1 -0
  423. data/lib/pubid/urn_parser/errors.rb +8 -1
  424. data/lib/pubid/urn_parser.rb +1 -0
  425. data/lib/pubid/utils.rb +1 -0
  426. data/lib/pubid/version.rb +1 -1
  427. data/lib/pubid/w3c/builder.rb +5 -5
  428. data/lib/pubid/w3c/identifier.rb +38 -9
  429. data/lib/pubid/w3c/parser.rb +1 -1
  430. data/lib/pubid/w3c/renderer.rb +1 -1
  431. data/lib/pubid/w3c/urn_generator.rb +2 -2
  432. data/lib/pubid/w3c.rb +1 -0
  433. data/lib/pubid/xsf/identifier.rb +9 -5
  434. data/lib/pubid/xsf/parser.rb +17 -4
  435. data/lib/pubid/xsf.rb +7 -1
  436. data/lib/pubid.rb +399 -21
  437. data/lib/tasks/conformance.rake +34 -0
  438. data/lib/tasks/docs.rake +16 -14
  439. data/lib/tasks/schema.rake +88 -0
  440. data/schema/core/joint_prefixes.yaml +24 -0
  441. data/schema/iec.yaml +1017 -0
  442. data/schema/iso.yaml +1586 -0
  443. data/schema/schema.schema.yaml +96 -0
  444. metadata +40 -19
  445. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  446. data/archived-gems/pubid-iec/stages.yaml +0 -129
  447. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  448. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  449. data/archived-gems/pubid-iso/stages.yaml +0 -106
  450. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  451. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  452. data/archived-gems/pubid-itu/series.yaml +0 -42
  453. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  454. data/archived-gems/pubid-nist/series.yaml +0 -121
  455. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  456. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  457. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +0 -70
  458. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  459. data/lib/pubid/iec/components/code.rb +0 -36
  460. data/lib/pubid/iso/components/code.rb +0 -24
  461. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Conformance
5
+ # Migrates the ground-truth fixture library (spec/fixtures/{flavor}/
6
+ # identifiers/{pass,fail}/*.txt) into the neutral corpus by running the
7
+ # reference implementation. Fixture lines that normalize to the same
8
+ # identifier collapse into one canonical case carrying the old
9
+ # spellings as non_normalized_aliases (old => normalized, and the
10
+ # normalized form round-trips). Unparseable pass lines are recorded as
11
+ # visible debt; fail lines (stored as "#ID# Error: ..." comments in the
12
+ # fixture files) become negative cases. Nothing is sampled, filtered,
13
+ # or dropped.
14
+ class Generator
15
+ attr_reader :flavor
16
+
17
+ def initialize(flavor)
18
+ require "pubid"
19
+
20
+ @flavor = flavor.to_s
21
+ Pubid.eager_load_flavors!
22
+ @flavor_module = Pubid::Registry.get(@flavor)
23
+ raise ArgumentError, "unknown flavor #{@flavor}" unless @flavor_module
24
+ end
25
+
26
+ def generate(output_dir:)
27
+ results = { files: 0, cases: 0, negative: 0, debt: 0, aliases: 0,
28
+ duplicates: 0, reclassify: 0, roundtrip_failures: 0 }
29
+ debt = []
30
+ pass_files.each do |path|
31
+ type = File.basename(path, ".txt")
32
+ cases, type_debt = build_pass_cases(path, type, results)
33
+ debt.concat(type_debt)
34
+ next if cases.empty?
35
+
36
+ write_yaml(File.join(output_dir, "#{type}.yaml"), cases)
37
+ results[:files] += 1
38
+ end
39
+ results[:debt] = debt.size
40
+ write_yaml(File.join(output_dir, "_debt.yaml"), debt) if debt.any?
41
+ negative = build_negative_cases(results)
42
+ if negative.any?
43
+ write_yaml(File.join(output_dir, "_negative.yaml"), negative)
44
+ end
45
+ results
46
+ end
47
+
48
+ private
49
+
50
+ def pass_files
51
+ Dir[File.join(fixtures_dir, "identifiers", "pass", "*.txt")] +
52
+ Dir[File.join(fixtures_dir, "identifiers", "full", "*.txt")]
53
+ end
54
+
55
+ def fail_files
56
+ Dir[File.join(fixtures_dir, "identifiers", "fail", "*.txt")]
57
+ end
58
+
59
+ def fixtures_dir
60
+ File.join(tests_repo, "reference-docs", @flavor)
61
+ end
62
+
63
+ def tests_repo
64
+ @tests_repo ||= ENV.fetch("PUBID_TESTSUITE_PATH",
65
+ File.expand_path("../../../../pubid-testsuite",
66
+ __dir__))
67
+ end
68
+
69
+ def fixture_lines(path)
70
+ File.readlines(path).map(&:strip).reject do |line|
71
+ line.empty? || line.start_with?("#")
72
+ end
73
+ end
74
+
75
+ # Fail fixtures store identifiers as comment lines shaped
76
+ # "#IDENTIFIER# Parslet::ParseFailed: ...". Strip the leading # and
77
+ # the trailing historical annotation to recover the input.
78
+ def fail_fixture_lines(path)
79
+ File.readlines(path).filter_map do |raw|
80
+ line = raw.strip
81
+ next if line.empty?
82
+ next line unless line.start_with?("#")
83
+
84
+ input = line.sub(/\A#/, "").split("#", 2).first.to_s.strip
85
+ input unless input.empty?
86
+ end
87
+ end
88
+
89
+ def build_pass_cases(path, type, results)
90
+ groups = {}
91
+ debt = []
92
+ fixture_lines(path).each do |input|
93
+ record = build_record(input)
94
+ if record.key?(:debt)
95
+ debt << record[:debt]
96
+ next
97
+ end
98
+ key = JSON.generate(record[:case]["identifier"]) +
99
+ record[:case]["representations"]["human"]
100
+ (groups[key] ||= { record: record[:case],
101
+ inputs: [] })[:inputs] << input
102
+ end
103
+ cases = assign_ids(groups, type, results)
104
+ [cases, debt]
105
+ end
106
+
107
+ def assign_ids(groups, type, results)
108
+ groups.values.sort_by { |g| canonical_input(g) }.each_with_index
109
+ .map do |group, index|
110
+ record = group[:record]
111
+ record["id"] = format("%s.%s.%04d", @flavor, type, index + 1)
112
+ canonical = canonical_input(group)
113
+ record["style"] = Conformance.style_for(canonical)
114
+ inputs = group[:inputs]
115
+ duplicates = [inputs.count(canonical) - 1, 0].max
116
+ results[:duplicates] += duplicates
117
+ aliases = inputs.reject { |i| i == canonical }.uniq.sort
118
+ results[:aliases] += aliases.size
119
+ unless aliases.empty?
120
+ record["non_normalized_aliases"] = aliases.map do |a|
121
+ { "input" => a, "style" => Conformance.style_for(a) }
122
+ end
123
+ end
124
+ results[:roundtrip_failures] += 1 if record["roundtrip"] == false
125
+ results[:cases] += 1
126
+ record
127
+ end
128
+ end
129
+
130
+ def canonical_input(group)
131
+ group.dig(:record, "representations", "human")
132
+ end
133
+
134
+ def build_record(input)
135
+ identifier = @flavor_module.parse(input)
136
+ hash = identifier.to_hash
137
+ representations = { "human" => identifier.to_s }
138
+ urn = safe_urn(identifier)
139
+ representations["urn"] = urn if urn
140
+ record = { "id" => nil,
141
+ "identifier" => Conformance.plainify(hash) }
142
+ record["representations"] = representations
143
+ record["roundtrip"] = false unless round_trips?(hash)
144
+ quarantine!(record) if bundled_month_bug?(hash)
145
+ { case: record }
146
+ rescue StandardError => e
147
+ { debt: { "id" => "#{@flavor}.debt.#{input.hash.abs}",
148
+ "input" => input,
149
+ "expect" => { "error" =>
150
+ { "code" => Conformance.error_code_for(e) } },
151
+ "notes" => "unparsed ground-truth fixture: " \
152
+ "#{e.class}: #{e.message.to_s[0, 200]}" } }
153
+ end
154
+
155
+ # Reference implementation parses bundled directive supplements with
156
+ # month-bearing dates nondeterministically across load orders (a
157
+ # spurious day component appears). Quarantine deterministically.
158
+ def bundled_month_bug?(hash)
159
+ hash.to_s.include?("bundled_identifier") && hash.to_s.include?("month")
160
+ end
161
+
162
+ def quarantine!(record)
163
+ record.delete("identifier")
164
+ record.delete("representations")
165
+ record.delete("roundtrip")
166
+ record.delete("non_normalized_aliases")
167
+ record["notes"] = "nondeterministic parse in reference " \
168
+ "implementation (month/day divergence) - " \
169
+ "reference bug, quarantined from gates"
170
+ end
171
+
172
+ def round_trips?(hash)
173
+ @flavor_module::Identifier.from_hash(hash).to_hash == hash
174
+ rescue StandardError
175
+ false
176
+ end
177
+
178
+ def safe_urn(identifier)
179
+ identifier.to_urn
180
+ rescue StandardError
181
+ nil
182
+ end
183
+
184
+ def build_negative_cases(results)
185
+ cases = []
186
+ fail_files.each do |path|
187
+ type = File.basename(path, ".txt")
188
+ fail_fixture_lines(path).each_with_index do |input, index|
189
+ cases << negative_case(format("%s.neg.%s.%04d",
190
+ @flavor, type, index + 1), input)
191
+ results[:negative] += 1
192
+ end
193
+ end
194
+ cases
195
+ end
196
+
197
+ def negative_case(id, input)
198
+ @flavor_module.parse(input)
199
+ { "id" => id, "input" => input, "style" => Conformance.style_for(input),
200
+ "notes" => "fail fixture unexpectedly parsed - reclassify" }
201
+ rescue StandardError => e
202
+ { "id" => id, "input" => input,
203
+ "expect" => { "error" =>
204
+ { "code" => Conformance.error_code_for(e) } } }
205
+ end
206
+
207
+ def write_yaml(path, entries)
208
+ File.write(path, YAML.dump(entries))
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Conformance
5
+ # Implementation-side PENDING markers: corpus cases this implementation
6
+ # explicitly cannot handle yet. The testsuite owns expectations; this
7
+ # file owns the admission that they are not met - so a pending case is
8
+ # visible debt, never a silent failure and never a hidden pass.
9
+ #
10
+ # Format (conformance/pending.yaml at the implementation repo root):
11
+ # "<case-id glob>":
12
+ # reason: <one line, the root cause>
13
+ # since: <YYYY-MM-DD>
14
+ # ref: <TODO.correct-test-suite PR queue entry or contract id>
15
+ #
16
+ # Rules enforced elsewhere: a pending case that passes raises the
17
+ # "pending satisfied - remove the marker" alarm; entries without a ref
18
+ # are rejected at load.
19
+ class Pending
20
+ PATH = File.expand_path("../../../conformance/pending.yaml", __dir__)
21
+
22
+ class << self
23
+ def for(case_id)
24
+ entry = registry[case_id]
25
+ return entry if entry
26
+
27
+ registry.each do |pattern, meta|
28
+ next if pattern == case_id
29
+
30
+ return meta if File.fnmatch(pattern, case_id)
31
+ end
32
+ nil
33
+ end
34
+
35
+ def empty?
36
+ registry.empty?
37
+ end
38
+
39
+ private
40
+
41
+ def registry
42
+ @registry ||= load_registry
43
+ end
44
+
45
+ def load_registry
46
+ return {} unless File.exist?(PATH)
47
+
48
+ (YAML.safe_load_file(PATH) || {})
49
+ .to_h { |pattern, meta| [pattern, validated(meta, pattern)] }
50
+ end
51
+
52
+ def validated(meta, pattern)
53
+ unless meta.is_a?(Hash) && meta["reason"] && meta["ref"]
54
+ raise ArgumentError,
55
+ "pending entry #{pattern.inspect} needs reason and ref"
56
+ end
57
+
58
+ meta
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Conformance
5
+ # Executes the pubid-testsuite corpus against this implementation.
6
+ # CLEAN flavors hard-gate (failures abort); DIRTY flavors report as
7
+ # the known reference-defect ledger.
8
+ class Runner
9
+ REGISTRY_KEYS = { "tgpp" => "3gpp" }.freeze
10
+
11
+ def run(flavors = corpus_flavors)
12
+ require "pubid"
13
+
14
+ if flavors.empty?
15
+ raise ArgumentError, "no corpus flavors found at #{corpus_dir} - " \
16
+ "refusing to report vacuous success"
17
+ end
18
+
19
+ Pubid.eager_load_flavors!
20
+ flavors.each_with_object([]) do |flavor, failures|
21
+ failures.concat(run_flavor(flavor))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def corpus_flavors
28
+ Dir[File.join(corpus_dir, "*")].select { |p| File.directory?(p) }
29
+ .map { |p| File.basename(p) }
30
+ end
31
+
32
+ def corpus_dir
33
+ File.join(ENV.fetch("PUBID_TESTSUITE_PATH",
34
+ File.expand_path("../../../../pubid-testsuite",
35
+ __dir__)), "tests")
36
+ end
37
+
38
+ def run_flavor(flavor)
39
+ flavor_module = Pubid::Registry.get(
40
+ REGISTRY_KEYS.fetch(flavor, flavor),
41
+ )
42
+ raise ArgumentError, "unknown flavor #{flavor}" if flavor_module.nil?
43
+
44
+ stats = Hash.new(0)
45
+ failures = []
46
+ each_payload_case(flavor) do |test_case|
47
+ execute(test_case, flavor_module, stats, failures)
48
+ end
49
+ Corpus.negative_file(flavor, corpus_dir).each do |path|
50
+ Corpus.load_file(path).each do |test_case|
51
+ execute_negative(test_case, flavor_module, stats, failures)
52
+ end
53
+ end
54
+ report(flavor, stats, failures)
55
+ known_dirty?(flavor) ? [] : failures
56
+ end
57
+
58
+ def each_payload_case(flavor, &block)
59
+ Corpus.case_files(flavor, corpus_dir).each do |path|
60
+ Corpus.load_file(path).each(&block)
61
+ end
62
+ end
63
+
64
+ def known_dirty?(flavor)
65
+ status = File.join(corpus_dir, flavor, "_status.yaml")
66
+ File.exist?(status) && !YAML.safe_load_file(status)["clean"]
67
+ end
68
+
69
+ # Every corpus case lands in exactly one quadrant:
70
+ # pass | FAIL (not pending, breaks the CLEAN gate) | pending
71
+ # (explicitly not handled yet, see Conformance::Pending) | review
72
+ # (the expectation itself is flagged for human verification).
73
+ def execute(test_case, flavor_module, stats, failures)
74
+ id = test_case.id
75
+ return stats[:review] += 1 if test_case.review?
76
+
77
+ if Pending.for(id)
78
+ run_pending(id, test_case, flavor_module, stats, failures)
79
+ else
80
+ run_case(test_case, flavor_module, stats, failures)
81
+ end
82
+ end
83
+
84
+ # Pending cases still run: a pending case that PASSES is reported as
85
+ # pending-satisfied - a cleanup alarm, the marker must be removed.
86
+ def run_pending(id, test_case, flavor_module, stats, failures)
87
+ stats[:pending] += 1
88
+ before = failures.size
89
+ run_case(test_case, flavor_module, stats, failures)
90
+ return failures.slice!(before..) if failures.size > before
91
+
92
+ stats[:pending_satisfied] += 1
93
+ puts " PENDING-SATISFIED #{id} - remove the marker"
94
+ end
95
+
96
+ def run_case(test_case, flavor_module, stats, failures)
97
+ if test_case.error_case?
98
+ execute_error_case(test_case, flavor_module, stats, failures)
99
+ return
100
+ end
101
+ return stats[:quarantined] += 1 if test_case.quarantined?
102
+
103
+ stats[:cases] += 1
104
+ Checks.check_case(test_case, flavor_module).each do |mismatch|
105
+ stat_for(mismatch, stats)
106
+ failures << mismatch
107
+ end
108
+ end
109
+
110
+ def stat_for(mismatch, stats)
111
+ case mismatch
112
+ when / raised\z/ then stats[:fail_parse] += 1
113
+ when /canonical hash/ then stats[:fail_tree] += 1
114
+ when / human\z/, / urn\z/
115
+ stats[:"fail_#{mismatch.split.last}"] += 1
116
+ when / alias/ then stats[:fail_alias] += 1
117
+ else stats[:fail_roundtrip] += 1
118
+ end
119
+ end
120
+
121
+ # Negatives (fail fixtures decoded by the exporter). A row with
122
+ # expect.error must be rejected by the reference; a row without it
123
+ # is the exporter's reclassification alarm - the fail-fixture line
124
+ # now parses, so the fixture belongs in pass/ - reported, never
125
+ # gated, like the PENDING-SATISFIED alarm.
126
+ def execute_negative(test_case, flavor_module, stats, failures)
127
+ return stats[:reclass] += 1 unless test_case.error_case?
128
+
129
+ execute_error_case(test_case, flavor_module, stats, failures)
130
+ end
131
+
132
+ def execute_error_case(test_case, flavor_module, stats, failures)
133
+ stats[:error_cases] += 1
134
+ flavor_module.parse(test_case.input)
135
+ stats[:fail_error] += 1
136
+ failures << "#{test_case.id} unexpectedly parsed"
137
+ rescue StandardError
138
+ stats[:error_ok] += 1
139
+ end
140
+
141
+ def report(flavor, stats, failures)
142
+ puts format(
143
+ "%-6s cases=%-6d err=%-4d quar=%-3d fail=%-4d " \
144
+ "pend=%-4d pendok=%-3d reclass=%-4d review=%d",
145
+ flavor, stats[:cases], stats[:error_cases], stats[:quarantined],
146
+ failures.size, stats[:pending], stats[:pending_satisfied],
147
+ stats[:reclass], stats[:review]
148
+ )
149
+ failures.first(10).each { |f| puts " FAIL #{f}" }
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+
6
+ module Pubid
7
+ # The shared, language-independent conformance corpus (conformance/).
8
+ # The identifier structure in each case is THE GEM'S OWN canonical
9
+ # to_hash, embedded verbatim (the same wire format relaton persists).
10
+ # Nothing is reinvented: the corpus adds only what the gem cannot know -
11
+ # representations, normalization aliases, styling, and neutral error
12
+ # codes for debt/negative records.
13
+ module Conformance
14
+ autoload :Checks, "pubid/conformance/checks"
15
+ autoload :Corpus, "pubid/conformance/corpus"
16
+ autoload :Generator, "pubid/conformance/generator"
17
+ autoload :Pending, "pubid/conformance/pending"
18
+ autoload :Runner, "pubid/conformance/runner"
19
+
20
+ # Neutral error vocabulary. Each implementation maps its native
21
+ # exceptions onto these codes; the corpus never records Ruby classes.
22
+ ERROR_CODES = {
23
+ "Parslet::ParseFailed" => "parse_failed",
24
+ "ArgumentError" => "invalid_argument",
25
+ }.freeze
26
+
27
+ class << self
28
+ def error_code_for(exception)
29
+ ERROR_CODES.fetch(exception.class.name, "unclassified")
30
+ end
31
+
32
+ # Heuristic PubID styling version. v1 = legacy "ISO/R 2533/3-1975";
33
+ # v2 = dotted/uppercase ("AMD.1"); v3 = modern ("Amd 1"). Heuristic
34
+ # until the model exposes its parsed format (TODO.restructure/12).
35
+ def style_for(input)
36
+ return "v1" if /\bISO\/R\b|\bISO R\b/.match?(input)
37
+ return "v2" if /\b(?:Amd|AMD|Cor|COR|Suppl|SUPPL)\.\d/.match?(input)
38
+
39
+ "v3"
40
+ end
41
+
42
+ # Hygiene only (not a redesign): guarantee the corpus is pure YAML -
43
+ # component objects leaking through serialization are reduced to
44
+ # their string rendering.
45
+ def plainify(value)
46
+ case value
47
+ when Hash then value.to_h { |key, entry| [key.to_s, plainify(entry)] }
48
+ when Array then value.map { |entry| plainify(entry) }
49
+ when String, Integer, Float, TrueClass, FalseClass, NilClass then value
50
+ else value.to_s
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/pubid/core.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  # Core module for shared functionality across all flavors.
5
6
  module Core
6
7
  autoload :UpdateCodes, "#{__dir__}/core/update_codes"
8
+ autoload :PatternDocGenerator, "#{__dir__}/core/pattern_doc_generator"
7
9
  end
8
10
  end
@@ -101,7 +101,7 @@ module Pubid
101
101
  else
102
102
  begin
103
103
  Pubid::Iso.parse(base_str)
104
- rescue Pubid::Errors::ParseError
104
+ rescue Parslet::ParseFailed
105
105
  Pubid::Iec.parse(base_str)
106
106
  end
107
107
  end
@@ -128,17 +128,17 @@ module Pubid
128
128
  end
129
129
  amendment_str = "#{base_str}/Amd #{parsed_hash[:adoption_amendment]}:#{wrapped_amendment_year}"
130
130
 
131
- wrapped_identifier = if adoption_org.start_with?("ISO", "CEI")
132
- Pubid::Iso.parse(amendment_str)
133
- elsif adoption_org.start_with?("IEC")
134
- Pubid::Iec.parse(amendment_str)
135
- else
136
- begin
137
- Pubid::Iso.parse(amendment_str)
138
- rescue Pubid::Errors::ParseError
139
- Pubid::Iec.parse(amendment_str)
140
- end
141
- end
131
+ base = if adoption_org.start_with?("ISO", "CEI")
132
+ Pubid::Iso.parse(amendment_str)
133
+ elsif adoption_org.start_with?("IEC")
134
+ Pubid::Iec.parse(amendment_str)
135
+ else
136
+ begin
137
+ Pubid::Iso.parse(amendment_str)
138
+ rescue Parslet::ParseFailed
139
+ Pubid::Iec.parse(amendment_str)
140
+ end
141
+ end
142
142
  else
143
143
  # No amendment, simple base identifier
144
144
  wrapped_id_str = "#{adoption_org} #{adoption_number}#{year_sep}#{wrapped_year}"
@@ -148,20 +148,20 @@ module Pubid
148
148
  wrapped_id_str = wrapped_id_str.sub("CEI/IEC", "IEC")
149
149
  end
150
150
 
151
- wrapped_identifier = if adoption_org.start_with?("ISO", "CEI")
152
- Pubid::Iso.parse(wrapped_id_str)
153
- elsif adoption_org.start_with?("IEC")
154
- Pubid::Iec.parse(wrapped_id_str)
155
- else
156
- begin
157
- Pubid::Iso.parse(wrapped_id_str)
158
- rescue Pubid::Errors::ParseError
159
- Pubid::Iec.parse(wrapped_id_str)
160
- end
161
- end
151
+ base = if adoption_org.start_with?("ISO", "CEI")
152
+ Pubid::Iso.parse(wrapped_id_str)
153
+ elsif adoption_org.start_with?("IEC")
154
+ Pubid::Iec.parse(wrapped_id_str)
155
+ else
156
+ begin
157
+ Pubid::Iso.parse(wrapped_id_str)
158
+ rescue Parslet::ParseFailed
159
+ Pubid::Iec.parse(wrapped_id_str)
160
+ end
161
+ end
162
162
  end
163
163
 
164
- adoption.wrapped_identifier = wrapped_identifier
164
+ adoption.base = base
165
165
 
166
166
  # Handle reaffirmation
167
167
  if parsed_hash[:reaffirmation]
@@ -195,23 +195,25 @@ module Pubid
195
195
  # SERIES matched - build Series identifier with CAN/CSA- prefix
196
196
  # The Series.to_s will handle rendering as "CAN/CSA-A220 SERIES-06"
197
197
  # (keeping the full prefix instead of "CAN/CSA-A220...")
198
- wrapped_identifier = build_series(wrapped_data)
198
+ base = build_series(wrapped_data)
199
199
  # Set the full publisher_prefix on the wrapped Series for proper rendering
200
- # The test expects wrapped_identifier.publisher_prefix to be "CAN/CSA-"
200
+ # The test expects base.publisher_prefix to be "CAN/CSA-"
201
201
  elsif parsed_hash.key?(:cec_part)
202
202
  # CEC matched - build Cec identifier
203
- wrapped_identifier = build_cec(wrapped_data)
203
+ base = build_cec(wrapped_data)
204
204
  else
205
205
  # Standard CSA identifier
206
- wrapped_identifier = build_single(wrapped_data)
206
+ base = build_single(wrapped_data)
207
207
  end
208
- wrapped_identifier.publisher_prefix = original_prefix if original_prefix && wrapped_identifier.class.attributes.key?(:publisher_prefix)
208
+ base.publisher_prefix = original_prefix if original_prefix && base.class.attributes.key?(:publisher_prefix)
209
209
 
210
- canadian_adopted.wrapped_identifier = wrapped_identifier
210
+ canadian_adopted.base = base
211
211
 
212
- # Handle reaffirmation - supports both 2-digit and 4-digit years
213
- # Note: CanadianAdopted (WrapperIdentifier) doesn't track original_reaffirmation_4digit
214
- # That's only for Series/Cec identifiers
212
+ # Handle reaffirmation - supports both 2-digit and 4-digit years.
213
+ # The wrapper's own original_reaffirmation_4digit is left at its
214
+ # default: CanadianAdopted#to_s only consults it when the wrapped id
215
+ # carries no reaffirmation of its own, and Series/Cec track the printed
216
+ # width themselves.
215
217
  if parsed_hash[:reaffirmation_4digit]
216
218
  # Original was 4-digit (e.g., "R2019")
217
219
  canadian_adopted.reaffirmation = parsed_hash[:reaffirmation_4digit].to_s
@@ -251,7 +253,7 @@ module Pubid
251
253
 
252
254
  # Code
253
255
  if parsed_hash[:code]
254
- series.code = Components::Code.new(value: parsed_hash[:code].to_s)
256
+ series.number = parsed_hash[:code].to_s
255
257
  end
256
258
 
257
259
  # Year format and year
@@ -451,10 +453,10 @@ module Pubid
451
453
  def build_combined(parsed_hash)
452
454
  combined = Identifiers::Combined.new
453
455
 
454
- # Build each part
455
- combined.first = build_single(parsed_hash[:first])
456
- combined.second = build_single(parsed_hash[:second])
457
- combined.third = build_single(parsed_hash[:third]) if parsed_hash[:third]
456
+ # Build each designation, in printed order, into one collection.
457
+ combined.identifiers = %i[first second third]
458
+ .filter_map { |key| parsed_hash[key] }
459
+ .map { |part| build_single(part) }
458
460
 
459
461
  # Detect separator from parser marker
460
462
  combined.separator = if parsed_hash[:comma_separator]
@@ -495,15 +497,13 @@ module Pubid
495
497
  identifier_class = select_identifier_class(data)
496
498
  identifier = identifier_class.new
497
499
 
498
- # Publisher prefix (CAN/CSA-, CAN3-, or CSA)
499
- # For code_only identifiers (no prefix in original), set to empty string
500
- # to prevent default "CSA" from being added in rendering
500
+ # Publisher prefix (CAN/CSA-, CAN3-, or CSA).
501
+ # A code with no prefix in the original is a code_only identifier; the
502
+ # flag stops rendering from supplying the default "CSA".
501
503
  if data[:publisher_prefix]
502
504
  identifier.publisher_prefix = data[:publisher_prefix].to_s
503
- elsif data[:code] && !data[:publisher_prefix]
504
- # Code exists but no publisher_prefix = code_only identifier
505
- # Use empty string to indicate "no prefix wanted"
506
- identifier.publisher_prefix = ""
505
+ elsif data[:code]
506
+ identifier.code_only = true
507
507
  end
508
508
 
509
509
  # Publisher presence flag
@@ -520,13 +520,13 @@ module Pubid
520
520
  # Pattern: "C22.1-15" should become code="C22.1", year="2015"
521
521
  if !data[:year] && code_value =~ /^(.+)-(\d{2})$/
522
522
  # Split code and year
523
- identifier.code = Components::Code.new(value: $1)
523
+ identifier.number = $1
524
524
  # Convert 2-digit year to 4-digit
525
525
  year_2digit = $2
526
526
  identifier.year = "20#{year_2digit}"
527
527
  identifier.year_format = "dash"
528
528
  else
529
- identifier.code = Components::Code.new(value: code_value)
529
+ identifier.number = code_value
530
530
  end
531
531
  end
532
532